feat: add SiliconFlow.cn chat completion and embedding support (#5316) * feat: add SiliconFlow.cn endpoint support and embedding vectors Chat completion: - Add endpoint selection dropdown (Global/.com vs China/.cn) to existing SiliconFlow provider, following the Z.AI endpoint pattern - Backend switches API URL based on selected endpoint - Add /api-url slash command support for endpoint switching Embeddings: - Add SiliconFlow as a vector/embedding source (OpenAI-compatible) - Support both .com and .cn endpoints via siliconflow_endpoint setting borrowed from the main connection panel (Vertex AI pattern) - Superset model list with platform attribution (.cn) markers - Models: Qwen3-Embedding (0.6B/4B/8B) + BGE/BCE models (.cn only) * Add filter by models type * Load embedding models from endpoint * Improve api-url command declaration * Support endpoint override in custom-request service --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>

2cb1861db6f25f4a4d41fce2a8d79dd0e1f27415

Xiangzhe <32761048+xz-dev@users.noreply.github.com>

Signed
12 files changed, +202 -12Showing whitespace changes
public/index.html+5 -0
@@ -3556,6 +3556,11 @@
35563556 <div data-for="api_key_siliconflow" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
35573557 For privacy reasons, your API key will be hidden after you click 'Connect'.
35583558 </div>
3559+ <h4 data-i18n="SiliconFlow Endpoint">SiliconFlow Endpoint</h4>
3560+ <select id="siliconflow_endpoint">
3561+ <option value="global" data-i18n="Global (siliconflow.com)">Global (siliconflow.com)</option>
3562+ <option value="cn" data-i18n="China (siliconflow.cn)">China (siliconflow.cn)</option>
3563+ </select>
35593564 <h4>SiliconFlow Model</h4>
35603565 <select id="model_siliconflow_select">
35613566 <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option>
public/scripts/custom-request.js+1 -1
@@ -591,7 +591,7 @@ export class ChatCompletionService {
591591 }
592592
593593 // Ensure api-url is properly applied for all sources that accept it
594594 ['custom_url', 'vertexai_region', 'zai_endpoint', 'siliconflow_endpoint'].forEach(field => {
595595 // The order is: connection profile => CC preset => CC settings
596596 overridePayload[field] = overridePayload[field] || settings[field] || oai_settings[field];
597597 });
public/scripts/extensions/shared.js+1 -0
@@ -438,6 +438,7 @@ export class ConnectionManagerRequestService {
438438 custom_url: profile['api-url'],
439439 vertexai_region: profile['api-url'],
440440 zai_endpoint: profile['api-url'],
441+ siliconflow_endpoint: profile['api-url'],
441442 reverse_proxy: proxyPreset?.url,
442443 proxy_password: proxyPreset?.password,
443444 custom_prompt_post_processing: profile['prompt-post-processing'],
public/scripts/extensions/vectors/index.js+55 -1
@@ -72,6 +72,7 @@ const settings = {
7272 google_model: 'text-embedding-005',
7373 chutes_model: 'chutes-qwen-qwen3-embedding-8b',
7474 nanogpt_model: 'text-embedding-3-small',
75+ siliconflow_model: 'Qwen/Qwen3-Embedding-0.6B',
7576 summarize: false,
7677 summarize_sent: false,
7778 summary_source: 'main',
@@ -837,6 +838,10 @@ function getVectorsRequestBody(args = {}) {
837838 case 'nanogpt':
838839 body.model = extension_settings.vectors.nanogpt_model;
839840 break;
841+ case 'siliconflow':
842+ body.model = extension_settings.vectors.siliconflow_model;
843+ body.siliconflow_endpoint = oai_settings.siliconflow_endpoint;
844+ break;
840845 default:
841846 break;
842847 }
@@ -929,7 +934,8 @@ function throwIfSourceInvalid() {
929934 settings.source === 'mistral' && !secret_state[SECRET_KEYS.MISTRALAI] ||
930935 settings.source === 'togetherai' && !secret_state[SECRET_KEYS.TOGETHERAI] ||
931936 settings.source === 'nomicai' && !secret_state[SECRET_KEYS.NOMICAI] ||
932937 settings.source === 'cohere' && !secret_state[SECRET_KEYS.COHERE]) {||
938+ settings.source === 'siliconflow' && !secret_state[SECRET_KEYS.SILICONFLOW]) {
933939 throw new Error('Vectors: API key missing', { cause: 'api_key_missing' });
934940 }
935941
@@ -1147,6 +1153,7 @@ function toggleSettings() {
11471153 $('#webllm_vectorsModel').toggle(settings.source === 'webllm');
11481154 $('#koboldcpp_vectorsModel').toggle(settings.source === 'koboldcpp');
11491155 $('#google_vectorsModel').toggle(settings.source === 'palm' || settings.source === 'vertexai');
1156+ $('#siliconflow_vectorsModel').toggle(settings.source === 'siliconflow');
11501157 $('#vector_altEndpointUrl').toggle(vectorApiRequiresUrl.includes(settings.source));
11511158 switch (settings.source) {
11521159 case 'webllm':
@@ -1164,6 +1171,9 @@ function toggleSettings() {
11641171 case 'nanogpt':
11651172 loadNanoGPTModels();
11661173 break;
1174+ case 'siliconflow':
1175+ loadSiliconFlowModels();
1176+ break;
11671177 }
11681178}
11691179
@@ -1312,6 +1322,45 @@ function populateOpenRouterModelSelect(models) {
13121322 $('#vectors_openrouter_model').val(settings.openrouter_model);
13131323}
13141324
1325+async function loadSiliconFlowModels() {
1326+ try {
1327+ const response = await fetch('/api/openai/siliconflow/models/embedding', {
1328+ method: 'POST',
1329+ headers: getRequestHeaders(),
1330+ body: JSON.stringify({
1331+ siliconflow_endpoint: oai_settings.siliconflow_endpoint,
1332+ }),
1333+ });
1334+
1335+ if (!response.ok) {
1336+ throw new Error(`HTTP ${response.status}`);
1337+ }
1338+
1339+ /** @type {Array<any>} */
1340+ const data = await response.json();
1341+ const models = Array.isArray(data) ? data : [];
1342+ populateSiliconFlowModelSelect(models);
1343+ } catch (err) {
1344+ console.warn('SiliconFlow models fetch failed', err);
1345+ populateSiliconFlowModelSelect([]);
1346+ }
1347+}
1348+
1349+function populateSiliconFlowModelSelect(models) {
1350+ const select = $('#vectors_siliconflow_model');
1351+ select.empty();
1352+ for (const m of models) {
1353+ const option = document.createElement('option');
1354+ option.value = m.id;
1355+ option.text = m.id;
1356+ select.append(option);
1357+ }
1358+ if (!settings.siliconflow_model && models.length) {
1359+ settings.siliconflow_model = models[0].id;
1360+ }
1361+ $('#vectors_siliconflow_model').val(settings.siliconflow_model);
1362+}
1363+
13151364/**
13161365 * Executes a function with WebLLM error handling.
13171366 * @param {function(): Promise<T>} func Function to execute
@@ -1724,6 +1773,11 @@ jQuery(async () => {
17241773 Object.assign(extension_settings.vectors, settings);
17251774 saveSettingsDebounced();
17261775 });
1776+ $('#vectors_siliconflow_model').val(settings.siliconflow_model).on('change', () => {
1777+ settings.siliconflow_model = String($('#vectors_siliconflow_model').val());
1778+ Object.assign(extension_settings.vectors, settings);
1779+ saveSettingsDebounced();
1780+ });
17271781 $('#vectors_openrouter_model').val(settings.openrouter_model).on('change', () => {
17281782 settings.openrouter_model = String($('#vectors_openrouter_model').val());
17291783 Object.assign(extension_settings.vectors, settings);
public/scripts/extensions/vectors/settings.html+10 -0
@@ -25,6 +25,7 @@
2525 <option value="ollama">Ollama</option>
2626 <option value="openai">OpenAI</option>
2727 <option value="openrouter">OpenRouter</option>
28+ <option value="siliconflow">SiliconFlow</option>
2829 <option value="togetherai">TogetherAI</option>
2930 <option value="vllm">vLLM</option>
3031 <option value="webllm" data-i18n="WebLLM Extension">WebLLM Extension</option>
@@ -194,6 +195,15 @@
194195 Hint: Set your OpenRouter API key in API Connections.
195196 </i>
196197 </div>
198+ <div class="flex-container flexFlowColumn" id="siliconflow_vectorsModel">
199+ <label for="vectors_siliconflow_model" data-i18n="Vectorization Model">
200+ Vectorization Model
201+ </label>
202+ <select id="vectors_siliconflow_model" class="text_pole"></select>
203+ <i data-i18n="Hint: Set your SiliconFlow API key in API Connections.">
204+ Hint: Set your SiliconFlow API key in API Connections.
205+ </i>
206+ </div>
197207
198208 <div class="flex-container marginTopBot5">
199209 <div class="flex-container flex1 flexFlowColumn" title="How many last messages will be matched for relevance.">
public/scripts/openai.js+19 -0
@@ -263,6 +263,11 @@ export const ZAI_ENDPOINT = {
263263 CODING: 'coding',
264264};
265265
266+export const SILICONFLOW_ENDPOINT = {
267+ GLOBAL: 'global',
268+ CN: 'cn',
269+};
270+
266271const sensitiveFields = [
267272 'reverse_proxy',
268273 'proxy_password',
@@ -310,6 +315,7 @@ export const settingsToUpdate = {
310315 chutes_model: ['#model_chutes_select', 'chutes_model', false, true],
311316 chutes_sort_models: ['#chutes_sort_models', 'chutes_sort_models', false, true],
312317 siliconflow_model: ['#model_siliconflow_select', 'siliconflow_model', false, true],
318+ siliconflow_endpoint: ['#siliconflow_endpoint', 'siliconflow_endpoint', false, true],
313319 electronhub_model: ['#model_electronhub_select', 'electronhub_model', false, true],
314320 electronhub_sort_models: ['#electronhub_sort_models', 'electronhub_sort_models', false, true],
315321 electronhub_group_models: ['#electronhub_group_models', 'electronhub_group_models', false, true],
@@ -419,6 +425,7 @@ const default_settings = {
419425 chutes_model: 'deepseek-ai/DeepSeek-V3-0324',
420426 chutes_sort_models: 'alphabetically',
421427 siliconflow_model: 'deepseek-ai/DeepSeek-V3',
428+ siliconflow_endpoint: SILICONFLOW_ENDPOINT.GLOBAL,
422429 electronhub_model: 'gpt-4o-mini',
423430 electronhub_sort_models: 'alphabetically',
424431 electronhub_group_models: false,
@@ -2804,6 +2811,10 @@ export async function createGenerationParameters(settings, model, type, messages
28042811 delete generate_data.frequency_penalty;
28052812 }
28062813
2814+ if (settings.chat_completion_source === chat_completion_sources.SILICONFLOW) {
2815+ generate_data.siliconflow_endpoint = settings.siliconflow_endpoint || SILICONFLOW_ENDPOINT.GLOBAL;
2816+ }
2817+
28072818 // https://docs.nano-gpt.com/api-reference/endpoint/chat-completion#temperature-&-nucleus
28082819 if (settings.chat_completion_source === chat_completion_sources.NANOGPT) {
28092820 generate_data.top_k = Number(settings.top_k_openai);
@@ -4257,6 +4268,10 @@ async function getStatusOpen() {
42574268 data.azure_api_version = oai_settings.azure_api_version;
42584269 }
42594270
4271+ if (oai_settings.chat_completion_source === chat_completion_sources.SILICONFLOW) {
4272+ data.siliconflow_endpoint = oai_settings.siliconflow_endpoint;
4273+ }
4274+
42604275 const canBypass = (oai_settings.chat_completion_source === chat_completion_sources.OPENAI && oai_settings.bypass_status_check) || oai_settings.chat_completion_source === chat_completion_sources.CUSTOM;
42614276 if (canBypass) {
42624277 setOnlineStatus(t`Status check bypassed`);
@@ -6929,6 +6944,10 @@ export function initOpenAI() {
69296944 oai_settings.zai_endpoint = String($(this).val());
69306945 saveSettingsDebounced();
69316946 });
6947+ $('#siliconflow_endpoint').on('input', function () {
6948+ oai_settings.siliconflow_endpoint = String($(this).val());
6949+ saveSettingsDebounced();
6950+ });
69326951 $('#vertexai_service_account_json').on('input', onVertexAIServiceAccountJsonChange);
69336952 $('#vertexai_validate_service_account').on('click', onVertexAIValidateServiceAccount);
69346953 $('#vertexai_clear_service_account').on('click', onVertexAIClearServiceAccount);
public/scripts/slash-commands.js+30 -3
@@ -70,7 +70,7 @@ import { hideChatMessageRange } from './chats.js';
7070import { getContext, saveMetadataDebounced } from './extensions.js';
7171import { getRegexedString, regex_placement } from './extensions/regex/engine.js';
7272import { findGroupMemberId, groups, is_group_generating, openGroupById, regenerateGroup, resetSelectedGroup, saveGroupChat, selected_group, getGroupMembers } from './group-chats.js';
7373import { chat_completion_sources, oai_settings, promptManager, SILICONFLOW_ENDPOINT, ZAI_ENDPOINT } from './openai.js';
7474import { user_avatar } from './personas.js';
7575import { addEphemeralStoppingString, chat_styles, context_presets, flushEphemeralStoppingStrings, playMessageSound, power_user } from './power-user.js';
7676import { SERVER_INPUTS, textgen_types, textgenerationwebui_settings } from './textgen-settings.js';
@@ -3132,8 +3132,9 @@ export function initDefaultSlashCommands() {
31323132 new SlashCommandEnumValue('custom', 'custom OpenAI-compatible', enumTypes.getBasedOnIndex(UNIQUE_APIS.findIndex(x => x === 'openai')), 'O'),
31333133 new SlashCommandEnumValue('zai', 'Z.AI', enumTypes.getBasedOnIndex(UNIQUE_APIS.findIndex(x => x === 'zai')), 'Z'),
31343134 new SlashCommandEnumValue('vertexai', 'Google Vertex AI', enumTypes.getBasedOnIndex(UNIQUE_APIS.findIndex(x => x === 'vertexai')), 'V'),
3135+ new SlashCommandEnumValue('siliconflow', 'SiliconFlow', enumTypes.getBasedOnIndex(UNIQUE_APIS.findIndex(x => x === 'siliconflow')), 'S'),
31353136 new SlashCommandEnumValue('kobold', 'KoboldAI Classic', enumTypes.getBasedOnIndex(UNIQUE_APIS.findIndex(x => x === 'kobold')), 'K'),
31363137 ...Object.values(textgen_types).filter(api => Object.keys(SERVER_INPUTS).includes(api)).map(api => new SlashCommandEnumValue(api, null, enumTypes.getBasedOnIndex(UNIQUE_APIS.findIndex(x => x === 'textgenerationwebui')), 'T')),
31373138 ],
31383139 }),
31393140 SlashCommandNamedArgument.fromProps({
@@ -3165,7 +3166,7 @@ export function initDefaultSlashCommands() {
31653166 ${t`If a manual API is provided to <b>set</b> the URL, make sure to set <code>connect=false</code>, as auto-connect only works for the currently selected API, or consider switching to it with <code>/api</code> first.`}
31663167 </div>
31673168 <div>
31683169 ${t`This slash command works for most of the Text Completion sources, KoboldAI Classic, and also Custom OpenAI compatible, Z.AI, SiliconFlow, and Google Vertex AI for the Chat Completion sources. If unsure which APIs are supported, check the auto-completion of the optional <code>api</code> argument of this command.`}
31693170 </div>
31703171 `,
31713172 }));
@@ -6715,6 +6716,32 @@ async function setApiUrlCallback({ api = null, connect = 'true', quiet = 'false'
67156716 return oai_settings.zai_endpoint || ZAI_ENDPOINT.COMMON;
67166717 }
67176718
6719+ const isCurrentlySiliconFlow = main_api === 'openai' && oai_settings.chat_completion_source === chat_completion_sources.SILICONFLOW;
6720+ if (api === chat_completion_sources.SILICONFLOW || (!api && isCurrentlySiliconFlow)) {
6721+ if (!url) {
6722+ return oai_settings.siliconflow_endpoint || SILICONFLOW_ENDPOINT.GLOBAL;
6723+ }
6724+
6725+ const permittedValues = Object.values(SILICONFLOW_ENDPOINT);
6726+ if (!permittedValues.includes(url)) {
6727+ !isQuiet && toastr.warning(t`Valid options are: ${permittedValues.join(', ')}`, t`SiliconFlow endpoint '${url}' is not a valid option.`);
6728+ return '';
6729+ }
6730+
6731+ if (!isCurrentlySiliconFlow && autoConnect) {
6732+ toastr.warning(t`SiliconFlow is not the currently selected API, so we cannot do an auto-connect. Consider switching to it via /api beforehand.`);
6733+ return '';
6734+ }
6735+
6736+ $('#siliconflow_endpoint').val(url).trigger('input');
6737+
6738+ if (autoConnect) {
6739+ $('#api_button_openai').trigger('click');
6740+ }
6741+
6742+ return oai_settings.siliconflow_endpoint || SILICONFLOW_ENDPOINT.GLOBAL;
6743+ }
6744+
67186745 const isCurrentlyVertexAI = main_api === 'openai' && oai_settings.chat_completion_source === chat_completion_sources.VERTEXAI;
67196746 if (api === chat_completion_sources.VERTEXAI || (!api && isCurrentlyVertexAI)) {
67206747 const defaultRegion = 'us-central1';
src/constants.js+5 -0
@@ -551,3 +551,8 @@ export const ZAI_ENDPOINT = {
551551 COMMON: 'common',
552552 CODING: 'coding',
553553};
554+
555+export const SILICONFLOW_ENDPOINT = {
556+ GLOBAL: 'global',
557+ CN: 'cn',
558+};
src/endpoints/backends/chat-completions.js+9 -2
@@ -17,6 +17,7 @@ import {
1717 OPENAI_VERBOSITY_MODELS,
1818 OPENROUTER_HEADERS,
1919 VERTEX_SAFETY,
20+ SILICONFLOW_ENDPOINT,
2021 ZAI_ENDPOINT,
2122} from '../../constants.js';
2223import {
@@ -87,6 +88,7 @@ const API_COMETAPI = 'https://api.cometapi.com/v1';
8788const API_ZAI_COMMON = 'https://api.z.ai/api/paas/v4';
8889const API_ZAI_CODING = 'https://api.z.ai/api/coding/paas/v4';
8990const API_SILICONFLOW = 'https://api.siliconflow.com/v1';
91+const API_SILICONFLOW_CN = 'https://api.siliconflow.cn/v1';
9092const API_OPENROUTER = 'https://openrouter.ai/api/v1';
9193
9294/**
@@ -1825,9 +1827,12 @@ router.post('/status', async function (request, statusResponse) {
18251827 return statusResponse.status(500).send({ error: true, message: 'Failed to connect to the Azure endpoint.' });
18261828 }
18271829 } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.SILICONFLOW) {
1828- apiUrl = API_SILICONFLOW;
1830+ const defaultApiUrl = request.body.siliconflow_endpoint === SILICONFLOW_ENDPOINT.CN
1831+ ? API_SILICONFLOW_CN : API_SILICONFLOW;
1832+ apiUrl = defaultApiUrl;
18291833 apiKey = readSecret(request.user.directories, SECRET_KEYS.SILICONFLOW);
18301834 headers = {};
1835+ queryParams = { type: 'text', sub_type: 'chat' };
18311836 } else {
18321837 console.warn('This chat completion source is not supported yet.');
18331838 return statusResponse.status(400).send({ error: true });
@@ -2301,7 +2306,9 @@ router.post('/generate', async function (request, response) {
23012306 setJsonObjectFormat(bodyParams, request.body.messages, request.body.json_schema);
23022307 }
23032308 } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.SILICONFLOW) {
2304- apiUrl = API_SILICONFLOW;
2309+ const defaultApiUrl = request.body.siliconflow_endpoint === SILICONFLOW_ENDPOINT.CN
2310+ ? API_SILICONFLOW_CN : API_SILICONFLOW;
2311+ apiUrl = defaultApiUrl;
23052312 apiKey = readSecret(request.user.directories, SECRET_KEYS.SILICONFLOW);
23062313 headers = {};
23072314 bodyParams = {};
src/endpoints/openai.js+42 -1
@@ -8,7 +8,7 @@ import express from 'express';
88import { getConfigValue, mergeObjectWithYaml, excludeKeysByYaml, trimV1, delay } from '../util.js';
99import { setAdditionalHeaders } from '../additional-headers.js';
1010import { readSecret, SECRET_KEYS } from './secrets.js';
1111import { AIMLAPI_HEADERS, OPENROUTER_HEADERS, SILICONFLOW_ENDPOINT, ZAI_ENDPOINT } from '../constants.js';
1212
1313export const router = express.Router();
1414
@@ -529,6 +529,47 @@ router.post('/nanogpt/models/embedding', async (request, response) => {
529529 }
530530});
531531
532+router.post('/siliconflow/models/embedding', async (request, response) => {
533+ try {
534+ const key = readSecret(request.user.directories, SECRET_KEYS.SILICONFLOW);
535+
536+ if (!key) {
537+ console.warn('No SiliconFlow key found');
538+ return response.sendStatus(400);
539+ }
540+
541+ const apiUrl = request.body.siliconflow_endpoint === SILICONFLOW_ENDPOINT.CN
542+ ? 'https://api.siliconflow.cn/v1/models?type=text&sub_type=embedding'
543+ : 'https://api.siliconflow.com/v1/models?type=text&sub_type=embedding';
544+
545+ const result = await fetch(apiUrl, {
546+ method: 'GET',
547+ headers: {
548+ Authorization: `Bearer ${key}`,
549+ },
550+ });
551+
552+ if (!result.ok) {
553+ const text = await result.text();
554+ console.warn('SiliconFlow embedding models request failed', result.statusText, text);
555+ return response.status(500).send(text);
556+ }
557+
558+ /** @type {any} */
559+ const data = await result.json();
560+
561+ if (!Array.isArray(data?.data)) {
562+ console.warn('SiliconFlow embedding models response invalid', data);
563+ return response.sendStatus(500);
564+ }
565+
566+ return response.json(data.data);
567+ } catch (error) {
568+ console.error('SiliconFlow embedding models fetch failed', error);
569+ response.sendStatus(500);
570+ }
571+});
572+
532573router.post('/generate-image', async (request, response) => {
533574 try {
534575 const key = readSecret(request.user.directories, SECRET_KEYS.OPENAI);
src/endpoints/vectors.js+12 -0
@@ -38,6 +38,7 @@ const SOURCES = [
3838 'openrouter',
3939 'chutes',
4040 'nanogpt',
41+ 'siliconflow',
4142];
4243
4344/**
@@ -85,6 +86,8 @@ async function getVector(source, sourceSettings, text, isQuery, directories) {
8586 return getOpenAIVector(text, source, directories, sourceSettings.model);
8687 case 'nanogpt':
8788 return getOpenAIVector(text, source, directories, sourceSettings.model);
89+ case 'siliconflow':
90+ return getOpenAIVector(text, source, directories, sourceSettings.model, sourceSettings.urlOverride);
8891 }
8992
9093 throw new Error(`Unknown vector source ${source}`);
@@ -156,6 +159,9 @@ async function getBatchVector(source, sourceSettings, texts, isQuery, directorie
156159 case 'nanogpt':
157160 results.push(...await getOpenAIBatchVector(batch, source, directories, sourceSettings.model));
158161 break;
162+ case 'siliconflow':
163+ results.push(...await getOpenAIBatchVector(batch, source, directories, sourceSettings.model, sourceSettings.urlOverride));
164+ break;
159165 default:
160166 throw new Error(`Unknown vector source ${source}`);
161167 }
@@ -248,6 +254,12 @@ function getSourceSettings(source, request) {
248254 return {
249255 model: String(request.body.model || 'text-embedding-3-small'),
250256 };
257+ case 'siliconflow':
258+ return {
259+ model: String(request.body.model || 'Qwen/Qwen3-Embedding-0.6B'),
260+ urlOverride: request.body.siliconflow_endpoint === 'cn'
261+ ? 'https://api.siliconflow.cn/v1' : null,
262+ };
251263 default:
252264 return {};
253265 }
src/vectors/openai-vectors.js+13 -4
@@ -54,6 +54,13 @@ const SOURCES = {
5454 headers: {},
5555 processBody: () => {},
5656 },
57+ 'siliconflow': {
58+ secretKey: SECRET_KEYS.SILICONFLOW,
59+ url: 'https://api.siliconflow.com/v1',
60+ model: 'Qwen/Qwen3-Embedding-0.6B',
61+ headers: {},
62+ processBody: () => {},
63+ },
5764};
5865
5966/**
@@ -62,9 +69,10 @@ const SOURCES = {
6269 * @param {string} source - The source of the vector
6370 * @param {import('../users.js').UserDirectoryList} directories - The directories object for the user
6471 * @param {string} model - The model to use for the embedding
72+ * @param {string|null} urlOverride - Optional URL override for the API endpoint
6573 * @returns {Promise<number[][]>} - The array of vectors for the texts
6674 */
6775export async function getOpenAIBatchVector(texts, source, directories, model = '', urlOverride = null) {
6876 const config = SOURCES[source];
6977
7078 if (!config) {
@@ -80,7 +88,7 @@ export async function getOpenAIBatchVector(texts, source, directories, model = '
8088 }
8189
8290 const modelName = model || config.model;
8391 const url = urlOverride || config.url.replace('{{MODEL}}', modelName);
8492 const body = {
8593 input: texts,
8694 model: modelName,
@@ -127,9 +135,10 @@ export async function getOpenAIBatchVector(texts, source, directories, model = '
127135 * @param {string} source - The source of the vector
128136 * @param {import('../users.js').UserDirectoryList} directories - The directories object for the user
129137 * @param {string} model - The model to use for the embedding
138+ * @param {string|null} urlOverride - Optional URL override for the API endpoint
130139 * @returns {Promise<number[]>} - The vector for the text
131140 */
132141export async function getOpenAIVector(text, source, directories, model = '', urlOverride = null) {
133142 const vectors = await getOpenAIBatchVector([text], source, directories, model, urlOverride);
134143 return vectors[0];
135144}