Chat Completion: common model sorting and grouping settings, improved NanoGPT model list (#5536) * Improve NanoGPT Model List * Update token multiplier condition to !== 1 to support showing discounts * Add Model Sorting * rework sorting to be more compact * feat: combine CC model sorting/grouping settings Co-authored-by: Copilot <copilot@github.com> * fix: adjust migration logic Co-authored-by: Copilot <copilot@github.com> * feat: implement grouping for Chutes * fix: apply review suggestions * fix: call reconnect instead of direct status check --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com> Co-authored-by: Copilot <copilot@github.com>

d327412e29847d5c38d6a66e5d48b03c22809b90

DeathStalker471 <thomasnealson85@gmail.com>

Signed
2 files changed, +284 -330Showing whitespace changes
public/index.html+31 -81
@@ -3224,36 +3224,6 @@
3224 <input id="openrouter_use_fallback" type="checkbox" />3224 <input id="openrouter_use_fallback" type="checkbox" />
3225 <span data-i18n="Allow fallback models">Allow fallback models</span>3225 <span data-i18n="Allow fallback models">Allow fallback models</span>
3226 </label>3226 </label>
3227 <div class="marginTopBot5">
3228 <div class="inline-drawer wide100p">
3229 <div class="inline-drawer-toggle inline-drawer-header">
3230 <b data-i18n="OpenRouter Model Sorting">OpenRouter Model Sorting</b>
3231 <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
3232 </div>
3233 <div class="inline-drawer-content m-b-1">
3234 <div class="marginTopBot5">
3235 <label for="openrouter_sort_models" class="checkbox_label">
3236 <select id="openrouter_sort_models">
3237 <option data-i18n="Alphabetically" value="alphabetically">Alphabetically</option>
3238 <option data-i18n="Price" value="pricing.prompt">Price (cheapest)</option>
3239 <option data-i18n="Context Size" value="context_length">Context Size</option>
3240 </select>
3241 </label>
3242 </div>
3243 <div class="marginTopBot5">
3244 <label for="openrouter_group_models" class="checkbox_label">
3245 <input id="openrouter_group_models" type="checkbox" />
3246 <span data-i18n="Group by vendors">Group by vendors</span>
3247 </label>
3248 <div class="toggle-description justifyLeft wide100p">
3249 <span data-i18n="Group by vendors Description">
3250 Put OpenAI models in one group, Anthropic models in other group, etc. Can be combined with sorting.
3251 </span>
3252 </div>
3253 </div>
3254 </div>
3255 </div>
3256 </div>
3257 <div>3227 <div>
3258 <h4>3228 <h4>
3259 <span data-i18n="Model Providers">Model Providers</span>3229 <span data-i18n="Model Providers">Model Providers</span>
@@ -3641,37 +3611,6 @@
3641 <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option>3611 <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option>
3642 </select>3612 </select>
3643 </div>3613 </div>
3644 <div class="marginTopBot5">
3645 <div class="inline-drawer wide100p">
3646 <div class="inline-drawer-toggle inline-drawer-header">
3647 <b data-i18n="Electron Hub Model Sorting">Electron Hub Model Sorting</b>
3648 <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
3649 </div>
3650 <div class="inline-drawer-content m-b-1">
3651 <div class="marginTopBot5">
3652 <label for="electronhub_sort_models" class="checkbox_label">
3653 <select id="electronhub_sort_models">
3654 <option data-i18n="Alphabetically" value="alphabetically">Alphabetically</option>
3655 <option data-i18n="Input Price" value="pricing.input">Input Price (cheapest)</option>
3656 <option data-i18n="Output Price" value="pricing.output">Output Price (cheapest)</option>
3657 <option data-i18n="Context Size" value="context_length">Context Size</option>
3658 </select>
3659 </label>
3660 </div>
3661 <div class="marginTopBot5">
3662 <label for="electronhub_group_models" class="checkbox_label">
3663 <input id="electronhub_group_models" type="checkbox" />
3664 <span data-i18n="Group by vendors">Group by vendors</span>
3665 </label>
3666 <div class="toggle-description justifyLeft wide100p">
3667 <span data-i18n="Group by vendors Description">
3668 Put OpenAI models in one group, Anthropic models in other group, etc. Can be combined with sorting.
3669 </span>
3670 </div>
3671 </div>
3672 </div>
3673 </div>
3674 </div>
3675 </div>3614 </div>
3676 <div id="chutes_form" data-source="chutes">3615 <div id="chutes_form" data-source="chutes">
3677 <h4 data-i18n="Chutes API Key">Chutes API Key</h4>3616 <h4 data-i18n="Chutes API Key">Chutes API Key</h4>
@@ -3691,26 +3630,6 @@
3691 <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option>3630 <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option>
3692 </select>3631 </select>
3693 </div>3632 </div>
3694 <div class="marginTopBot5">
3695 <div class="inline-drawer wide100p">
3696 <div class="inline-drawer-toggle inline-drawer-header">
3697 <b data-i18n="Chutes Model Sorting">Chutes Model Sorting</b>
3698 <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
3699 </div>
3700 <div class="inline-drawer-content m-b-1">
3701 <div class="marginTopBot5">
3702 <label for="chutes_sort_models" class="checkbox_label">
3703 <select id="chutes_sort_models">
3704 <option data-i18n="Alphabetically" value="alphabetically">Alphabetically</option>
3705 <option data-i18n="Input Price" value="pricing.input">Input Price (cheapest)</option>
3706 <option data-i18n="Output Price" value="pricing.output">Output Price (cheapest)</option>
3707 <option data-i18n="Context Size" value="context_length">Context Size</option>
3708 </select>
3709 </label>
3710 </div>
3711 </div>
3712 </div>
3713 </div>
3714 </div>3633 </div>
3715 <div id="nanogpt_form" data-source="nanogpt">3634 <div id="nanogpt_form" data-source="nanogpt">
3716 <h4 data-i18n="NanoGPT API Key">NanoGPT API Key</h4>3635 <h4 data-i18n="NanoGPT API Key">NanoGPT API Key</h4>
@@ -4062,6 +3981,37 @@
4062 <small data-i18n="The underlying model of your deployment. This is detected automatically when you connect.">The underlying model of your deployment. This is detected automatically when you connect.</small>3981 <small data-i18n="The underlying model of your deployment. This is detected automatically when you connect.">The underlying model of your deployment. This is detected automatically when you connect.</small>
4063 </div>3982 </div>
4064 </div>3983 </div>
3984 <div id="model_sorting_form" data-source="openrouter,chutes,electronhub,nanogpt,aimlapi">
3985 <div class="inline-drawer wide100p">
3986 <div class="inline-drawer-toggle inline-drawer-header">
3987 <b data-i18n="Model Sorting">Model Sorting</b>
3988 <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
3989 </div>
3990 <div class="inline-drawer-content m-b-1">
3991 <div class="marginTopBot5">
3992 <label for="cc_sort_models" class="checkbox_label">
3993 <select id="cc_sort_models">
3994 <option data-i18n="Alphabetically" value="alphabetically">Alphabetically</option>
3995 <option data-i18n="Prompt Price (cheapest)" value="pricing.prompt">Prompt Price (cheapest)</option>
3996 <option data-i18n="Completion Price (cheapest)" value="pricing.completion">Completion Price (cheapest)</option>
3997 <option data-i18n="Context Size" value="context_length">Context Size</option>
3998 </select>
3999 </label>
4000 </div>
4001 <div class="marginTopBot5">
4002 <label for="cc_group_models" class="checkbox_label">
4003 <input id="cc_group_models" type="checkbox" />
4004 <span data-i18n="Group by vendors">Group by vendors</span>
4005 </label>
4006 <div class="toggle-description justifyLeft wide100p">
4007 <span data-i18n="Group by vendors Description">
4008 Put OpenAI models in one group, Anthropic models in other group, etc. Can be combined with sorting.
4009 </span>
4010 </div>
4011 </div>
4012 </div>
4013 </div>
4014 </div>
4065 <div id="prompt_post_processing_form">4015 <div id="prompt_post_processing_form">
4066 <h4>4016 <h4>
4067 <span data-i18n="Prompt Post-Processing">4017 <span data-i18n="Prompt Post-Processing">
public/scripts/openai.js+253 -249
@@ -306,12 +306,12 @@ export const settingsToUpdate = {
306 min_p: ['#min_p_openai', 'min_p_openai', false, false],306 min_p: ['#min_p_openai', 'min_p_openai', false, false],
307 repetition_penalty: ['#repetition_penalty_openai', 'repetition_penalty_openai', false, false],307 repetition_penalty: ['#repetition_penalty_openai', 'repetition_penalty_openai', false, false],
308 max_context_unlocked: ['#oai_max_context_unlocked', 'max_context_unlocked', true, false],308 max_context_unlocked: ['#oai_max_context_unlocked', 'max_context_unlocked', true, false],
309 group_models: ['#cc_group_models', 'group_models', true, true],
310 sort_models: ['#cc_sort_models', 'sort_models', false, true],
309 openai_model: ['#model_openai_select', 'openai_model', false, true],311 openai_model: ['#model_openai_select', 'openai_model', false, true],
310 claude_model: ['#model_claude_select', 'claude_model', false, true],312 claude_model: ['#model_claude_select', 'claude_model', false, true],
311 openrouter_model: ['#model_openrouter_select', 'openrouter_model', false, true],313 openrouter_model: ['#model_openrouter_select', 'openrouter_model', false, true],
312 openrouter_use_fallback: ['#openrouter_use_fallback', 'openrouter_use_fallback', true, true],314 openrouter_use_fallback: ['#openrouter_use_fallback', 'openrouter_use_fallback', true, true],
313 openrouter_group_models: ['#openrouter_group_models', 'openrouter_group_models', false, true],
314 openrouter_sort_models: ['#openrouter_sort_models', 'openrouter_sort_models', false, true],
315 openrouter_providers: ['#openrouter_providers_chat', 'openrouter_providers', false, true],315 openrouter_providers: ['#openrouter_providers_chat', 'openrouter_providers', false, true],
316 openrouter_quantizations: ['#openrouter_quantizations_chat', 'openrouter_quantizations', false, true],316 openrouter_quantizations: ['#openrouter_quantizations_chat', 'openrouter_quantizations', false, true],
317 openrouter_allow_fallbacks: ['#openrouter_allow_fallbacks', 'openrouter_allow_fallbacks', true, true],317 openrouter_allow_fallbacks: ['#openrouter_allow_fallbacks', 'openrouter_allow_fallbacks', true, true],
@@ -323,14 +323,11 @@ export const settingsToUpdate = {
323 perplexity_model: ['#model_perplexity_select', 'perplexity_model', false, true],323 perplexity_model: ['#model_perplexity_select', 'perplexity_model', false, true],
324 groq_model: ['#model_groq_select', 'groq_model', false, true],324 groq_model: ['#model_groq_select', 'groq_model', false, true],
325 chutes_model: ['#model_chutes_select', 'chutes_model', false, true],325 chutes_model: ['#model_chutes_select', 'chutes_model', false, true],
326 chutes_sort_models: ['#chutes_sort_models', 'chutes_sort_models', false, true],
327 siliconflow_model: ['#model_siliconflow_select', 'siliconflow_model', false, true],326 siliconflow_model: ['#model_siliconflow_select', 'siliconflow_model', false, true],
328 siliconflow_endpoint: ['#siliconflow_endpoint', 'siliconflow_endpoint', false, true],327 siliconflow_endpoint: ['#siliconflow_endpoint', 'siliconflow_endpoint', false, true],
329 minimax_model: ['#model_minimax_select', 'minimax_model', false, true],328 minimax_model: ['#model_minimax_select', 'minimax_model', false, true],
330 minimax_endpoint: ['#minimax_endpoint', 'minimax_endpoint', false, true],329 minimax_endpoint: ['#minimax_endpoint', 'minimax_endpoint', false, true],
331 electronhub_model: ['#model_electronhub_select', 'electronhub_model', false, true],330 electronhub_model: ['#model_electronhub_select', 'electronhub_model', false, true],
332 electronhub_sort_models: ['#electronhub_sort_models', 'electronhub_sort_models', false, true],
333 electronhub_group_models: ['#electronhub_group_models', 'electronhub_group_models', false, true],
334 nanogpt_model: ['#model_nanogpt_select', 'nanogpt_model', false, true],331 nanogpt_model: ['#model_nanogpt_select', 'nanogpt_model', false, true],
335 deepseek_model: ['#model_deepseek_select', 'deepseek_model', false, true],332 deepseek_model: ['#model_deepseek_select', 'deepseek_model', false, true],
336 aimlapi_model: ['#model_aimlapi_select', 'aimlapi_model', false, true],333 aimlapi_model: ['#model_aimlapi_select', 'aimlapi_model', false, true],
@@ -428,6 +425,8 @@ const default_settings = {
428 group_nudge_prompt: default_group_nudge_prompt,425 group_nudge_prompt: default_group_nudge_prompt,
429 scenario_format: default_scenario_format,426 scenario_format: default_scenario_format,
430 personality_format: default_personality_format,427 personality_format: default_personality_format,
428 sort_models: 'alphabetically',
429 group_models: false,
431 openai_model: 'gpt-4-turbo',430 openai_model: 'gpt-4-turbo',
432 claude_model: 'claude-sonnet-4-5',431 claude_model: 'claude-sonnet-4-5',
433 google_model: 'gemini-2.5-pro',432 google_model: 'gemini-2.5-pro',
@@ -438,14 +437,11 @@ const default_settings = {
438 perplexity_model: 'sonar-pro',437 perplexity_model: 'sonar-pro',
439 groq_model: 'llama-3.3-70b-versatile',438 groq_model: 'llama-3.3-70b-versatile',
440 chutes_model: 'deepseek-ai/DeepSeek-V3-0324',439 chutes_model: 'deepseek-ai/DeepSeek-V3-0324',
441 chutes_sort_models: 'alphabetically',
442 siliconflow_model: 'deepseek-ai/DeepSeek-V3',440 siliconflow_model: 'deepseek-ai/DeepSeek-V3',
443 siliconflow_endpoint: SILICONFLOW_ENDPOINT.GLOBAL,441 siliconflow_endpoint: SILICONFLOW_ENDPOINT.GLOBAL,
444 minimax_model: 'MiniMax-M2.7',442 minimax_model: 'MiniMax-M2.7',
445 minimax_endpoint: MINIMAX_ENDPOINT.GLOBAL,443 minimax_endpoint: MINIMAX_ENDPOINT.GLOBAL,
446 electronhub_model: 'gpt-4o-mini',444 electronhub_model: 'gpt-4o-mini',
447 electronhub_sort_models: 'alphabetically',
448 electronhub_group_models: false,
449 nanogpt_model: 'gpt-4o-mini',445 nanogpt_model: 'gpt-4o-mini',
450 deepseek_model: 'deepseek-v4-flash',446 deepseek_model: 'deepseek-v4-flash',
451 aimlapi_model: 'chatgpt-4o-latest',447 aimlapi_model: 'chatgpt-4o-latest',
@@ -469,8 +465,6 @@ const default_settings = {
469 custom_include_headers: '',465 custom_include_headers: '',
470 openrouter_model: openrouter_website_model,466 openrouter_model: openrouter_website_model,
471 openrouter_use_fallback: false,467 openrouter_use_fallback: false,
472 openrouter_group_models: false,
473 openrouter_sort_models: 'alphabetically',
474 openrouter_providers: [],468 openrouter_providers: [],
475 openrouter_quantizations: [],469 openrouter_quantizations: [],
476 openrouter_allow_fallbacks: true,470 openrouter_allow_fallbacks: true,
@@ -1897,6 +1891,33 @@ function getChutesModelTemplate(option) {
1897 `));1891 `));
1898}1892}
18991893
1894function calculateChutesCost() {
1895 if (oai_settings.chat_completion_source !== chat_completion_sources.CHUTES) {
1896 return;
1897 }
1898
1899 let cost = 'Unknown';
1900 const model = model_list.find(x => x.id === oai_settings.chutes_model);
1901
1902 if (model?.pricing) {
1903 const outputPrice = model.pricing?.output;
1904 const inputPrice = model.pricing?.input;
1905
1906 if (outputPrice !== undefined && inputPrice !== undefined) {
1907 const outputCost = Number(outputPrice / 1000000);
1908 const inputCost = Number(inputPrice / 1000000);
1909 const outputTokens = oai_settings.openai_max_tokens;
1910 const inputTokens = (oai_settings.openai_max_context - outputTokens);
1911 const totalCost = (outputCost * outputTokens) + (inputCost * inputTokens);
1912 if (!isNaN(totalCost)) {
1913 cost = '$' + totalCost.toFixed(4);
1914 }
1915 }
1916 }
1917
1918 $('#chutes_max_prompt_cost').text(cost);
1919}
1920
1900function getNanoGptModelTemplate(option) {1921function getNanoGptModelTemplate(option) {
1901 const model = model_list.find(x => x.id === option?.element?.value);1922 const model = model_list.find(x => x.id === option?.element?.value);
19021923
@@ -1906,10 +1927,9 @@ function getNanoGptModelTemplate(option) {
19061927
1907 const inputPrice = model.pricing?.prompt;1928 const inputPrice = model.pricing?.prompt;
1908 const outputPrice = model.pricing?.completion;1929 const outputPrice = model.pricing?.completion;
1909
1910 let price = 'Unknown';1930 let price = 'Unknown';
1931
1911 if (inputPrice !== undefined && outputPrice !== undefined) {1932 if (inputPrice !== undefined && outputPrice !== undefined) {
1912 // Check if both prices are 0 (free model)
1913 if (inputPrice === 0 && outputPrice === 0) {1933 if (inputPrice === 0 && outputPrice === 0) {
1914 price = 'Free';1934 price = 'Free';
1915 } else {1935 } else {
@@ -1917,40 +1937,61 @@ function getNanoGptModelTemplate(option) {
1917 }1937 }
1918 }1938 }
19191939
1940 const visionIcon = model.capabilities?.vision ? '<i class="fa-solid fa-eye fa-sm" title="This model supports vision"></i>' : '';
1941 const reasoningIcon = model.capabilities?.reasoning ? '<i class="fa-solid fa-brain fa-sm" title="This model supports reasoning"></i>' : '';
1942 const toolCallsIcon = model.capabilities?.tool_calling ? '<i class="fa-solid fa-wrench fa-sm" title="This model supports tool calling"></i>' : '';
1943
1944 let subHtml = '';
1945 const sub = model.subscription;
1946
1947 if (sub) {
1948 if (sub.included) {
1949 let titleText = 'Included in subscription';
1950 let multiplierText = '';
1951
1952 if (sub.inputTokenMultiplier && sub.inputTokenMultiplier !== 1) {
1953 multiplierText = ` (${sub.inputTokenMultiplier}x)`;
1954 titleText += ` - Input Multiplier: ${sub.inputTokenMultiplier}x`;
1955 }
1956 subHtml = ` <small title="${titleText}"><i class="fa-solid fa-crown fa-sm"></i> Sub${multiplierText}</small>`;
1957 } else if (sub.note) {
1958 const safeNote = DOMPurify.sanitize(sub.note);
1959 subHtml = ` <small title="${safeNote}"><i class="fa-solid fa-circle-info fa-sm"></i> Not in Sub</small>`;
1960 }
1961 }
1962
1963 const iconsContainer = document.createElement('span');
1964 iconsContainer.insertAdjacentHTML('beforeend', visionIcon);
1965 iconsContainer.insertAdjacentHTML('beforeend', reasoningIcon);
1966 iconsContainer.insertAdjacentHTML('beforeend', toolCallsIcon);
1967 iconsContainer.insertAdjacentHTML('beforeend', subHtml);
1968
1969 const capabilities = (iconsContainer.children.length) ? ` | ${iconsContainer.innerHTML}` : '';
1970
1920 const contextLength = model.context_length || 'Unknown';1971 const contextLength = model.context_length || 'Unknown';
1972 const modelName = model.name || model.id;
19211973
1922 return $((`1974 return $((`
1923 <div class="flex-container alignItemsBaseline" title="${DOMPurify.sanitize(model.id)}">1975 <div class="flex-container alignItemsBaseline" title="${DOMPurify.sanitize(model.id)}">
1924 <strong>${DOMPurify.sanitize(model.id)}</strong> | ${contextLength} ctx | <small>${price}</small>1976 <strong>${DOMPurify.sanitize(modelName)}</strong> | ${contextLength} ctx | <small>${price}</small>${capabilities}
1925 </div>1977 </div>
1926 `));1978 `));
1927}1979}
19281980
1929function calculateChutesCost() {1981function getAimlapiModelTemplate(option) {
1930 if (oai_settings.chat_completion_source !== chat_completion_sources.CHUTES) {1982 const model = model_list.find(x => x.id === option?.element?.value);
1931 return;
1932 }
1933
1934 let cost = 'Unknown';
1935 const model = model_list.find(x => x.id === oai_settings.chutes_model);
1936
1937 if (model?.pricing) {
1938 const outputPrice = model.pricing?.output;
1939 const inputPrice = model.pricing?.input;
19401983
1941 if (outputPrice !== undefined && inputPrice !== undefined) {1984 if (!option.id || !model) {
1942 const outputCost = Number(outputPrice / 1000000);1985 return option.text;
1943 const inputCost = Number(inputPrice / 1000000);
1944 const outputTokens = oai_settings.openai_max_tokens;
1945 const inputTokens = (oai_settings.openai_max_context - outputTokens);
1946 const totalCost = (outputCost * outputTokens) + (inputCost * inputTokens);
1947 if (!isNaN(totalCost)) {
1948 cost = '$' + totalCost.toFixed(4);
1949 }
1950 }
1951 }1986 }
19521987
1953 $('#chutes_max_prompt_cost').text(cost);1988 const vendor = model.id.split('/')[0];
1989
1990 return $((`
1991 <div class="flex-container flexFlowColumn" title="${DOMPurify.sanitize(model.id)}">
1992 <div><strong>${DOMPurify.sanitize(model.info?.name || model.name || model.id)}</strong> | ${vendor}</div>
1993 </div>
1994 `));
1954}1995}
19551996
1956function saveModelList(data) {1997function saveModelList(data) {
@@ -1958,14 +1999,22 @@ function saveModelList(data) {
1958 model_list.sort((a, b) => a?.id && b?.id && a.id.localeCompare(b.id));1999 model_list.sort((a, b) => a?.id && b?.id && a.id.localeCompare(b.id));
19592000
1960 if (oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER) {2001 if (oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER) {
1961 model_list = openRouterSortBy(model_list, oai_settings.openrouter_sort_models);2002 model_list = sortModelsBy(model_list, oai_settings.sort_models, chat_completion_sources.OPENROUTER);
1962
1963 $('#model_openrouter_select').empty();2003 $('#model_openrouter_select').empty();
2004 $('#model_openrouter_select').append($('<option>', { value: openrouter_website_model, text: t`Use OpenRouter website setting` }));
19642005
1965 if (true === oai_settings.openrouter_group_models) {2006 if (oai_settings.group_models) {
1966 appendOpenRouterOptions(openRouterGroupByVendor(model_list), oai_settings.openrouter_group_models);2007 groupModelsByVendor(model_list, chat_completion_sources.OPENROUTER).forEach((models, vendor) => {
2008 const optgroup = $('<optgroup>').attr('label', vendor);
2009 models.forEach((model) => {
2010 optgroup.append($('<option>', { value: model.id, text: model.name }));
2011 });
2012 $('#model_openrouter_select').append(optgroup);
2013 });
1967 } else {2014 } else {
1968 appendOpenRouterOptions(model_list);2015 model_list.forEach((model) => {
2016 $('#model_openrouter_select').append($('<option>', { value: model.id, text: model.name }));
2017 });
1969 }2018 }
19702019
1971 $('#model_openrouter_select').val(oai_settings.openrouter_model).trigger('change');2020 $('#model_openrouter_select').val(oai_settings.openrouter_model).trigger('change');
@@ -2005,13 +2054,26 @@ function saveModelList(data) {
2005 }2054 }
20062055
2007 if (oai_settings.chat_completion_source == chat_completion_sources.AIMLAPI) {2056 if (oai_settings.chat_completion_source == chat_completion_sources.AIMLAPI) {
2057 model_list = model_list.filter(m => m.type === 'chat-completion');
2058 model_list = sortModelsBy(model_list, oai_settings.sort_models, chat_completion_sources.AIMLAPI);
2008 $('#model_aimlapi_select').empty();2059 $('#model_aimlapi_select').empty();
2009 const chatModels = model_list.filter(m => m.type === 'chat-completion');
20102060
2011 appendAimlapiOptions(aimlapiGroupByVendor(chatModels));2061 if (oai_settings.group_models) {
2062 groupModelsByVendor(model_list, chat_completion_sources.AIMLAPI).forEach((models, vendor) => {
2063 const optgroup = $('<optgroup>').attr('label', vendor);
2064 models.forEach((model) => {
2065 optgroup.append($('<option>', { value: model.id, text: model.info?.name || model.id }));
2066 });
2067 $('#model_aimlapi_select').append(optgroup);
2068 });
2069 } else {
2070 model_list.forEach((model) => {
2071 $('#model_aimlapi_select').append($('<option>', { value: model.id, text: model.info?.name || model.id }));
2072 });
2073 }
20122074
2013 if (!oai_settings.aimlapi_model && chatModels.length > 0) {2075 if (!oai_settings.aimlapi_model && model_list.length > 0) {
2014 oai_settings.aimlapi_model = chatModels[0].id;2076 oai_settings.aimlapi_model = model_list[0].id;
2015 }2077 }
20162078
2017 $('#model_aimlapi_select').val(oai_settings.aimlapi_model).trigger('change');2079 $('#model_aimlapi_select').val(oai_settings.aimlapi_model).trigger('change');
@@ -2034,13 +2096,22 @@ function saveModelList(data) {
20342096
2035 if (oai_settings.chat_completion_source == chat_completion_sources.ELECTRONHUB) {2097 if (oai_settings.chat_completion_source == chat_completion_sources.ELECTRONHUB) {
2036 model_list = model_list.filter(model => model?.endpoints?.includes('/v1/chat/completions'));2098 model_list = model_list.filter(model => model?.endpoints?.includes('/v1/chat/completions'));
20372099 model_list = sortModelsBy(model_list, oai_settings.sort_models, chat_completion_sources.ELECTRONHUB);
2038 model_list = electronHubSortBy(model_list, oai_settings.electronhub_sort_models);
2039
2040 $('#model_electronhub_select').empty();2100 $('#model_electronhub_select').empty();
20412101
2042 const groupedList = oai_settings.electronhub_group_models ? electronHubGroupByVendor(model_list) : model_list;2102 if (oai_settings.group_models) {
2043 appendElectronHubOptions(groupedList, oai_settings.electronhub_group_models);2103 groupModelsByVendor(model_list, chat_completion_sources.ELECTRONHUB).forEach((models, vendor) => {
2104 const optgroup = $('<optgroup>').attr('label', vendor);
2105 models.forEach((model) => {
2106 optgroup.append($('<option>', { value: model.id, text: model.name }));
2107 });
2108 $('#model_electronhub_select').append(optgroup);
2109 });
2110 } else {
2111 model_list.forEach((model) => {
2112 $('#model_electronhub_select').append($('<option>', { value: model.id, text: model.name }));
2113 });
2114 }
20442115
2045 const selectedModel = model_list.find(model => model.id === oai_settings.electronhub_model);2116 const selectedModel = model_list.find(model => model.id === oai_settings.electronhub_model);
2046 if (model_list.length > 0 && (!selectedModel || !oai_settings.electronhub_model)) {2117 if (model_list.length > 0 && (!selectedModel || !oai_settings.electronhub_model)) {
@@ -2052,15 +2123,21 @@ function saveModelList(data) {
20522123
2053 if (oai_settings.chat_completion_source == chat_completion_sources.CHUTES) {2124 if (oai_settings.chat_completion_source == chat_completion_sources.CHUTES) {
2054 model_list = model_list.filter(model => typeof model.id === 'string' && !model.id.toLowerCase().includes('affine'));2125 model_list = model_list.filter(model => typeof model.id === 'string' && !model.id.toLowerCase().includes('affine'));
20552126 model_list = sortModelsBy(model_list, oai_settings.sort_models, chat_completion_sources.CHUTES);
2056 model_list = chutesSortBy(model_list, oai_settings.chutes_sort_models);
2057
2058 $('#model_chutes_select').empty();2127 $('#model_chutes_select').empty();
20592128
2060 for (const model of model_list) {2129 if (oai_settings.group_models) {
2061 const option = $('<option>').val(model.id).text(model.id);2130 groupModelsByVendor(model_list, chat_completion_sources.CHUTES).forEach((models, vendor) => {
2062 option.attr('data-model', JSON.stringify(model));2131 const optgroup = $('<optgroup>').attr('label', vendor);
2063 $('#model_chutes_select').append(option);2132 models.forEach((model) => {
2133 optgroup.append($('<option>', { value: model.id, text: model.id }));
2134 });
2135 $('#model_chutes_select').append(optgroup);
2136 });
2137 } else {
2138 model_list.forEach((model) => {
2139 $('#model_chutes_select').append($('<option>', { value: model.id, text: model.id }));
2140 });
2064 }2141 }
20652142
2066 const selectedModel = model_list.find(model => model.id === oai_settings.chutes_model);2143 const selectedModel = model_list.find(model => model.id === oai_settings.chutes_model);
@@ -2072,14 +2149,22 @@ function saveModelList(data) {
2072 }2149 }
20732150
2074 if (oai_settings.chat_completion_source == chat_completion_sources.NANOGPT) {2151 if (oai_settings.chat_completion_source == chat_completion_sources.NANOGPT) {
2152 model_list = sortModelsBy(model_list, oai_settings.sort_models, chat_completion_sources.NANOGPT);
2075 $('#model_nanogpt_select').empty();2153 $('#model_nanogpt_select').empty();
2154
2155 if (oai_settings.group_models) {
2156 groupModelsByVendor(model_list, chat_completion_sources.NANOGPT).forEach((models, vendor) => {
2157 const optgroup = $('<optgroup>').attr('label', vendor);
2158 models.forEach((model) => {
2159 optgroup.append($('<option>', { value: model.id, text: model.name || model.id }));
2160 });
2161 $('#model_nanogpt_select').append(optgroup);
2162 });
2163 } else {
2076 model_list.forEach((model) => {2164 model_list.forEach((model) => {
2077 $('#model_nanogpt_select').append(2165 $('#model_nanogpt_select').append($('<option>', { value: model.id, text: model.name || model.id }));
2078 $('<option>', {
2079 value: model.id,
2080 text: model.id,
2081 }));
2082 });2166 });
2167 }
20832168
2084 const selectedModel = model_list.find(model => model.id === oai_settings.nanogpt_model);2169 const selectedModel = model_list.find(model => model.id === oai_settings.nanogpt_model);
2085 if (model_list.length > 0 && (!selectedModel || !oai_settings.nanogpt_model)) {2170 if (model_list.length > 0 && (!selectedModel || !oai_settings.nanogpt_model)) {
@@ -2092,11 +2177,7 @@ function saveModelList(data) {
2092 if (oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK) {2177 if (oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK) {
2093 $('#model_deepseek_select').empty();2178 $('#model_deepseek_select').empty();
2094 model_list.forEach((model) => {2179 model_list.forEach((model) => {
2095 $('#model_deepseek_select').append(2180 $('#model_deepseek_select').append($('<option>', { value: model.id, text: model.id }));
2096 $('<option>', {
2097 value: model.id,
2098 text: model.id,
2099 }));
2100 });2181 });
21012182
2102 const selectedModel = model_list.find(model => model.id === oai_settings.deepseek_model);2183 const selectedModel = model_list.find(model => model.id === oai_settings.deepseek_model);
@@ -2110,11 +2191,7 @@ function saveModelList(data) {
2110 if (oai_settings.chat_completion_source === chat_completion_sources.POLLINATIONS) {2191 if (oai_settings.chat_completion_source === chat_completion_sources.POLLINATIONS) {
2111 $('#model_pollinations_select').empty();2192 $('#model_pollinations_select').empty();
2112 model_list.forEach((model) => {2193 model_list.forEach((model) => {
2113 $('#model_pollinations_select').append(2194 $('#model_pollinations_select').append($('<option>', { value: model.id, text: model.id }));
2114 $('<option>', {
2115 value: model.id,
2116 text: model.id,
2117 }));
2118 });2195 });
21192196
2120 const selectedModel = model_list.find(model => model.id === oai_settings.pollinations_model);2197 const selectedModel = model_list.find(model => model.id === oai_settings.pollinations_model);
@@ -2303,181 +2380,134 @@ function saveModelList(data) {
2303 }2380 }
2304}2381}
23052382
2306function appendOpenRouterOptions(model_list, groupModels = false, sort = false) {2383/**
2307 $('#model_openrouter_select').append($('<option>', { value: openrouter_website_model, text: t`Use OpenRouter website setting` }));2384 * Sorts models by the specified property for the given source.
23082385 * @param {object[]} data - Array of model objects
2309 const appendOption = (model, parent = null) => {2386 * @param {string} property - Sort property ('alphabetically', 'context_length', 'pricing.prompt', 'pricing.completion')
2310 (parent || $('#model_openrouter_select')).append(2387 * @param {string} source - Chat Completion source (e.g., 'openrouter', 'chutes', 'electronhub', 'nanogpt')
2311 $('<option>', {2388 * @returns {object[]} Sorted array of model objects
2312 value: model.id,2389 */
2313 text: model.name,2390function sortModelsBy(data, property, source) {
2314 }));2391 switch (source) {
2315 };2392 case chat_completion_sources.OPENROUTER:
2316
2317 if (groupModels) {
2318 model_list.forEach((models, vendor) => {
2319 const optgroup = $(`<optgroup label="${vendor}">`);
2320
2321 models.forEach((model) => {
2322 appendOption(model, optgroup);
2323 });
2324
2325 $('#model_openrouter_select').append(optgroup);
2326 });
2327 } else {
2328 model_list.forEach((model) => {
2329 appendOption(model);
2330 });
2331 }
2332}
2333
2334const openRouterSortBy = (data, property = 'alphabetically') => {
2335 return data.sort((a, b) => {2393 return data.sort((a, b) => {
2336 if (property === 'context_length') {2394 if (property === 'context_length') {
2337 return b.context_length - a.context_length;2395 return (b.context_length || 0) - (a.context_length || 0);
2338 } else if (property === 'pricing.prompt') {2396 } else if (property === 'pricing.input' || property === 'pricing.prompt') {
2339 return parseFloat(a.pricing.prompt) - parseFloat(b.pricing.prompt);2397 return parseFloat(a.pricing?.prompt || 0) - parseFloat(b.pricing?.prompt || 0);
2398 } else if (property === 'pricing.output' || property === 'pricing.completion') {
2399 return parseFloat(a.pricing?.completion || 0) - parseFloat(b.pricing?.completion || 0);
2340 } else {2400 } else {
2341 // Alphabetically2401 return a?.name && b?.name ? a.name.localeCompare(b.name) : 0;
2342 return a?.name && b?.name && a.name.localeCompare(b.name);
2343 }2402 }
2344 });2403 });
2345};2404 case chat_completion_sources.CHUTES:
2346
2347function openRouterGroupByVendor(array) {
2348 return array.reduce((acc, curr) => {
2349 const vendor = curr.id.split('/')[0];
2350
2351 if (!acc.has(vendor)) {
2352 acc.set(vendor, []);
2353 }
2354
2355 acc.get(vendor).push(curr);
2356
2357 return acc;
2358 }, new Map());
2359}
2360
2361function chutesSortBy(data, property = 'alphabetically') {
2362 return data.sort((a, b) => {2405 return data.sort((a, b) => {
2363 if (property === 'context_length') {2406 if (property === 'context_length') {
2364 return b.context_length - a.context_length;2407 return (b.context_length || 0) - (a.context_length || 0);
2365 } else if (property === 'pricing.input') {2408 } else if (property === 'pricing.input' || property === 'pricing.prompt') {
2366 const aPrice = parseFloat(a.pricing?.input || 0);2409 return parseFloat(a.pricing?.input || 0) - parseFloat(b.pricing?.input || 0);
2367 const bPrice = parseFloat(b.pricing?.input || 0);2410 } else if (property === 'pricing.output' || property === 'pricing.completion') {
2368 return aPrice - bPrice;2411 return parseFloat(a.pricing?.output || 0) - parseFloat(b.pricing?.output || 0);
2369 } else if (property === 'pricing.output') {
2370 const aPrice = parseFloat(a.pricing?.output || 0);
2371 const bPrice = parseFloat(b.pricing?.output || 0);
2372 return aPrice - bPrice;
2373 } else {2412 } else {
2374 return a?.id && b?.id && a.id.localeCompare(b.id);2413 return a?.id && b?.id ? a.id.localeCompare(b.id) : 0;
2375 }2414 }
2376 });2415 });
2416 case chat_completion_sources.ELECTRONHUB:
2417 return data.sort((a, b) => {
2418 if (property === 'context_length') {
2419 return (b.tokens || 0) - (a.tokens || 0);
2420 } else if (property === 'pricing.input' || property === 'pricing.prompt') {
2421 return parseFloat(a.pricing?.input || 0) - parseFloat(b.pricing?.input || 0);
2422 } else if (property === 'pricing.output' || property === 'pricing.completion') {
2423 return parseFloat(a.pricing?.output || 0) - parseFloat(b.pricing?.output || 0);
2424 } else {
2425 return a?.name && b?.name ? a.name.localeCompare(b.name) : 0;
2377 }2426 }
2378
2379function appendElectronHubOptions(model_list, groupModels = false) {
2380 const appendOption = (model, parent = null) => {
2381 (parent || $('#model_electronhub_select')).append(
2382 $('<option>', {
2383 value: model.id,
2384 text: model.name,
2385 }));
2386 };
2387
2388 if (groupModels) {
2389 model_list.forEach((models, vendor) => {
2390 const optgroup = $('<optgroup>').attr('label', vendor);
2391
2392 models.forEach((model) => {
2393 appendOption(model, optgroup);
2394 });
2395
2396 $('#model_electronhub_select').append(optgroup);
2397 });2427 });
2428 case chat_completion_sources.NANOGPT:
2429 return data.sort((a, b) => {
2430 if (property === 'context_length') {
2431 return (b.context_length || 0) - (a.context_length || 0);
2432 } else if (property === 'pricing.input' || property === 'pricing.prompt') {
2433 return parseFloat(a.pricing?.prompt || 0) - parseFloat(b.pricing?.prompt || 0);
2434 } else if (property === 'pricing.output' || property === 'pricing.completion') {
2435 return parseFloat(a.pricing?.completion || 0) - parseFloat(b.pricing?.completion || 0);
2398 } else {2436 } else {
2399 model_list.forEach((model) => {2437 return a?.name && b?.name ? a.name.localeCompare(b.name) : 0;
2400 appendOption(model);
2401 });
2402 }2438 }
2403}2439 });
24042440 case chat_completion_sources.AIMLAPI:
2405function electronHubSortBy(data, property = 'alphabetically') {
2406 return data.sort((a, b) => {2441 return data.sort((a, b) => {
2407 if (property === 'context_length') {2442 if (property === 'context_length') {
2408 return b.tokens - a.tokens;2443 return (b.info?.contextLength || 0) - (a.info?.contextLength || 0);
2409 } else if (property === 'pricing.input') {
2410 return parseFloat(a.pricing.input) - parseFloat(b.pricing.input);
2411 } else if (property === 'pricing.output') {
2412 return parseFloat(a.pricing.output) - parseFloat(b.pricing.output);
2413 } else {2444 } else {
2414 return a?.name && b?.name && a.name.localeCompare(b.name);2445 // No pricing information on the API. Sort alphabetically by name.
2446 return a?.info?.name && b?.info?.name ? a.info.name.localeCompare(b.info.name) : 0;
2415 }2447 }
2416 });2448 });
2449 default:
2450 return data;
2451 }
2417}2452}
24182453
2419function electronHubGroupByVendor(array) {2454/**
2455 * Groups models by vendor for the given source. If not supported, returns a map with a single entry containing all models.
2456 * @param {object[]} array Array of model objects
2457 * @param {string} source Chat Completion source (e.g., 'openrouter')
2458 * @returns {Map<string, object[]>} Map of vendor to array of models
2459 */
2460function groupModelsByVendor(array, source) {
2461 switch (source) {
2462 case chat_completion_sources.OPENROUTER:
2420 return array.reduce((acc, curr) => {2463 return array.reduce((acc, curr) => {
2421 const vendor = String(curr?.name || curr?.id || 'Other').split(':')[0].trim() || 'Other';2464 const vendor = curr.id.split('/')[0];
2422
2423 if (!acc.has(vendor)) {2465 if (!acc.has(vendor)) {
2424 acc.set(vendor, []);2466 acc.set(vendor, []);
2425 }2467 }
2426
2427 acc.get(vendor).push(curr);2468 acc.get(vendor).push(curr);
2428
2429 return acc;2469 return acc;
2430 }, new Map());2470 }, new Map());
2471 case chat_completion_sources.ELECTRONHUB:
2472 return array.reduce((acc, curr) => {
2473 const vendor = String(curr?.name || curr?.id || 'Other').split(':')[0].trim() || 'Other';
2474 if (!acc.has(vendor)) {
2475 acc.set(vendor, []);
2431 }2476 }
24322477 acc.get(vendor).push(curr);
2433function aimlapiGroupByVendor(array) {2478 return acc;
2479 }, new Map());
2480 case chat_completion_sources.NANOGPT:
2434 return array.reduce((acc, curr) => {2481 return array.reduce((acc, curr) => {
2435 const vendor = curr.info.developer;2482 const vendorPart = /\//.test(curr.id) ? curr.id.split('/')[0] : curr.id.split('-')[0];
24362483 const vendor = String(vendorPart?.trim()?.toLowerCase() || 'Other');
2437 if (!acc.has(vendor)) {2484 if (!acc.has(vendor)) {
2438 acc.set(vendor, []);2485 acc.set(vendor, []);
2439 }2486 }
2440
2441 acc.get(vendor).push(curr);2487 acc.get(vendor).push(curr);
2442
2443 return acc;2488 return acc;
2444 }, new Map());2489 }, new Map());
2490 case chat_completion_sources.CHUTES:
2491 return array.reduce((acc, curr) => {
2492 const vendor = curr.id.split('/')[0];
2493 if (!acc.has(vendor)) {
2494 acc.set(vendor, []);
2445 }2495 }
24462496 acc.get(vendor).push(curr);
2447function appendAimlapiOptions(model_list) {2497 return acc;
2448 const appendOption = (model, parent = null) => {2498 }, new Map());
2449 (parent || $('#model_aimlapi_select')).append(2499 case chat_completion_sources.AIMLAPI:
2450 $('<option>', {2500 return array.reduce((acc, curr) => {
2451 value: model.id,2501 const vendor = curr.info?.developer || 'Other';
2452 text: model.info?.name || model.name || model.id,2502 if (!acc.has(vendor)) {
2453 }));2503 acc.set(vendor, []);
2454 };
2455
2456 model_list.forEach((models, vendor) => {
2457 const optgroup = $(`<optgroup label="${vendor}">`);
2458
2459 models.forEach((model) => {
2460 appendOption(model, optgroup);
2461 });
2462
2463 $('#model_aimlapi_select').append(optgroup);
2464 });
2465 }2504 }
24662505 acc.get(vendor).push(curr);
2467function getAimlapiModelTemplate(option) {2506 return acc;
2468 const model = model_list.find(x => x.id === option?.element?.value);2507 }, new Map());
24692508 default:
2470 if (!option.id || !model) {2509 return new Map([['', array]]);
2471 return option.text;
2472 }2510 }
2473
2474 const vendor = model.id.split('/')[0];
2475
2476 return $((`
2477 <div class="flex-container flexFlowColumn" title="${DOMPurify.sanitize(model.id)}">
2478 <div><strong>${DOMPurify.sanitize(model.info?.name || model.name || model.id)}</strong> | ${vendor}</div>
2479 </div>
2480 `));
2481}2511}
24822512
2483/**2513/**
@@ -4151,6 +4181,10 @@ function migrateChatCompletionSettings(settings) {
4151 { oldKey: 'use_makersuite_sysprompt', oldValue: true, newKey: 'use_sysprompt', newValue: true },4181 { oldKey: 'use_makersuite_sysprompt', oldValue: true, newKey: 'use_sysprompt', newValue: true },
4152 { oldKey: 'mistralai_model', oldValue: /^(mistral-medium|mistral-small)$/, newKey: 'mistralai_model', newValue: (settings.mistralai_model + '-latest') },4182 { oldKey: 'mistralai_model', oldValue: /^(mistral-medium|mistral-small)$/, newKey: 'mistralai_model', newValue: (settings.mistralai_model + '-latest') },
4153 { oldKey: 'deepseek_model', oldValue: /^deepseek-(chat|reasoner|coder)$/, newKey: 'deepseek_model', newValue: 'deepseek-v4-flash' },4183 { oldKey: 'deepseek_model', oldValue: /^deepseek-(chat|reasoner|coder)$/, newKey: 'deepseek_model', newValue: 'deepseek-v4-flash' },
4184 { oldKey: 'openrouter_sort_models', oldValue: 'alphabetically', newKey: 'sort_models', newValue: 'alphabetically' },
4185 { oldKey: 'openrouter_sort_models', oldValue: 'pricing.prompt', newKey: 'sort_models', newValue: 'pricing.prompt' },
4186 { oldKey: 'openrouter_sort_models', oldValue: 'context_length', newKey: 'sort_models', newValue: 'context_length' },
4187 { oldKey: 'openrouter_group_models', oldValue: true, newKey: 'group_models', newValue: true },
4154 ];4188 ];
41554189
4156 for (const migration of migrateMap) {4190 for (const migration of migrateMap) {
@@ -5845,18 +5879,6 @@ async function onModelChange() {
5845 eventSource.emit(event_types.CHATCOMPLETION_MODEL_CHANGED, value);5879 eventSource.emit(event_types.CHATCOMPLETION_MODEL_CHANGED, value);
5846}5880}
58475881
5848async function onOpenrouterModelSortChange() {
5849 await getStatusOpen();
5850}
5851
5852async function onChutesModelSortChange() {
5853 await getStatusOpen();
5854}
5855
5856async function onElectronHubModelSortChange() {
5857 await getStatusOpen();
5858}
5859
5860async function onNewPresetClick() {5882async function onNewPresetClick() {
5861 const name = await Popup.show.input(t`Preset name:`, t`Hint: Use a character/group name to bind preset to a specific chat.`, oai_settings.preset_settings_openai);5883 const name = await Popup.show.input(t`Preset name:`, t`Hint: Use a character/group name to bind preset to a specific chat.`, oai_settings.preset_settings_openai);
58625884
@@ -6841,16 +6863,6 @@ export function initOpenAI() {
6841 saveSettingsDebounced();6863 saveSettingsDebounced();
6842 });6864 });
68436865
6844 $('#openrouter_group_models').on('input', function () {
6845 oai_settings.openrouter_group_models = !!$(this).prop('checked');
6846 saveSettingsDebounced();
6847 });
6848
6849 $('#openrouter_sort_models').on('input', function () {
6850 oai_settings.openrouter_sort_models = String($(this).val());
6851 saveSettingsDebounced();
6852 });
6853
6854 $('#openrouter_allow_fallbacks').on('input', function () {6866 $('#openrouter_allow_fallbacks').on('input', function () {
6855 oai_settings.openrouter_allow_fallbacks = !!$(this).prop('checked');6867 oai_settings.openrouter_allow_fallbacks = !!$(this).prop('checked');
6856 updateOpenRouterProvidersWarning('#openrouter_providers_chat');6868 updateOpenRouterProvidersWarning('#openrouter_providers_chat');
@@ -6862,21 +6874,6 @@ export function initOpenAI() {
6862 saveSettingsDebounced();6874 saveSettingsDebounced();
6863 });6875 });
68646876
6865 $('#electronhub_sort_models').on('input', function () {
6866 oai_settings.electronhub_sort_models = String($(this).val());
6867 saveSettingsDebounced();
6868 });
6869
6870 $('#chutes_sort_models').on('input', function () {
6871 oai_settings.chutes_sort_models = String($(this).val());
6872 saveSettingsDebounced();
6873 });
6874
6875 $('#electronhub_group_models').on('input', function () {
6876 oai_settings.electronhub_group_models = !!$(this).prop('checked');
6877 saveSettingsDebounced();
6878 });
6879
6880 $('#squash_system_messages').on('input', function () {6877 $('#squash_system_messages').on('input', function () {
6881 oai_settings.squash_system_messages = !!$(this).prop('checked');6878 oai_settings.squash_system_messages = !!$(this).prop('checked');
6882 saveSettingsDebounced();6879 saveSettingsDebounced();
@@ -7142,6 +7139,18 @@ export function initOpenAI() {
7142 saveSettingsDebounced();7139 saveSettingsDebounced();
7143 });7140 });
71447141
7142 $('#cc_group_models').on('input', async () => {
7143 oai_settings.group_models = $('#cc_group_models').prop('checked');
7144 reconnectOpenAi();
7145 saveSettingsDebounced();
7146 });
7147
7148 $('#cc_sort_models').on('input', async () => {
7149 oai_settings.sort_models = $('#cc_sort_models').val().toString();
7150 reconnectOpenAi();
7151 saveSettingsDebounced();
7152 });
7153
7145 $('#api_button_openai').on('click', onConnectButtonClick);7154 $('#api_button_openai').on('click', onConnectButtonClick);
7146 $('#openai_reverse_proxy').on('input', onReverseProxyInput);7155 $('#openai_reverse_proxy').on('input', onReverseProxyInput);
7147 $('#model_openai_select').on('change', onModelChange);7156 $('#model_openai_select').on('change', onModelChange);
@@ -7177,11 +7186,6 @@ export function initOpenAI() {
7177 $('#vertexai_validate_service_account').on('click', onVertexAIValidateServiceAccount);7186 $('#vertexai_validate_service_account').on('click', onVertexAIValidateServiceAccount);
7178 $('#vertexai_clear_service_account').on('click', onVertexAIClearServiceAccount);7187 $('#vertexai_clear_service_account').on('click', onVertexAIClearServiceAccount);
7179 $('#model_openrouter_select').on('change', onModelChange);7188 $('#model_openrouter_select').on('change', onModelChange);
7180 $('#openrouter_group_models').on('change', onOpenrouterModelSortChange);
7181 $('#openrouter_sort_models').on('change', onOpenrouterModelSortChange);
7182 $('#chutes_sort_models').on('change', onChutesModelSortChange);
7183 $('#electronhub_group_models').on('change', onElectronHubModelSortChange);
7184 $('#electronhub_sort_models').on('change', onElectronHubModelSortChange);
7185 $('#model_ai21_select').on('change', onModelChange);7189 $('#model_ai21_select').on('change', onModelChange);
7186 $('#model_mistralai_select').on('change', onModelChange);7190 $('#model_mistralai_select').on('change', onModelChange);
7187 $('#model_cohere_select').on('change', onModelChange);7191 $('#model_cohere_select').on('change', onModelChange);