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:
@@ -3648,6 +3648,19 @@
|
||||
<option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<h4>
|
||||
<span data-i18n="Model Providers">Model Providers</span>
|
||||
<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>
|
||||
</h4>
|
||||
<select id="nanogpt_provider">
|
||||
<option value="" data-i18n="Auto">Auto</option>
|
||||
</select>
|
||||
<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.">
|
||||
<input id="nanogpt_payg_override" type="checkbox" />
|
||||
<span data-i18n="Use pay-as-you-go billing">Use pay-as-you-go billing</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div id="workers_ai_form" data-source="workers_ai">
|
||||
<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';
|
||||
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();
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2382,6 +2382,13 @@ router.post('/generate', async function (request, response) {
|
||||
apiKey = readSecret(request.user.directories, SECRET_KEYS.NANOGPT, request.body.secret_id);
|
||||
headers = {};
|
||||
bodyParams = {};
|
||||
if (request.body.nanogpt_provider) {
|
||||
headers['X-Provider'] = request.body.nanogpt_provider;
|
||||
}
|
||||
if (request.body.nanogpt_payg_override) {
|
||||
headers['X-Billing-Mode'] = 'paygo';
|
||||
bodyParams['billing_mode'] = 'paygo';
|
||||
}
|
||||
if (request.body.enable_web_search && !/:online$/.test(request.body.model)) {
|
||||
request.body.model = `${request.body.model}:online`;
|
||||
}
|
||||
|
||||
@@ -100,3 +100,39 @@ router.post('/credits', async (req, res) => {
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/models/providers', async (req, res) => {
|
||||
try {
|
||||
const { model } = req.body;
|
||||
|
||||
if (!model) {
|
||||
return res.status(400).json({ supportsProviderSelection: false, providers: [] });
|
||||
}
|
||||
|
||||
const encodedModel = encodeURIComponent(model);
|
||||
const response = await fetch(`${API_NANOGPT}/models/${encodedModel}/providers`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
return res.json({ supportsProviderSelection: false, providers: [] });
|
||||
}
|
||||
|
||||
/** @type {any} */
|
||||
const data = await response.json();
|
||||
const providers = Array.isArray(data?.providers)
|
||||
? data.providers.filter(p => p?.available !== false).map(p => p.provider).filter(Boolean)
|
||||
: [];
|
||||
|
||||
return res.json({
|
||||
supportsProviderSelection: Boolean(data?.supportsProviderSelection),
|
||||
providers,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user