Add NanoGPT embeddings support for Vector Storage (#5150) * Initial plan * Add NanoGPT embeddings support for Vector Storage Co-authored-by: Cohee1207 <18619528+Cohee1207@users.noreply.github.com> * Fix models loading --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Cohee1207 <18619528+Cohee1207@users.noreply.github.com>

5832cb8b07f8e7c62880b77be49b765dccb6fe49

Copilot <198982749+Copilot@users.noreply.github.com>

Signed
5 files changed, +112 -0Showing whitespace changes
public/scripts/extensions/vectors/index.js+48 -0
@@ -71,6 +71,7 @@ const settings = {
7171 webllm_model: '',
7272 google_model: 'text-embedding-005',
7373 chutes_model: 'chutes-qwen-qwen3-embedding-8b',
74+ nanogpt_model: 'text-embedding-3-small',
7475 summarize: false,
7576 summarize_sent: false,
7677 summary_source: 'main',
@@ -834,6 +835,9 @@ function getVectorsRequestBody(args = {}) {
834835 case 'chutes':
835836 body.model = extension_settings.vectors.chutes_model;
836837 break;
838+ case 'nanogpt':
839+ body.model = extension_settings.vectors.nanogpt_model;
840+ break;
837841 default:
838842 break;
839843 }
@@ -919,6 +923,7 @@ function throwIfSourceInvalid() {
919923 if (settings.source === 'openai' && !secret_state[SECRET_KEYS.OPENAI] ||
920924 settings.source === 'electronhub' && !secret_state[SECRET_KEYS.ELECTRONHUB] ||
921925 settings.source === 'chutes' && !secret_state[SECRET_KEYS.CHUTES] ||
926+ settings.source === 'nanogpt' && !secret_state[SECRET_KEYS.NANOGPT] ||
922927 settings.source === 'openrouter' && !secret_state[SECRET_KEYS.OPENROUTER] ||
923928 settings.source === 'palm' && !secret_state[SECRET_KEYS.MAKERSUITE] ||
924929 settings.source === 'vertexai' && !secret_state[SECRET_KEYS.VERTEXAI] && !secret_state[SECRET_KEYS.VERTEXAI_SERVICE_ACCOUNT] ||
@@ -1134,6 +1139,7 @@ function toggleSettings() {
11341139 $('#openai_vectorsModel').toggle(settings.source === 'openai');
11351140 $('#electronhub_vectorsModel').toggle(settings.source === 'electronhub');
11361141 $('#chutes_vectorsModel').toggle(settings.source === 'chutes');
1142+ $('#nanogpt_vectorsModel').toggle(settings.source === 'nanogpt');
11371143 $('#openrouter_vectorsModel').toggle(settings.source === 'openrouter');
11381144 $('#cohere_vectorsModel').toggle(settings.source === 'cohere');
11391145 $('#ollama_vectorsModel').toggle(settings.source === 'ollama');
@@ -1157,6 +1163,9 @@ function toggleSettings() {
11571163 case 'chutes':
11581164 loadChutesModels();
11591165 break;
1166+ case 'nanogpt':
1167+ loadNanoGPTModels();
1168+ break;
11601169 }
11611170}
11621171
@@ -1194,6 +1203,40 @@ function populateChutesModelSelect(models) {
11941203 $('#vectors_chutes_model').val(settings.chutes_model);
11951204}
11961205
1206+async function loadNanoGPTModels() {
1207+ try {
1208+ const response = await fetch('/api/openai/nanogpt/models/embedding', {
1209+ method: 'POST',
1210+ headers: getRequestHeaders({ omitContentType: true }),
1211+ });
1212+ if (!response.ok) {
1213+ throw new Error(`HTTP ${response.status}`);
1214+ }
1215+ /** @type {Array<any>} */
1216+ const data = await response.json();
1217+ const models = Array.isArray(data) ? data : [];
1218+ populateNanoGPTModelSelect(models);
1219+ } catch (err) {
1220+ console.warn('NanoGPT models fetch failed', err);
1221+ populateNanoGPTModelSelect([]);
1222+ }
1223+}
1224+
1225+function populateNanoGPTModelSelect(models) {
1226+ const select = $('#vectors_nanogpt_model');
1227+ select.empty();
1228+ for (const m of models) {
1229+ const option = document.createElement('option');
1230+ option.value = m.id;
1231+ option.text = m.name || m.id;
1232+ select.append(option);
1233+ }
1234+ if (!settings.nanogpt_model && models.length) {
1235+ settings.nanogpt_model = models[0].id;
1236+ }
1237+ $('#vectors_nanogpt_model').val(settings.nanogpt_model);
1238+}
1239+
11971240async function loadElectronHubModels() {
11981241 try {
11991242 const response = await fetch('/api/openai/electronhub/models', {
@@ -1679,6 +1722,11 @@ jQuery(async () => {
16791722 Object.assign(extension_settings.vectors, settings);
16801723 saveSettingsDebounced();
16811724 });
1725+ $('#vectors_nanogpt_model').val(settings.nanogpt_model).on('change', () => {
1726+ settings.nanogpt_model = String($('#vectors_nanogpt_model').val());
1727+ Object.assign(extension_settings.vectors, settings);
1728+ saveSettingsDebounced();
1729+ });
16821730 $('#vectors_openrouter_model').val(settings.openrouter_model).on('change', () => {
16831731 settings.openrouter_model = String($('#vectors_openrouter_model').val());
16841732 Object.assign(extension_settings.vectors, settings);
public/scripts/extensions/vectors/settings.html+10 -0
@@ -20,6 +20,7 @@
2020 <option value="llamacpp">llama.cpp</option>
2121 <option value="transformers" data-i18n="Local (Transformers)">Local (Transformers)</option>
2222 <option value="mistral">MistralAI</option>
23+ <option value="nanogpt">NanoGPT</option>
2324 <option value="nomicai">NomicAI</option>
2425 <option value="ollama">Ollama</option>
2526 <option value="openai">OpenAI</option>
@@ -38,6 +39,15 @@
3839 Hint: Set your Chutes API key in API Connections.
3940 </i>
4041 </div>
42+ <div class="flex-container flexFlowColumn" id="nanogpt_vectorsModel">
43+ <label for="vectors_nanogpt_model" data-i18n="Vectorization Model">
44+ Vectorization Model
45+ </label>
46+ <select id="vectors_nanogpt_model" class="text_pole"></select>
47+ <i data-i18n="Hint: Set your NanoGPT API key in API Connections.">
48+ Hint: Set your NanoGPT API key in API Connections.
49+ </i>
50+ </div>
4151 <div class="flex-container flexFlowColumn" id="electronhub_vectorsModel">
4252 <label for="vectors_electronhub_model" data-i18n="Vectorization Model">
4353 Vectorization Model
src/endpoints/openai.js+37 -0
@@ -493,6 +493,43 @@ router.post('/chutes/models/embedding', async (request, response) => {
493493 }
494494});
495495
496+router.post('/nanogpt/models/embedding', async (request, response) => {
497+ try {
498+ const key = readSecret(request.user.directories, SECRET_KEYS.NANOGPT);
499+
500+ if (!key) {
501+ console.warn('No NanoGPT key found');
502+ return response.sendStatus(400);
503+ }
504+
505+ const result = await fetch('https://nano-gpt.com/api/v1/embedding-models', {
506+ method: 'GET',
507+ headers: {
508+ 'Authorization': `Bearer ${key}`,
509+ 'Accept-Encoding': 'identity',
510+ },
511+ });
512+
513+ if (!result.ok) {
514+ const text = await result.text();
515+ console.warn('NanoGPT embedding models request failed', result.statusText, text);
516+ return response.status(500).send(text);
517+ }
518+
519+ /** @type {any} */
520+ const data = await result.json();
521+
522+ if (!Array.isArray(data?.data)) {
523+ console.warn('NanoGPT embedding models response invalid', data);
524+ return response.sendStatus(500);
525+ }
526+ return response.json(data.data);
527+ } catch (error) {
528+ console.error('NanoGPT embedding models fetch failed', error);
529+ response.sendStatus(500);
530+ }
531+});
532+
496533router.post('/generate-image', async (request, response) => {
497534 try {
498535 const key = readSecret(request.user.directories, SECRET_KEYS.OPENAI);
src/endpoints/vectors.js+10 -0
@@ -37,6 +37,7 @@ const SOURCES = [
3737 'electronhub',
3838 'openrouter',
3939 'chutes',
40+ 'nanogpt',
4041];
4142
4243/**
@@ -82,6 +83,8 @@ async function getVector(source, sourceSettings, text, isQuery, directories) {
8283 return sourceSettings.embeddings[text];
8384 case 'chutes':
8485 return getOpenAIVector(text, source, directories, sourceSettings.model);
86+ case 'nanogpt':
87+ return getOpenAIVector(text, source, directories, sourceSettings.model);
8588 }
8689
8790 throw new Error(`Unknown vector source ${source}`);
@@ -150,6 +153,9 @@ async function getBatchVector(source, sourceSettings, texts, isQuery, directorie
150153 case 'chutes':
151154 results.push(...await getOpenAIBatchVector(batch, source, directories, sourceSettings.model));
152155 break;
156+ case 'nanogpt':
157+ results.push(...await getOpenAIBatchVector(batch, source, directories, sourceSettings.model));
158+ break;
153159 default:
154160 throw new Error(`Unknown vector source ${source}`);
155161 }
@@ -238,6 +244,10 @@ function getSourceSettings(source, request) {
238244 return {
239245 model: String(request.body.model || 'chutes-qwen-qwen3-embedding-8b'),
240246 };
247+ case 'nanogpt':
248+ return {
249+ model: String(request.body.model || 'text-embedding-3-small'),
250+ };
241251 default:
242252 return {};
243253 }
src/vectors/openai-vectors.js+7 -0
@@ -47,6 +47,13 @@ const SOURCES = {
4747 body.model = null;
4848 },
4949 },
50+ 'nanogpt': {
51+ secretKey: SECRET_KEYS.NANOGPT,
52+ url: 'https://nano-gpt.com/api/v1',
53+ model: 'text-embedding-3-small',
54+ headers: {},
55+ processBody: () => {},
56+ },
5057};
5158
5259/**