From 4ca9863f3809be18e39fc7439be234035f54abeb Mon Sep 17 00:00:00 2001 From: DeathStalker471 Date: Thu, 30 Apr 2026 13:55:42 -0700 Subject: [PATCH] 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> --- public/index.html | 13 + public/scripts/openai.js | 25 +- public/scripts/textgen-models.js | 290 +++++++++++++++++++++ src/endpoints/backends/chat-completions.js | 7 + src/endpoints/nanogpt.js | 36 +++ 5 files changed, 370 insertions(+), 1 deletion(-) diff --git a/public/index.html b/public/index.html index 75c3e0bbf..af2d4ef94 100644 --- a/public/index.html +++ b/public/index.html @@ -3648,6 +3648,19 @@ +
+

+ Model Providers + +

+ + +

Cloudflare Workers AI API Key

diff --git a/public/scripts/openai.js b/public/scripts/openai.js index 7217967e2..97b96658e 100644 --- a/public/scripts/openai.js +++ b/public/scripts/openai.js @@ -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(); diff --git a/public/scripts/textgen-models.js b/public/scripts/textgen-models.js index a44d5e956..1af689e10 100644 --- a/public/scripts/textgen-models.js +++ b/public/scripts/textgen-models.js @@ -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($('