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>
This commit is contained in:
Copilot
2026-02-14 00:31:30 +02:00
committed by GitHub
parent 266f3ade0d
commit 5832cb8b07
5 changed files with 112 additions and 0 deletions
@@ -71,6 +71,7 @@ const settings = {
webllm_model: '',
google_model: 'text-embedding-005',
chutes_model: 'chutes-qwen-qwen3-embedding-8b',
nanogpt_model: 'text-embedding-3-small',
summarize: false,
summarize_sent: false,
summary_source: 'main',
@@ -834,6 +835,9 @@ function getVectorsRequestBody(args = {}) {
case 'chutes':
body.model = extension_settings.vectors.chutes_model;
break;
case 'nanogpt':
body.model = extension_settings.vectors.nanogpt_model;
break;
default:
break;
}
@@ -919,6 +923,7 @@ function throwIfSourceInvalid() {
if (settings.source === 'openai' && !secret_state[SECRET_KEYS.OPENAI] ||
settings.source === 'electronhub' && !secret_state[SECRET_KEYS.ELECTRONHUB] ||
settings.source === 'chutes' && !secret_state[SECRET_KEYS.CHUTES] ||
settings.source === 'nanogpt' && !secret_state[SECRET_KEYS.NANOGPT] ||
settings.source === 'openrouter' && !secret_state[SECRET_KEYS.OPENROUTER] ||
settings.source === 'palm' && !secret_state[SECRET_KEYS.MAKERSUITE] ||
settings.source === 'vertexai' && !secret_state[SECRET_KEYS.VERTEXAI] && !secret_state[SECRET_KEYS.VERTEXAI_SERVICE_ACCOUNT] ||
@@ -1134,6 +1139,7 @@ function toggleSettings() {
$('#openai_vectorsModel').toggle(settings.source === 'openai');
$('#electronhub_vectorsModel').toggle(settings.source === 'electronhub');
$('#chutes_vectorsModel').toggle(settings.source === 'chutes');
$('#nanogpt_vectorsModel').toggle(settings.source === 'nanogpt');
$('#openrouter_vectorsModel').toggle(settings.source === 'openrouter');
$('#cohere_vectorsModel').toggle(settings.source === 'cohere');
$('#ollama_vectorsModel').toggle(settings.source === 'ollama');
@@ -1157,6 +1163,9 @@ function toggleSettings() {
case 'chutes':
loadChutesModels();
break;
case 'nanogpt':
loadNanoGPTModels();
break;
}
}
@@ -1194,6 +1203,40 @@ function populateChutesModelSelect(models) {
$('#vectors_chutes_model').val(settings.chutes_model);
}
async function loadNanoGPTModels() {
try {
const response = await fetch('/api/openai/nanogpt/models/embedding', {
method: 'POST',
headers: getRequestHeaders({ omitContentType: true }),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
/** @type {Array<any>} */
const data = await response.json();
const models = Array.isArray(data) ? data : [];
populateNanoGPTModelSelect(models);
} catch (err) {
console.warn('NanoGPT models fetch failed', err);
populateNanoGPTModelSelect([]);
}
}
function populateNanoGPTModelSelect(models) {
const select = $('#vectors_nanogpt_model');
select.empty();
for (const m of models) {
const option = document.createElement('option');
option.value = m.id;
option.text = m.name || m.id;
select.append(option);
}
if (!settings.nanogpt_model && models.length) {
settings.nanogpt_model = models[0].id;
}
$('#vectors_nanogpt_model').val(settings.nanogpt_model);
}
async function loadElectronHubModels() {
try {
const response = await fetch('/api/openai/electronhub/models', {
@@ -1679,6 +1722,11 @@ jQuery(async () => {
Object.assign(extension_settings.vectors, settings);
saveSettingsDebounced();
});
$('#vectors_nanogpt_model').val(settings.nanogpt_model).on('change', () => {
settings.nanogpt_model = String($('#vectors_nanogpt_model').val());
Object.assign(extension_settings.vectors, settings);
saveSettingsDebounced();
});
$('#vectors_openrouter_model').val(settings.openrouter_model).on('change', () => {
settings.openrouter_model = String($('#vectors_openrouter_model').val());
Object.assign(extension_settings.vectors, settings);
@@ -20,6 +20,7 @@
<option value="llamacpp">llama.cpp</option>
<option value="transformers" data-i18n="Local (Transformers)">Local (Transformers)</option>
<option value="mistral">MistralAI</option>
<option value="nanogpt">NanoGPT</option>
<option value="nomicai">NomicAI</option>
<option value="ollama">Ollama</option>
<option value="openai">OpenAI</option>
@@ -38,6 +39,15 @@
Hint: Set your Chutes API key in API Connections.
</i>
</div>
<div class="flex-container flexFlowColumn" id="nanogpt_vectorsModel">
<label for="vectors_nanogpt_model" data-i18n="Vectorization Model">
Vectorization Model
</label>
<select id="vectors_nanogpt_model" class="text_pole"></select>
<i data-i18n="Hint: Set your NanoGPT API key in API Connections.">
Hint: Set your NanoGPT API key in API Connections.
</i>
</div>
<div class="flex-container flexFlowColumn" id="electronhub_vectorsModel">
<label for="vectors_electronhub_model" data-i18n="Vectorization Model">
Vectorization Model
+37
View File
@@ -493,6 +493,43 @@ router.post('/chutes/models/embedding', async (request, response) => {
}
});
router.post('/nanogpt/models/embedding', async (request, response) => {
try {
const key = readSecret(request.user.directories, SECRET_KEYS.NANOGPT);
if (!key) {
console.warn('No NanoGPT key found');
return response.sendStatus(400);
}
const result = await fetch('https://nano-gpt.com/api/v1/embedding-models', {
method: 'GET',
headers: {
'Authorization': `Bearer ${key}`,
'Accept-Encoding': 'identity',
},
});
if (!result.ok) {
const text = await result.text();
console.warn('NanoGPT embedding models request failed', result.statusText, text);
return response.status(500).send(text);
}
/** @type {any} */
const data = await result.json();
if (!Array.isArray(data?.data)) {
console.warn('NanoGPT embedding models response invalid', data);
return response.sendStatus(500);
}
return response.json(data.data);
} catch (error) {
console.error('NanoGPT embedding models fetch failed', error);
response.sendStatus(500);
}
});
router.post('/generate-image', async (request, response) => {
try {
const key = readSecret(request.user.directories, SECRET_KEYS.OPENAI);
+10
View File
@@ -37,6 +37,7 @@ const SOURCES = [
'electronhub',
'openrouter',
'chutes',
'nanogpt',
];
/**
@@ -82,6 +83,8 @@ async function getVector(source, sourceSettings, text, isQuery, directories) {
return sourceSettings.embeddings[text];
case 'chutes':
return getOpenAIVector(text, source, directories, sourceSettings.model);
case 'nanogpt':
return getOpenAIVector(text, source, directories, sourceSettings.model);
}
throw new Error(`Unknown vector source ${source}`);
@@ -150,6 +153,9 @@ async function getBatchVector(source, sourceSettings, texts, isQuery, directorie
case 'chutes':
results.push(...await getOpenAIBatchVector(batch, source, directories, sourceSettings.model));
break;
case 'nanogpt':
results.push(...await getOpenAIBatchVector(batch, source, directories, sourceSettings.model));
break;
default:
throw new Error(`Unknown vector source ${source}`);
}
@@ -238,6 +244,10 @@ function getSourceSettings(source, request) {
return {
model: String(request.body.model || 'chutes-qwen-qwen3-embedding-8b'),
};
case 'nanogpt':
return {
model: String(request.body.model || 'text-embedding-3-small'),
};
default:
return {};
}
+7
View File
@@ -47,6 +47,13 @@ const SOURCES = {
body.model = null;
},
},
'nanogpt': {
secretKey: SECRET_KEYS.NANOGPT,
url: 'https://nano-gpt.com/api/v1',
model: 'text-embedding-3-small',
headers: {},
processBody: () => {},
},
};
/**