feat: add nanogpt provider selection (#5544)

* add nanogpt provider selection

* update payg text

* fix: resync providers from endpoint

---------

Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
This commit is contained in:
DeathStalker471
2026-04-30 13:55:42 -07:00
committed by GitHub
parent 3a9c10d680
commit 4ca9863f38
5 changed files with 370 additions and 1 deletions
+24 -1
View File
@@ -80,7 +80,7 @@ import { t } from './i18n.js';
import { ToolManager } from './tool-calling.js';
import { accountStorage } from './util/AccountStorage.js';
import { COMETAPI_IGNORE_PATTERNS, IGNORE_SYMBOL, MEDIA_DISPLAY, MEDIA_TYPE } from './constants.js';
import { syncOpenRouterProvidersForModel, updateOpenRouterProvidersWarning } from './textgen-models.js';
import { syncNanoGptProvidersForModel, syncOpenRouterProvidersForModel, updateNanoGptProvidersWarning, updateOpenRouterProvidersWarning } from './textgen-models.js';
export {
openai_messages_count,
@@ -329,6 +329,8 @@ export const settingsToUpdate = {
minimax_endpoint: ['#minimax_endpoint', 'minimax_endpoint', false, true],
electronhub_model: ['#model_electronhub_select', 'electronhub_model', false, true],
nanogpt_model: ['#model_nanogpt_select', 'nanogpt_model', false, true],
nanogpt_provider: ['#nanogpt_provider', 'nanogpt_provider', false, true],
nanogpt_payg_override: ['#nanogpt_payg_override', 'nanogpt_payg_override', true, true],
deepseek_model: ['#model_deepseek_select', 'deepseek_model', false, true],
aimlapi_model: ['#model_aimlapi_select', 'aimlapi_model', false, true],
xai_model: ['#model_xai_select', 'xai_model', false, true],
@@ -443,6 +445,8 @@ const default_settings = {
minimax_endpoint: MINIMAX_ENDPOINT.GLOBAL,
electronhub_model: 'gpt-4o-mini',
nanogpt_model: 'gpt-4o-mini',
nanogpt_provider: '',
nanogpt_payg_override: false,
deepseek_model: 'deepseek-v4-flash',
aimlapi_model: 'chatgpt-4o-latest',
xai_model: 'grok-3-beta',
@@ -2828,6 +2832,11 @@ export async function createGenerationParameters(settings, model, type, messages
generate_data.middleout = settings.openrouter_middleout;
}
if (settings.chat_completion_source === chat_completion_sources.NANOGPT) {
generate_data.nanogpt_provider = settings.nanogpt_provider;
generate_data.nanogpt_payg_override = settings.nanogpt_payg_override;
}
if ([chat_completion_sources.MAKERSUITE, chat_completion_sources.VERTEXAI].includes(settings.chat_completion_source)) {
const stopStringsLimit = 5;
generate_data.top_k = Number(settings.top_k_openai);
@@ -4290,6 +4299,7 @@ function loadOpenAISettings(data, settings) {
$('#openrouter_providers_chat').trigger('change');
$('#openrouter_quantizations_chat').trigger('change');
$('#nanogpt_provider').trigger('change');
$('#chat_completion_source').trigger('change');
}
@@ -4937,6 +4947,7 @@ function onSettingsPresetChange() {
$('#chat_completion_source').trigger('change');
$('#openrouter_providers_chat').trigger('change');
$('#openrouter_quantizations_chat').trigger('change');
$('#nanogpt_provider').trigger('change');
}
$('#openai_logit_bias_preset').trigger('change');
@@ -5464,6 +5475,7 @@ async function onModelChange() {
console.log('NanoGPT model changed to', value);
oai_settings.nanogpt_model = value;
syncNanoGptProvidersForModel(value, '#nanogpt_provider');
}
if ($(this).is('#model_deepseek_select')) {
@@ -7134,6 +7146,17 @@ export function initOpenAI() {
saveSettingsDebounced();
});
$('#nanogpt_provider').on('change', function () {
oai_settings.nanogpt_provider = String($(this).val() || '');
updateNanoGptProvidersWarning('#nanogpt_provider');
saveSettingsDebounced();
});
$('#nanogpt_payg_override').on('input', function () {
oai_settings.nanogpt_payg_override = !!$(this).prop('checked');
saveSettingsDebounced();
});
$('#bind_preset_to_connection').on('input', function () {
oai_settings.bind_preset_to_connection = !!$(this).prop('checked');
saveSettingsDebounced();
+290
View File
@@ -108,6 +108,214 @@ const OPENROUTER_PROVIDERS = [
'Z.AI',
];
/**
* List of NanoGPT providers.
* Providers endpoint: https://nano-gpt.com/api/models/providers
* @type {{id: string, label: string}[]}
*/
const NANOGPT_PROVIDERS = [
{
'id': 'akash',
'label': 'Akash',
},
{
'id': 'alibaba',
'label': 'Alibaba',
},
{
'id': 'ambient',
'label': 'Ambient',
},
{
'id': 'arliai',
'label': 'ArliAI',
},
{
'id': 'atlascloud',
'label': 'AtlasCloud',
},
{
'id': 'azure',
'label': 'Azure',
},
{
'id': 'awsbedrock',
'label': 'Amazon Bedrock',
},
{
'id': 'baidu',
'label': 'Baidu',
},
{
'id': 'baseten',
'label': 'BaseTen',
},
{
'id': 'cerebras',
'label': 'Cerebras',
},
{
'id': 'chutes',
'label': 'Chutes',
},
{
'id': 'clarifai',
'label': 'Clarifai',
},
{
'id': 'cloudflare',
'label': 'Cloudflare',
},
{
'id': 'crusoe',
'label': 'Crusoe',
},
{
'id': 'dekallm',
'label': 'DekaLLM',
},
{
'id': 'deepinfra',
'label': 'DeepInfra',
},
{
'id': 'deepseek',
'label': 'DeepSeek',
},
{
'id': 'fireworks',
'label': 'Fireworks',
},
{
'id': 'friendli',
'label': 'Friendli',
},
{
'id': 'gmicloud',
'label': 'GMICloud',
},
{
'id': 'lilac',
'label': 'Lilac',
},
{
'id': 'google',
'label': 'Google',
},
{
'id': 'groq',
'label': 'Groq',
},
{
'id': 'hyperbolic',
'label': 'Hyperbolic',
},
{
'id': 'ionet',
'label': 'Io Net',
},
{
'id': 'inceptron',
'label': 'Inceptron',
},
{
'id': 'mancer',
'label': 'Mancer',
},
{
'id': 'mara',
'label': 'Mara',
},
{
'id': 'meganova',
'label': 'MegaNova',
},
{
'id': 'minimax',
'label': 'MiniMax',
},
{
'id': 'modelrun',
'label': 'ModelRun',
},
{
'id': 'moonshot',
'label': 'Moonshot',
},
{
'id': 'morph',
'label': 'Morph',
},
{
'id': 'ncompass',
'label': 'NCompass',
},
{
'id': 'nebius',
'label': 'Nebius',
},
{
'id': 'neuralwatt',
'label': 'Neuralwatt',
},
{
'id': 'nextbit',
'label': 'NextBit',
},
{
'id': 'novita',
'label': 'Novita',
},
{
'id': 'parasail',
'label': 'Parasail',
},
{
'id': 'phala',
'label': 'Phala',
},
{
'id': 'redpill',
'label': 'Redpill',
},
{
'id': 'sambanova',
'label': 'SambaNova',
},
{
'id': 'sambanova-high-throughput',
'label': 'SambaNova (High Throughput)',
},
{
'id': 'siliconflow',
'label': 'SiliconFlow',
},
{
'id': 'streamlake',
'label': 'StreamLake',
},
{
'id': 'tinfoil',
'label': 'Tinfoil',
},
{
'id': 'together',
'label': 'Together',
},
{
'id': 'venice',
'label': 'Venice',
},
{
'id': 'wandb',
'label': 'Weights & Biases',
},
{
'id': 'zai',
'label': 'Z.AI',
},
];
const OPENROUTER_PROVIDER_WARNING_SELECTORS = {
'#openrouter_providers_text': {
fallbackSelector: '#openrouter_allow_fallbacks_textgenerationwebui',
@@ -187,6 +395,72 @@ export async function syncOpenRouterProvidersForModel(modelId, providersSelector
}
}
export async function syncNanoGptProvidersForModel(modelId, providersSelector) {
const $providers = $(providersSelector);
const refreshWarningState = () => {
updateNanoGptProvidersWarning(providersSelector);
};
if (!modelId) {
$providers.find('option').prop('disabled', false);
$providers.trigger('change.select2');
refreshWarningState();
return;
}
try {
const response = await fetch('/api/nanogpt/models/providers', {
method: 'POST',
headers: getRequestHeaders(),
body: JSON.stringify({ model: modelId }),
});
if (!response.ok) {
refreshWarningState();
return;
}
const data = await response.json();
const providerIds = Array.isArray(data?.providers) ? data.providers : [];
if (!data?.supportsProviderSelection || providerIds.length === 0) {
$providers.find('option').each(function () {
$(this).prop('disabled', Boolean($(this).val()));
});
$providers.trigger('change').trigger('change.select2');
refreshWarningState();
return;
}
$providers.find('option').each(function () {
const value = $(this).val();
const isAvailable = !value || providerIds.includes(value);
$(this).prop('disabled', !isAvailable);
});
$providers.trigger('change.select2');
refreshWarningState();
} catch (error) {
console.error('Failed to fetch NanoGPT providers for model', error);
refreshWarningState();
}
}
export function updateNanoGptProvidersWarning(providersSelector) {
const $providers = $(providersSelector);
if ($providers.length === 0) {
return;
}
const selectedCount = $providers.find('option:selected').length;
const applicableSelectedCount = $providers.find('option:selected:not(:disabled)').length;
const showWarning = selectedCount > 0 && applicableSelectedCount === 0;
$('#nanogpt_provider_warning').toggleClass('displayNone', !showWarning);
}
export async function loadOllamaModels(data) {
if (!Array.isArray(data)) {
console.error('Invalid Ollama models data', data);
@@ -1084,6 +1358,14 @@ export function initTextGenModels() {
}));
}
const nanoGptProvidersSelect = $('#nanogpt_provider');
for (const provider of NANOGPT_PROVIDERS) {
nanoGptProvidersSelect.append($('<option>', {
value: provider.id,
text: provider.label,
}));
}
if (!isMobile()) {
$('#mancer_model').select2({
placeholder: t`Select a model`,
@@ -1178,5 +1460,13 @@ export function initTextGenModels() {
$(this).append($element);
$(this).trigger('change');
});
nanoGptProvidersSelect.select2({
sorter: data => data.sort((a, b) => a.text.localeCompare(b.text)),
placeholder: t`Select providers. No selection = all providers.`,
searchInputPlaceholder: t`Search providers...`,
searchInputCssClass: 'text_pole',
width: '100%',
allowClear: true,
});
}
}