Open router provider filter (#5208) * Added filter for OpenRouter models provider selection Now if a model is selected, only available providers for that model will show. Wanted to do the same for the quants, but I think the API is not returning the quants available for each model at the moment. Used existing API that for some reason was not consumed. * Added filter for OpenRouter providers Now if a model is selected, only the providers available show. Wanted to do the same with the quants but it seems the OpenRouter API is not giving the available quants list at the moment for each model. * gua * Now it also works on chat completion and only disables options * detail * Warning added * eslint * Move inline styles to CSS --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>

8f5c8f0a8edce57ae3fa9458e379784b07311e06

GERNOMA <54479238+GERNOMA@users.noreply.github.com>

Signed
5 files changed, +105 -3Ignore whitespace
public/css/toggle-dependent.css+6 -0
@@ -568,3 +568,9 @@ label[for="bind_preset_to_connection"]:has(input:checked) {
568568#adaptive_p_block:has([data-tg-samplers="adaptive_target"][style*="display: none"]):has([data-tg-samplers="adaptive_decay"][style*="display: none"]) {
569569 display: none;
570570}
571+
572+#openrouter_provider_warning_text,
573+#openrouter_provider_warning_chat {
574+ color: var(--warning);
575+ margin-left: 5px;
576+}
public/index.html+8 -2
@@ -2460,7 +2460,10 @@
24602460 </select>
24612461 </div>
24622462 <div>
2463- <h4 data-i18n="Model Providers">Model Providers</h4>
2463+ <h4>
2464+ <span data-i18n="Model Providers">Model Providers</span>
2465+ <i id="openrouter_provider_warning_text" class="fa-solid fa-circle-exclamation displayNone" title="Deselect inapplicable provider(s) or select an applicable provider to avoid a 404 error."></i>
2466+ </h4>
24642467 <select id="openrouter_providers_text" class="openrouter_providers" multiple>
24652468 </select>
24662469 <label class="checkbox_label" data-i18n="[title]Automatically chooses an alternative provider if chosen providers can't serve your request." for="openrouter_allow_fallbacks_textgenerationwebui" title="Automatically chooses an alternative provider if chosen providers can't serve your request.">
@@ -3214,7 +3217,10 @@
32143217 </div>
32153218 </div>
32163219 <div>
3217- <h4 data-i18n="Model Providers">Model Providers</h4>
3220+ <h4>
3221+ <span data-i18n="Model Providers">Model Providers</span>
3222+ <i id="openrouter_provider_warning_chat" class="fa-solid fa-circle-exclamation displayNone" title="Deselect inapplicable provider(s) or select an applicable provider to avoid a 404 error."></i>
3223+ </h4>
32183224 <select id="openrouter_providers_chat" class="openrouter_providers" multiple>
32193225 </select>
32203226 <label class="checkbox_label marginTopBot5" for="openrouter_allow_fallbacks" data-i18n="[title]Automatically chooses an alternative provider if chosen providers can't serve your request." title="Automatically chooses an alternative provider if chosen providers can't serve your request.">
public/scripts/openai.js+4 -0
@@ -79,6 +79,7 @@ import { t } from './i18n.js';
7979import { ToolManager } from './tool-calling.js';
8080import { accountStorage } from './util/AccountStorage.js';
8181import { COMETAPI_IGNORE_PATTERNS, IGNORE_SYMBOL, MEDIA_DISPLAY, MEDIA_TYPE } from './constants.js';
82+import { syncOpenRouterProvidersForModel, updateOpenRouterProvidersWarning } from './textgen-models.js';
8283
8384export {
8485 openai_messages_count,
@@ -5135,6 +5136,7 @@ async function onModelChange() {
51355136
51365137 console.log('OpenRouter model changed to', value);
51375138 oai_settings.openrouter_model = value;
5139+ syncOpenRouterProvidersForModel(value, '#openrouter_providers_chat');
51385140 }
51395141
51405142 if ($(this).is('#model_ai21_select')) {
@@ -6623,6 +6625,7 @@ export function initOpenAI() {
66236625
66246626 $('#openrouter_allow_fallbacks').on('input', function () {
66256627 oai_settings.openrouter_allow_fallbacks = !!$(this).prop('checked');
6628+ updateOpenRouterProvidersWarning('#openrouter_providers_chat');
66266629 saveSettingsDebounced();
66276630 });
66286631
@@ -6882,6 +6885,7 @@ export function initOpenAI() {
68826885
68836886 oai_settings.openrouter_providers = selectedProviders;
68846887
6888+ updateOpenRouterProvidersWarning('#openrouter_providers_chat');
68856889 saveSettingsDebounced();
68866890 });
68876891
public/scripts/textgen-models.js+81 -0
@@ -101,6 +101,85 @@ const OPENROUTER_PROVIDERS = [
101101 'Z.AI',
102102];
103103
104+const OPENROUTER_PROVIDER_WARNING_SELECTORS = {
105+ '#openrouter_providers_text': {
106+ fallbackSelector: '#openrouter_allow_fallbacks_textgenerationwebui',
107+ warningSelector: '#openrouter_provider_warning_text',
108+ },
109+ '#openrouter_providers_chat': {
110+ fallbackSelector: '#openrouter_allow_fallbacks',
111+ warningSelector: '#openrouter_provider_warning_chat',
112+ },
113+};
114+
115+export function updateOpenRouterProvidersWarning(providersSelector) {
116+ const $providers = $(providersSelector);
117+
118+ const warningSelectors = OPENROUTER_PROVIDER_WARNING_SELECTORS[providersSelector];
119+
120+ if ($providers.length === 0 || !warningSelectors) {
121+ return;
122+ }
123+
124+ const $fallback = $(warningSelectors.fallbackSelector);
125+ const $warning = $(warningSelectors.warningSelector);
126+
127+ const allowFallback = !!$fallback.prop('checked');
128+ const selectedCount = $providers.find('option:selected').length;
129+ const applicableSelectedCount = $providers.find('option:selected:not(:disabled)').length;
130+ const showWarning = !allowFallback && selectedCount > 0 && applicableSelectedCount === 0;
131+
132+ $warning.toggleClass('displayNone', !showWarning);
133+}
134+
135+export async function syncOpenRouterProvidersForModel(modelId, providersSelector) {
136+ const $providers = $(providersSelector);
137+
138+ const refreshWarningState = () => {
139+ updateOpenRouterProvidersWarning(providersSelector);
140+ };
141+
142+ if (!modelId || !modelId.includes('/')) {
143+ $providers.find('option').prop('disabled', false);
144+ $providers.trigger('change.select2');
145+ refreshWarningState();
146+ return;
147+ }
148+
149+ try {
150+ const response = await fetch('/api/openrouter/models/providers', {
151+ method: 'POST',
152+ headers: getRequestHeaders(),
153+ body: JSON.stringify({ model: modelId }),
154+ });
155+
156+ if (!response.ok) {
157+ refreshWarningState();
158+ return;
159+ }
160+
161+ const providerNames = await response.json();
162+
163+ if (!Array.isArray(providerNames) || providerNames.length === 0) {
164+ $providers.find('option').prop('disabled', false);
165+ $providers.trigger('change.select2');
166+ refreshWarningState();
167+ return;
168+ }
169+
170+ $providers.find('option').each(function () {
171+ const isAvailable = providerNames.includes($(this).val());
172+ $(this).prop('disabled', !isAvailable);
173+ });
174+
175+ $providers.trigger('change.select2');
176+ refreshWarningState();
177+ } catch (error) {
178+ console.error('Failed to fetch OpenRouter providers for model', error);
179+ refreshWarningState();
180+ }
181+}
182+
104183export async function loadOllamaModels(data) {
105184 if (!Array.isArray(data)) {
106185 console.error('Invalid Ollama models data', data);
@@ -315,6 +394,7 @@ export async function loadOpenRouterModels(data) {
315394
316395 // Calculate the cost of the selected model + update on settings change
317396 calculateOpenRouterCost();
397+ syncOpenRouterProvidersForModel(textgen_settings.openrouter_model, '#openrouter_providers_text');
318398}
319399
320400export async function loadVllmModels(data) {
@@ -675,6 +755,7 @@ function onOpenRouterModelSelect() {
675755 textgen_settings.openrouter_model = modelId;
676756 $('#api_button_textgenerationwebui').trigger('click');
677757 const model = openRouterModels.find(x => x.id === modelId);
758+ syncOpenRouterProvidersForModel(modelId, '#openrouter_providers_text');
678759 setGenerationParamsFromPreset({ max_length: model.context_length });
679760}
680761
public/scripts/textgen-settings.js+6 -1
@@ -23,7 +23,7 @@ import { power_user, registerDebugFunction } from './power-user.js';
2323import { getActiveManualApiSamplers, loadApiSelectedSamplers, isSamplerManualPriorityEnabled } from './samplerSelect.js';
2424import { SECRET_KEYS, writeSecret } from './secrets.js';
2525import { getEventSourceStream } from './sse-stream.js';
2626import { getCurrentDreamGenModelTokenizer, getCurrentOpenRouterModelTokenizer, loadAphroditeModels, loadDreamGenModels, loadFeatherlessModels, loadGenericModels, loadInfermaticAIModels, loadLlamaCppModels, loadMancerModels, loadOllamaModels, loadOpenRouterModels, loadTabbyModels, loadTogetherAIModels, loadVllmModels, updateOpenRouterProvidersWarning } from './textgen-models.js';
2727import { ENCODE_TOKENIZERS, TEXTGEN_TOKENIZERS, TOKENIZER_SUPPORTED_KEY, getTextTokens, getTokenizerBestMatch, tokenizers } from './tokenizers.js';
2828import { AbortReason } from './util/AbortReason.js';
2929import { getSortableDelay, onlyUnique, arraysEqual, isObject } from './utils.js';
@@ -1069,9 +1069,14 @@ export function initTextGenSettings() {
10691069
10701070 textgenerationwebui_settings.openrouter_providers = selectedProviders;
10711071
1072+ updateOpenRouterProvidersWarning('#openrouter_providers_text');
10721073 saveSettingsDebounced();
10731074 });
10741075
1076+ $('#openrouter_allow_fallbacks_textgenerationwebui').on('input', function () {
1077+ updateOpenRouterProvidersWarning('#openrouter_providers_text');
1078+ });
1079+
10751080 $('#openrouter_quantizations_text').on('change', function () {
10761081 const selectedQuantizations = $(this).val();
10771082