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>
Signed| @@ -3648,6 +3648,19 @@ | |||
| 3648 | <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option> | 3648 | <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option> |
| 3649 | </select> | 3649 | </select> |
| 3650 | </div> | 3650 | </div> |
| 3651 | <div> | ||
| 3652 | <h4> | ||
| 3653 | <span data-i18n="Model Providers">Model Providers</span> | ||
| 3654 | <i id="nanogpt_provider_warning" class="fa-solid fa-circle-exclamation displayNone" title="Deselect inapplicable provider(s) or select an applicable provider to avoid a 404 error."></i> | ||
| 3655 | </h4> | ||
| 3656 | <select id="nanogpt_provider"> | ||
| 3657 | <option value="" data-i18n="Auto">Auto</option> | ||
| 3658 | </select> | ||
| 3659 | <label class="checkbox_label marginTopBot5" for="nanogpt_payg_override" data-i18n="[title]Force NanoGPT pay-as-you-go billing for this request." title="Force NanoGPT pay-as-you-go billing for this request."> | ||
| 3660 | <input id="nanogpt_payg_override" type="checkbox" /> | ||
| 3661 | <span data-i18n="Use pay-as-you-go billing">Use pay-as-you-go billing</span> | ||
| 3662 | </label> | ||
| 3663 | </div> | ||
| 3651 | </div> | 3664 | </div> |
| 3652 | <div id="workers_ai_form" data-source="workers_ai"> | 3665 | <div id="workers_ai_form" data-source="workers_ai"> |
| 3653 | <h4><a href="https://dash.cloudflare.com/?to=/:account/ai/workers-ai/api-quick-start" target="_blank" rel="noopener noreferrer" data-i18n="Cloudflare Workers AI API Key">Cloudflare Workers AI API Key</a></h4> | 3666 | <h4><a href="https://dash.cloudflare.com/?to=/:account/ai/workers-ai/api-quick-start" target="_blank" rel="noopener noreferrer" data-i18n="Cloudflare Workers AI API Key">Cloudflare Workers AI API Key</a></h4> |
| @@ -80,7 +80,7 @@ import { t } from './i18n.js'; | |||
| 80 | import { ToolManager } from './tool-calling.js'; | 80 | import { ToolManager } from './tool-calling.js'; |
| 81 | import { accountStorage } from './util/AccountStorage.js'; | 81 | import { accountStorage } from './util/AccountStorage.js'; |
| 82 | import { COMETAPI_IGNORE_PATTERNS, IGNORE_SYMBOL, MEDIA_DISPLAY, MEDIA_TYPE } from './constants.js'; | 82 | import { COMETAPI_IGNORE_PATTERNS, IGNORE_SYMBOL, MEDIA_DISPLAY, MEDIA_TYPE } from './constants.js'; |
| 83 | import { syncOpenRouterProvidersForModel, updateOpenRouterProvidersWarning } from './textgen-models.js'; | 83 | import { syncNanoGptProvidersForModel, syncOpenRouterProvidersForModel, updateNanoGptProvidersWarning, updateOpenRouterProvidersWarning } from './textgen-models.js'; |
| 84 | 84 | ||
| 85 | export { | 85 | export { |
| 86 | openai_messages_count, | 86 | openai_messages_count, |
| @@ -329,6 +329,8 @@ export const settingsToUpdate = { | |||
| 329 | minimax_endpoint: ['#minimax_endpoint', 'minimax_endpoint', false, true], | 329 | minimax_endpoint: ['#minimax_endpoint', 'minimax_endpoint', false, true], |
| 330 | electronhub_model: ['#model_electronhub_select', 'electronhub_model', false, true], | 330 | electronhub_model: ['#model_electronhub_select', 'electronhub_model', false, true], |
| 331 | nanogpt_model: ['#model_nanogpt_select', 'nanogpt_model', false, true], | 331 | nanogpt_model: ['#model_nanogpt_select', 'nanogpt_model', false, true], |
| 332 | nanogpt_provider: ['#nanogpt_provider', 'nanogpt_provider', false, true], | ||
| 333 | nanogpt_payg_override: ['#nanogpt_payg_override', 'nanogpt_payg_override', true, true], | ||
| 332 | deepseek_model: ['#model_deepseek_select', 'deepseek_model', false, true], | 334 | deepseek_model: ['#model_deepseek_select', 'deepseek_model', false, true], |
| 333 | aimlapi_model: ['#model_aimlapi_select', 'aimlapi_model', false, true], | 335 | aimlapi_model: ['#model_aimlapi_select', 'aimlapi_model', false, true], |
| 334 | xai_model: ['#model_xai_select', 'xai_model', false, true], | 336 | xai_model: ['#model_xai_select', 'xai_model', false, true], |
| @@ -443,6 +445,8 @@ const default_settings = { | |||
| 443 | minimax_endpoint: MINIMAX_ENDPOINT.GLOBAL, | 445 | minimax_endpoint: MINIMAX_ENDPOINT.GLOBAL, |
| 444 | electronhub_model: 'gpt-4o-mini', | 446 | electronhub_model: 'gpt-4o-mini', |
| 445 | nanogpt_model: 'gpt-4o-mini', | 447 | nanogpt_model: 'gpt-4o-mini', |
| 448 | nanogpt_provider: '', | ||
| 449 | nanogpt_payg_override: false, | ||
| 446 | deepseek_model: 'deepseek-v4-flash', | 450 | deepseek_model: 'deepseek-v4-flash', |
| 447 | aimlapi_model: 'chatgpt-4o-latest', | 451 | aimlapi_model: 'chatgpt-4o-latest', |
| 448 | xai_model: 'grok-3-beta', | 452 | xai_model: 'grok-3-beta', |
| @@ -2828,6 +2832,11 @@ export async function createGenerationParameters(settings, model, type, messages | |||
| 2828 | generate_data.middleout = settings.openrouter_middleout; | 2832 | generate_data.middleout = settings.openrouter_middleout; |
| 2829 | } | 2833 | } |
| 2830 | 2834 | ||
| 2835 | if (settings.chat_completion_source === chat_completion_sources.NANOGPT) { | ||
| 2836 | generate_data.nanogpt_provider = settings.nanogpt_provider; | ||
| 2837 | generate_data.nanogpt_payg_override = settings.nanogpt_payg_override; | ||
| 2838 | } | ||
| 2839 | |||
| 2831 | if ([chat_completion_sources.MAKERSUITE, chat_completion_sources.VERTEXAI].includes(settings.chat_completion_source)) { | 2840 | if ([chat_completion_sources.MAKERSUITE, chat_completion_sources.VERTEXAI].includes(settings.chat_completion_source)) { |
| 2832 | const stopStringsLimit = 5; | 2841 | const stopStringsLimit = 5; |
| 2833 | generate_data.top_k = Number(settings.top_k_openai); | 2842 | generate_data.top_k = Number(settings.top_k_openai); |
| @@ -4290,6 +4299,7 @@ function loadOpenAISettings(data, settings) { | |||
| 4290 | 4299 | ||
| 4291 | $('#openrouter_providers_chat').trigger('change'); | 4300 | $('#openrouter_providers_chat').trigger('change'); |
| 4292 | $('#openrouter_quantizations_chat').trigger('change'); | 4301 | $('#openrouter_quantizations_chat').trigger('change'); |
| 4302 | $('#nanogpt_provider').trigger('change'); | ||
| 4293 | $('#chat_completion_source').trigger('change'); | 4303 | $('#chat_completion_source').trigger('change'); |
| 4294 | } | 4304 | } |
| 4295 | 4305 | ||
| @@ -4937,6 +4947,7 @@ function onSettingsPresetChange() { | |||
| 4937 | $('#chat_completion_source').trigger('change'); | 4947 | $('#chat_completion_source').trigger('change'); |
| 4938 | $('#openrouter_providers_chat').trigger('change'); | 4948 | $('#openrouter_providers_chat').trigger('change'); |
| 4939 | $('#openrouter_quantizations_chat').trigger('change'); | 4949 | $('#openrouter_quantizations_chat').trigger('change'); |
| 4950 | $('#nanogpt_provider').trigger('change'); | ||
| 4940 | } | 4951 | } |
| 4941 | 4952 | ||
| 4942 | $('#openai_logit_bias_preset').trigger('change'); | 4953 | $('#openai_logit_bias_preset').trigger('change'); |
| @@ -5464,6 +5475,7 @@ async function onModelChange() { | |||
| 5464 | 5475 | ||
| 5465 | console.log('NanoGPT model changed to', value); | 5476 | console.log('NanoGPT model changed to', value); |
| 5466 | oai_settings.nanogpt_model = value; | 5477 | oai_settings.nanogpt_model = value; |
| 5478 | syncNanoGptProvidersForModel(value, '#nanogpt_provider'); | ||
| 5467 | } | 5479 | } |
| 5468 | 5480 | ||
| 5469 | if ($(this).is('#model_deepseek_select')) { | 5481 | if ($(this).is('#model_deepseek_select')) { |
| @@ -7134,6 +7146,17 @@ export function initOpenAI() { | |||
| 7134 | saveSettingsDebounced(); | 7146 | saveSettingsDebounced(); |
| 7135 | }); | 7147 | }); |
| 7136 | 7148 | ||
| 7149 | $('#nanogpt_provider').on('change', function () { | ||
| 7150 | oai_settings.nanogpt_provider = String($(this).val() || ''); | ||
| 7151 | updateNanoGptProvidersWarning('#nanogpt_provider'); | ||
| 7152 | saveSettingsDebounced(); | ||
| 7153 | }); | ||
| 7154 | |||
| 7155 | $('#nanogpt_payg_override').on('input', function () { | ||
| 7156 | oai_settings.nanogpt_payg_override = !!$(this).prop('checked'); | ||
| 7157 | saveSettingsDebounced(); | ||
| 7158 | }); | ||
| 7159 | |||
| 7137 | $('#bind_preset_to_connection').on('input', function () { | 7160 | $('#bind_preset_to_connection').on('input', function () { |
| 7138 | oai_settings.bind_preset_to_connection = !!$(this).prop('checked'); | 7161 | oai_settings.bind_preset_to_connection = !!$(this).prop('checked'); |
| 7139 | saveSettingsDebounced(); | 7162 | saveSettingsDebounced(); |
| @@ -108,6 +108,214 @@ const OPENROUTER_PROVIDERS = [ | |||
| 108 | 'Z.AI', | 108 | 'Z.AI', |
| 109 | ]; | 109 | ]; |
| 110 | 110 | ||
| 111 | /** | ||
| 112 | * List of NanoGPT providers. | ||
| 113 | * Providers endpoint: https://nano-gpt.com/api/models/providers | ||
| 114 | * @type {{id: string, label: string}[]} | ||
| 115 | */ | ||
| 116 | const NANOGPT_PROVIDERS = [ | ||
| 117 | { | ||
| 118 | 'id': 'akash', | ||
| 119 | 'label': 'Akash', | ||
| 120 | }, | ||
| 121 | { | ||
| 122 | 'id': 'alibaba', | ||
| 123 | 'label': 'Alibaba', | ||
| 124 | }, | ||
| 125 | { | ||
| 126 | 'id': 'ambient', | ||
| 127 | 'label': 'Ambient', | ||
| 128 | }, | ||
| 129 | { | ||
| 130 | 'id': 'arliai', | ||
| 131 | 'label': 'ArliAI', | ||
| 132 | }, | ||
| 133 | { | ||
| 134 | 'id': 'atlascloud', | ||
| 135 | 'label': 'AtlasCloud', | ||
| 136 | }, | ||
| 137 | { | ||
| 138 | 'id': 'azure', | ||
| 139 | 'label': 'Azure', | ||
| 140 | }, | ||
| 141 | { | ||
| 142 | 'id': 'awsbedrock', | ||
| 143 | 'label': 'Amazon Bedrock', | ||
| 144 | }, | ||
| 145 | { | ||
| 146 | 'id': 'baidu', | ||
| 147 | 'label': 'Baidu', | ||
| 148 | }, | ||
| 149 | { | ||
| 150 | 'id': 'baseten', | ||
| 151 | 'label': 'BaseTen', | ||
| 152 | }, | ||
| 153 | { | ||
| 154 | 'id': 'cerebras', | ||
| 155 | 'label': 'Cerebras', | ||
| 156 | }, | ||
| 157 | { | ||
| 158 | 'id': 'chutes', | ||
| 159 | 'label': 'Chutes', | ||
| 160 | }, | ||
| 161 | { | ||
| 162 | 'id': 'clarifai', | ||
| 163 | 'label': 'Clarifai', | ||
| 164 | }, | ||
| 165 | { | ||
| 166 | 'id': 'cloudflare', | ||
| 167 | 'label': 'Cloudflare', | ||
| 168 | }, | ||
| 169 | { | ||
| 170 | 'id': 'crusoe', | ||
| 171 | 'label': 'Crusoe', | ||
| 172 | }, | ||
| 173 | { | ||
| 174 | 'id': 'dekallm', | ||
| 175 | 'label': 'DekaLLM', | ||
| 176 | }, | ||
| 177 | { | ||
| 178 | 'id': 'deepinfra', | ||
| 179 | 'label': 'DeepInfra', | ||
| 180 | }, | ||
| 181 | { | ||
| 182 | 'id': 'deepseek', | ||
| 183 | 'label': 'DeepSeek', | ||
| 184 | }, | ||
| 185 | { | ||
| 186 | 'id': 'fireworks', | ||
| 187 | 'label': 'Fireworks', | ||
| 188 | }, | ||
| 189 | { | ||
| 190 | 'id': 'friendli', | ||
| 191 | 'label': 'Friendli', | ||
| 192 | }, | ||
| 193 | { | ||
| 194 | 'id': 'gmicloud', | ||
| 195 | 'label': 'GMICloud', | ||
| 196 | }, | ||
| 197 | { | ||
| 198 | 'id': 'lilac', | ||
| 199 | 'label': 'Lilac', | ||
| 200 | }, | ||
| 201 | { | ||
| 202 | 'id': 'google', | ||
| 203 | 'label': 'Google', | ||
| 204 | }, | ||
| 205 | { | ||
| 206 | 'id': 'groq', | ||
| 207 | 'label': 'Groq', | ||
| 208 | }, | ||
| 209 | { | ||
| 210 | 'id': 'hyperbolic', | ||
| 211 | 'label': 'Hyperbolic', | ||
| 212 | }, | ||
| 213 | { | ||
| 214 | 'id': 'ionet', | ||
| 215 | 'label': 'Io Net', | ||
| 216 | }, | ||
| 217 | { | ||
| 218 | 'id': 'inceptron', | ||
| 219 | 'label': 'Inceptron', | ||
| 220 | }, | ||
| 221 | { | ||
| 222 | 'id': 'mancer', | ||
| 223 | 'label': 'Mancer', | ||
| 224 | }, | ||
| 225 | { | ||
| 226 | 'id': 'mara', | ||
| 227 | 'label': 'Mara', | ||
| 228 | }, | ||
| 229 | { | ||
| 230 | 'id': 'meganova', | ||
| 231 | 'label': 'MegaNova', | ||
| 232 | }, | ||
| 233 | { | ||
| 234 | 'id': 'minimax', | ||
| 235 | 'label': 'MiniMax', | ||
| 236 | }, | ||
| 237 | { | ||
| 238 | 'id': 'modelrun', | ||
| 239 | 'label': 'ModelRun', | ||
| 240 | }, | ||
| 241 | { | ||
| 242 | 'id': 'moonshot', | ||
| 243 | 'label': 'Moonshot', | ||
| 244 | }, | ||
| 245 | { | ||
| 246 | 'id': 'morph', | ||
| 247 | 'label': 'Morph', | ||
| 248 | }, | ||
| 249 | { | ||
| 250 | 'id': 'ncompass', | ||
| 251 | 'label': 'NCompass', | ||
| 252 | }, | ||
| 253 | { | ||
| 254 | 'id': 'nebius', | ||
| 255 | 'label': 'Nebius', | ||
| 256 | }, | ||
| 257 | { | ||
| 258 | 'id': 'neuralwatt', | ||
| 259 | 'label': 'Neuralwatt', | ||
| 260 | }, | ||
| 261 | { | ||
| 262 | 'id': 'nextbit', | ||
| 263 | 'label': 'NextBit', | ||
| 264 | }, | ||
| 265 | { | ||
| 266 | 'id': 'novita', | ||
| 267 | 'label': 'Novita', | ||
| 268 | }, | ||
| 269 | { | ||
| 270 | 'id': 'parasail', | ||
| 271 | 'label': 'Parasail', | ||
| 272 | }, | ||
| 273 | { | ||
| 274 | 'id': 'phala', | ||
| 275 | 'label': 'Phala', | ||
| 276 | }, | ||
| 277 | { | ||
| 278 | 'id': 'redpill', | ||
| 279 | 'label': 'Redpill', | ||
| 280 | }, | ||
| 281 | { | ||
| 282 | 'id': 'sambanova', | ||
| 283 | 'label': 'SambaNova', | ||
| 284 | }, | ||
| 285 | { | ||
| 286 | 'id': 'sambanova-high-throughput', | ||
| 287 | 'label': 'SambaNova (High Throughput)', | ||
| 288 | }, | ||
| 289 | { | ||
| 290 | 'id': 'siliconflow', | ||
| 291 | 'label': 'SiliconFlow', | ||
| 292 | }, | ||
| 293 | { | ||
| 294 | 'id': 'streamlake', | ||
| 295 | 'label': 'StreamLake', | ||
| 296 | }, | ||
| 297 | { | ||
| 298 | 'id': 'tinfoil', | ||
| 299 | 'label': 'Tinfoil', | ||
| 300 | }, | ||
| 301 | { | ||
| 302 | 'id': 'together', | ||
| 303 | 'label': 'Together', | ||
| 304 | }, | ||
| 305 | { | ||
| 306 | 'id': 'venice', | ||
| 307 | 'label': 'Venice', | ||
| 308 | }, | ||
| 309 | { | ||
| 310 | 'id': 'wandb', | ||
| 311 | 'label': 'Weights & Biases', | ||
| 312 | }, | ||
| 313 | { | ||
| 314 | 'id': 'zai', | ||
| 315 | 'label': 'Z.AI', | ||
| 316 | }, | ||
| 317 | ]; | ||
| 318 | |||
| 111 | const OPENROUTER_PROVIDER_WARNING_SELECTORS = { | 319 | const OPENROUTER_PROVIDER_WARNING_SELECTORS = { |
| 112 | '#openrouter_providers_text': { | 320 | '#openrouter_providers_text': { |
| 113 | fallbackSelector: '#openrouter_allow_fallbacks_textgenerationwebui', | 321 | fallbackSelector: '#openrouter_allow_fallbacks_textgenerationwebui', |
| @@ -187,6 +395,72 @@ export async function syncOpenRouterProvidersForModel(modelId, providersSelector | |||
| 187 | } | 395 | } |
| 188 | } | 396 | } |
| 189 | 397 | ||
| 398 | export async function syncNanoGptProvidersForModel(modelId, providersSelector) { | ||
| 399 | const $providers = $(providersSelector); | ||
| 400 | |||
| 401 | const refreshWarningState = () => { | ||
| 402 | updateNanoGptProvidersWarning(providersSelector); | ||
| 403 | }; | ||
| 404 | |||
| 405 | if (!modelId) { | ||
| 406 | $providers.find('option').prop('disabled', false); | ||
| 407 | $providers.trigger('change.select2'); | ||
| 408 | refreshWarningState(); | ||
| 409 | return; | ||
| 410 | } | ||
| 411 | |||
| 412 | try { | ||
| 413 | const response = await fetch('/api/nanogpt/models/providers', { | ||
| 414 | method: 'POST', | ||
| 415 | headers: getRequestHeaders(), | ||
| 416 | body: JSON.stringify({ model: modelId }), | ||
| 417 | }); | ||
| 418 | |||
| 419 | if (!response.ok) { | ||
| 420 | refreshWarningState(); | ||
| 421 | return; | ||
| 422 | } | ||
| 423 | |||
| 424 | const data = await response.json(); | ||
| 425 | const providerIds = Array.isArray(data?.providers) ? data.providers : []; | ||
| 426 | |||
| 427 | if (!data?.supportsProviderSelection || providerIds.length === 0) { | ||
| 428 | $providers.find('option').each(function () { | ||
| 429 | $(this).prop('disabled', Boolean($(this).val())); | ||
| 430 | }); | ||
| 431 | $providers.trigger('change').trigger('change.select2'); | ||
| 432 | refreshWarningState(); | ||
| 433 | return; | ||
| 434 | } | ||
| 435 | |||
| 436 | $providers.find('option').each(function () { | ||
| 437 | const value = $(this).val(); | ||
| 438 | const isAvailable = !value || providerIds.includes(value); | ||
| 439 | $(this).prop('disabled', !isAvailable); | ||
| 440 | }); | ||
| 441 | |||
| 442 | $providers.trigger('change.select2'); | ||
| 443 | refreshWarningState(); | ||
| 444 | } catch (error) { | ||
| 445 | console.error('Failed to fetch NanoGPT providers for model', error); | ||
| 446 | refreshWarningState(); | ||
| 447 | } | ||
| 448 | } | ||
| 449 | |||
| 450 | export function updateNanoGptProvidersWarning(providersSelector) { | ||
| 451 | const $providers = $(providersSelector); | ||
| 452 | |||
| 453 | if ($providers.length === 0) { | ||
| 454 | return; | ||
| 455 | } | ||
| 456 | |||
| 457 | const selectedCount = $providers.find('option:selected').length; | ||
| 458 | const applicableSelectedCount = $providers.find('option:selected:not(:disabled)').length; | ||
| 459 | const showWarning = selectedCount > 0 && applicableSelectedCount === 0; | ||
| 460 | |||
| 461 | $('#nanogpt_provider_warning').toggleClass('displayNone', !showWarning); | ||
| 462 | } | ||
| 463 | |||
| 190 | export async function loadOllamaModels(data) { | 464 | export async function loadOllamaModels(data) { |
| 191 | if (!Array.isArray(data)) { | 465 | if (!Array.isArray(data)) { |
| 192 | console.error('Invalid Ollama models data', data); | 466 | console.error('Invalid Ollama models data', data); |
| @@ -1084,6 +1358,14 @@ export function initTextGenModels() { | |||
| 1084 | })); | 1358 | })); |
| 1085 | } | 1359 | } |
| 1086 | 1360 | ||
| 1361 | const nanoGptProvidersSelect = $('#nanogpt_provider'); | ||
| 1362 | for (const provider of NANOGPT_PROVIDERS) { | ||
| 1363 | nanoGptProvidersSelect.append($('<option>', { | ||
| 1364 | value: provider.id, | ||
| 1365 | text: provider.label, | ||
| 1366 | })); | ||
| 1367 | } | ||
| 1368 | |||
| 1087 | if (!isMobile()) { | 1369 | if (!isMobile()) { |
| 1088 | $('#mancer_model').select2({ | 1370 | $('#mancer_model').select2({ |
| 1089 | placeholder: t`Select a model`, | 1371 | placeholder: t`Select a model`, |
| @@ -1178,5 +1460,13 @@ export function initTextGenModels() { | |||
| 1178 | $(this).append($element); | 1460 | $(this).append($element); |
| 1179 | $(this).trigger('change'); | 1461 | $(this).trigger('change'); |
| 1180 | }); | 1462 | }); |
| 1463 | nanoGptProvidersSelect.select2({ | ||
| 1464 | sorter: data => data.sort((a, b) => a.text.localeCompare(b.text)), | ||
| 1465 | placeholder: t`Select providers. No selection = all providers.`, | ||
| 1466 | searchInputPlaceholder: t`Search providers...`, | ||
| 1467 | searchInputCssClass: 'text_pole', | ||
| 1468 | width: '100%', | ||
| 1469 | allowClear: true, | ||
| 1470 | }); | ||
| 1181 | } | 1471 | } |
| 1182 | } | 1472 | } |
| @@ -2382,6 +2382,13 @@ router.post('/generate', async function (request, response) { | |||
| 2382 | apiKey = readSecret(request.user.directories, SECRET_KEYS.NANOGPT, request.body.secret_id); | 2382 | apiKey = readSecret(request.user.directories, SECRET_KEYS.NANOGPT, request.body.secret_id); |
| 2383 | headers = {}; | 2383 | headers = {}; |
| 2384 | bodyParams = {}; | 2384 | bodyParams = {}; |
| 2385 | if (request.body.nanogpt_provider) { | ||
| 2386 | headers['X-Provider'] = request.body.nanogpt_provider; | ||
| 2387 | } | ||
| 2388 | if (request.body.nanogpt_payg_override) { | ||
| 2389 | headers['X-Billing-Mode'] = 'paygo'; | ||
| 2390 | bodyParams['billing_mode'] = 'paygo'; | ||
| 2391 | } | ||
| 2385 | if (request.body.enable_web_search && !/:online$/.test(request.body.model)) { | 2392 | if (request.body.enable_web_search && !/:online$/.test(request.body.model)) { |
| 2386 | request.body.model = `${request.body.model}:online`; | 2393 | request.body.model = `${request.body.model}:online`; |
| 2387 | } | 2394 | } |
| @@ -100,3 +100,39 @@ router.post('/credits', async (req, res) => { | |||
| 100 | return res.sendStatus(500); | 100 | return res.sendStatus(500); |
| 101 | } | 101 | } |
| 102 | }); | 102 | }); |
| 103 | |||
| 104 | router.post('/models/providers', async (req, res) => { | ||
| 105 | try { | ||
| 106 | const { model } = req.body; | ||
| 107 | |||
| 108 | if (!model) { | ||
| 109 | return res.status(400).json({ supportsProviderSelection: false, providers: [] }); | ||
| 110 | } | ||
| 111 | |||
| 112 | const encodedModel = encodeURIComponent(model); | ||
| 113 | const response = await fetch(`${API_NANOGPT}/models/${encodedModel}/providers`, { | ||
| 114 | method: 'GET', | ||
| 115 | headers: { | ||
| 116 | 'Accept': 'application/json', | ||
| 117 | }, | ||
| 118 | }); | ||
| 119 | |||
| 120 | if (!response.ok) { | ||
| 121 | return res.json({ supportsProviderSelection: false, providers: [] }); | ||
| 122 | } | ||
| 123 | |||
| 124 | /** @type {any} */ | ||
| 125 | const data = await response.json(); | ||
| 126 | const providers = Array.isArray(data?.providers) | ||
| 127 | ? data.providers.filter(p => p?.available !== false).map(p => p.provider).filter(Boolean) | ||
| 128 | : []; | ||
| 129 | |||
| 130 | return res.json({ | ||
| 131 | supportsProviderSelection: Boolean(data?.supportsProviderSelection), | ||
| 132 | providers, | ||
| 133 | }); | ||
| 134 | } catch (error) { | ||
| 135 | console.error(error); | ||
| 136 | return res.sendStatus(500); | ||
| 137 | } | ||
| 138 | }); | ||