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>
Signed| @@ -3224,36 +3224,6 @@ | ||
| 3224 | 3224 | <input id="openrouter_use_fallback" type="checkbox" /> |
| 3225 | 3225 | <span data-i18n="Allow fallback models">Allow fallback models</span> |
| 3226 | 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 | 3227 | <div> |
| 3258 | 3228 | <h4> |
| 3259 | 3229 | <span data-i18n="Model Providers">Model Providers</span> |
| @@ -3641,37 +3611,6 @@ | ||
| 3641 | 3611 | <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option> |
| 3642 | 3612 | </select> |
| 3643 | 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 | 3614 | </div> |
| 3676 | 3615 | <div id="chutes_form" data-source="chutes"> |
| 3677 | 3616 | <h4 data-i18n="Chutes API Key">Chutes API Key</h4> |
| @@ -3691,26 +3630,6 @@ | ||
| 3691 | 3630 | <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option> |
| 3692 | 3631 | </select> |
| 3693 | 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 | 3633 | </div> |
| 3715 | 3634 | <div id="nanogpt_form" data-source="nanogpt"> |
| 3716 | 3635 | <h4 data-i18n="NanoGPT API Key">NanoGPT API Key</h4> |
| @@ -4062,6 +3981,37 @@ | ||
| 4062 | 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 | 3982 | </div> |
| 4064 | 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 | 4015 | <div id="prompt_post_processing_form"> |
| 4066 | 4016 | <h4> |
| 4067 | 4017 | <span data-i18n="Prompt Post-Processing"> |
| @@ -306,12 +306,12 @@ export const settingsToUpdate = { | ||
| 306 | 306 | min_p: ['#min_p_openai', 'min_p_openai', false, false], |
| 307 | 307 | repetition_penalty: ['#repetition_penalty_openai', 'repetition_penalty_openai', false, false], |
| 308 | 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 | 311 | openai_model: ['#model_openai_select', 'openai_model', false, true], |
| 310 | 312 | claude_model: ['#model_claude_select', 'claude_model', false, true], |
| 311 | 313 | openrouter_model: ['#model_openrouter_select', 'openrouter_model', false, true], |
| 312 | 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 | 315 | openrouter_providers: ['#openrouter_providers_chat', 'openrouter_providers', false, true], |
| 316 | 316 | openrouter_quantizations: ['#openrouter_quantizations_chat', 'openrouter_quantizations', false, true], |
| 317 | 317 | openrouter_allow_fallbacks: ['#openrouter_allow_fallbacks', 'openrouter_allow_fallbacks', true, true], |
| @@ -323,14 +323,11 @@ export const settingsToUpdate = { | ||
| 323 | 323 | perplexity_model: ['#model_perplexity_select', 'perplexity_model', false, true], |
| 324 | 324 | groq_model: ['#model_groq_select', 'groq_model', false, true], |
| 325 | 325 | chutes_model: ['#model_chutes_select', 'chutes_model', false, true], |
| 326 | - chutes_sort_models: ['#chutes_sort_models', 'chutes_sort_models', false, true], | |
| 327 | 326 | siliconflow_model: ['#model_siliconflow_select', 'siliconflow_model', false, true], |
| 328 | 327 | siliconflow_endpoint: ['#siliconflow_endpoint', 'siliconflow_endpoint', false, true], |
| 329 | 328 | minimax_model: ['#model_minimax_select', 'minimax_model', false, true], |
| 330 | 329 | minimax_endpoint: ['#minimax_endpoint', 'minimax_endpoint', false, true], |
| 331 | 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 | 331 | nanogpt_model: ['#model_nanogpt_select', 'nanogpt_model', false, true], |
| 335 | 332 | deepseek_model: ['#model_deepseek_select', 'deepseek_model', false, true], |
| 336 | 333 | aimlapi_model: ['#model_aimlapi_select', 'aimlapi_model', false, true], |
| @@ -428,6 +425,8 @@ const default_settings = { | ||
| 428 | 425 | group_nudge_prompt: default_group_nudge_prompt, |
| 429 | 426 | scenario_format: default_scenario_format, |
| 430 | 427 | personality_format: default_personality_format, |
| 428 | + sort_models: 'alphabetically', | |
| 429 | + group_models: false, | |
| 431 | 430 | openai_model: 'gpt-4-turbo', |
| 432 | 431 | claude_model: 'claude-sonnet-4-5', |
| 433 | 432 | google_model: 'gemini-2.5-pro', |
| @@ -438,14 +437,11 @@ const default_settings = { | ||
| 438 | 437 | perplexity_model: 'sonar-pro', |
| 439 | 438 | groq_model: 'llama-3.3-70b-versatile', |
| 440 | 439 | chutes_model: 'deepseek-ai/DeepSeek-V3-0324', |
| 441 | - chutes_sort_models: 'alphabetically', | |
| 442 | 440 | siliconflow_model: 'deepseek-ai/DeepSeek-V3', |
| 443 | 441 | siliconflow_endpoint: SILICONFLOW_ENDPOINT.GLOBAL, |
| 444 | 442 | minimax_model: 'MiniMax-M2.7', |
| 445 | 443 | minimax_endpoint: MINIMAX_ENDPOINT.GLOBAL, |
| 446 | 444 | electronhub_model: 'gpt-4o-mini', |
| 447 | - electronhub_sort_models: 'alphabetically', | |
| 448 | - electronhub_group_models: false, | |
| 449 | 445 | nanogpt_model: 'gpt-4o-mini', |
| 450 | 446 | deepseek_model: 'deepseek-v4-flash', |
| 451 | 447 | aimlapi_model: 'chatgpt-4o-latest', |
| @@ -469,8 +465,6 @@ const default_settings = { | ||
| 469 | 465 | custom_include_headers: '', |
| 470 | 466 | openrouter_model: openrouter_website_model, |
| 471 | 467 | openrouter_use_fallback: false, |
| 472 | - openrouter_group_models: false, | |
| 473 | - openrouter_sort_models: 'alphabetically', | |
| 474 | 468 | openrouter_providers: [], |
| 475 | 469 | openrouter_quantizations: [], |
| 476 | 470 | openrouter_allow_fallbacks: true, |
| @@ -1897,6 +1891,33 @@ function getChutesModelTemplate(option) { | ||
| 1897 | 1891 | `)); |
| 1898 | 1892 | } |
| 1899 | 1893 | |
| 1894 | +function 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 | + | |
| 1900 | 1921 | function getNanoGptModelTemplate(option) { |
| 1901 | 1922 | const model = model_list.find(x => x.id === option?.element?.value); |
| 1902 | 1923 | |
| @@ -1906,10 +1927,9 @@ function getNanoGptModelTemplate(option) { | ||
| 1906 | 1927 | |
| 1907 | 1928 | const inputPrice = model.pricing?.prompt; |
| 1908 | 1929 | const outputPrice = model.pricing?.completion; |
| 1909 | - | |
| 1910 | 1930 | let price = 'Unknown'; |
| 1931 | + | |
| 1911 | 1932 | if (inputPrice !== undefined && outputPrice !== undefined) { |
| 1912 | - // Check if both prices are 0 (free model) | |
| 1913 | 1933 | if (inputPrice === 0 && outputPrice === 0) { |
| 1914 | 1934 | price = 'Free'; |
| 1915 | 1935 | } else { |
| @@ -1917,40 +1937,61 @@ function getNanoGptModelTemplate(option) { | ||
| 1917 | 1937 | } |
| 1918 | 1938 | } |
| 1919 | 1939 | |
| 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 | 1971 | const contextLength = model.context_length || 'Unknown'; |
| 1972 | + const modelName = model.name || model.id; | |
| 1921 | 1973 | |
| 1922 | 1974 | return $((` |
| 1923 | 1975 | <div class="flex-container alignItemsBaseline" title="${DOMPurify.sanitize(model.id)}"> |
| 1924 | 1976 | <strong>${DOMPurify.sanitize(model.idmodelName)}</strong> | ${contextLength} ctx | <small>${price}</small>${capabilities} |
| 1925 | 1977 | </div> |
| 1926 | 1978 | `)); |
| 1927 | 1979 | } |
| 1928 | 1980 | |
| 1929 | 1981 | function calculateChutesCostgetAimlapiModelTemplate(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; | |
| 1940 | 1983 | |
| 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 | } |
| 1952 | 1987 | |
| 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 | } |
| 1955 | 1996 | |
| 1956 | 1997 | function saveModelList(data) { |
| @@ -1958,14 +1999,22 @@ function saveModelList(data) { | ||
| 1958 | 1999 | model_list.sort((a, b) => a?.id && b?.id && a.id.localeCompare(b.id)); |
| 1959 | 2000 | |
| 1960 | 2001 | if (oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER) { |
| 1961 | 2002 | model_list = openRouterSortBysortModelsBy(model_list, oai_settings.openrouter_sort_modelssort_models, chat_completion_sources.OPENROUTER); |
| 1962 | - | |
| 1963 | 2003 | $('#model_openrouter_select').empty(); |
| 2004 | + $('#model_openrouter_select').append($('<option>', { value: openrouter_website_model, text: t`Use OpenRouter website setting` })); | |
| 1964 | 2005 | |
| 1965 | 2006 | if (true === oai_settings.openrouter_group_modelsgroup_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 | 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 | } |
| 1970 | 2019 | |
| 1971 | 2020 | $('#model_openrouter_select').val(oai_settings.openrouter_model).trigger('change'); |
| @@ -2005,13 +2054,26 @@ function saveModelList(data) { | ||
| 2005 | 2054 | } |
| 2006 | 2055 | |
| 2007 | 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 | 2059 | $('#model_aimlapi_select').empty(); |
| 2009 | - const chatModels = model_list.filter(m => m.type === 'chat-completion'); | |
| 2010 | 2060 | |
| 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 | + } | |
| 2012 | 2074 | |
| 2013 | 2075 | if (!oai_settings.aimlapi_model && chatModelsmodel_list.length > 0) { |
| 2014 | 2076 | oai_settings.aimlapi_model = chatModelsmodel_list[0].id; |
| 2015 | 2077 | } |
| 2016 | 2078 | |
| 2017 | 2079 | $('#model_aimlapi_select').val(oai_settings.aimlapi_model).trigger('change'); |
| @@ -2034,13 +2096,22 @@ function saveModelList(data) { | ||
| 2034 | 2096 | |
| 2035 | 2097 | if (oai_settings.chat_completion_source == chat_completion_sources.ELECTRONHUB) { |
| 2036 | 2098 | model_list = model_list.filter(model => model?.endpoints?.includes('/v1/chat/completions')); |
| 2037 | - | |
| 2099 | + 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 | 2100 | $('#model_electronhub_select').empty(); |
| 2041 | 2101 | |
| 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 | + } | |
| 2044 | 2115 | |
| 2045 | 2116 | const selectedModel = model_list.find(model => model.id === oai_settings.electronhub_model); |
| 2046 | 2117 | if (model_list.length > 0 && (!selectedModel || !oai_settings.electronhub_model)) { |
| @@ -2052,15 +2123,21 @@ function saveModelList(data) { | ||
| 2052 | 2123 | |
| 2053 | 2124 | if (oai_settings.chat_completion_source == chat_completion_sources.CHUTES) { |
| 2054 | 2125 | model_list = model_list.filter(model => typeof model.id === 'string' && !model.id.toLowerCase().includes('affine')); |
| 2055 | - | |
| 2126 | + 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 | 2127 | $('#model_chutes_select').empty(); |
| 2059 | 2128 | |
| 2060 | 2129 | forif (const model of model_listoai_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 | } |
| 2065 | 2142 | |
| 2066 | 2143 | const selectedModel = model_list.find(model => model.id === oai_settings.chutes_model); |
| @@ -2072,14 +2149,22 @@ function saveModelList(data) { | ||
| 2072 | 2149 | } |
| 2073 | 2150 | |
| 2074 | 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 | 2153 | $('#model_nanogpt_select').empty(); |
| 2076 | - model_list.forEach((model) => { | |
| 2154 | + | |
| 2077 | - $('#model_nanogpt_select').append( | |
| 2155 | + if (oai_settings.group_models) { | |
| 2078 | - $('<option>', { | |
| 2156 | + groupModelsByVendor(model_list, chat_completion_sources.NANOGPT).forEach((models, vendor) => { | |
| 2079 | - value: model.id, | |
| 2157 | + const optgroup = $('<optgroup>').attr('label', vendor); | |
| 2080 | - text: model.id, | |
| 2158 | + models.forEach((model) => { | |
| 2081 | - })); | |
| 2159 | + optgroup.append($('<option>', { value: model.id, text: model.name || model.id })); | |
| 2082 | 2160 | }); |
| 2161 | + $('#model_nanogpt_select').append(optgroup); | |
| 2162 | + }); | |
| 2163 | + } else { | |
| 2164 | + model_list.forEach((model) => { | |
| 2165 | + $('#model_nanogpt_select').append($('<option>', { value: model.id, text: model.name || model.id })); | |
| 2166 | + }); | |
| 2167 | + } | |
| 2083 | 2168 | |
| 2084 | 2169 | const selectedModel = model_list.find(model => model.id === oai_settings.nanogpt_model); |
| 2085 | 2170 | if (model_list.length > 0 && (!selectedModel || !oai_settings.nanogpt_model)) { |
| @@ -2092,11 +2177,7 @@ function saveModelList(data) { | ||
| 2092 | 2177 | if (oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK) { |
| 2093 | 2178 | $('#model_deepseek_select').empty(); |
| 2094 | 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 | }); |
| 2101 | 2182 | |
| 2102 | 2183 | const selectedModel = model_list.find(model => model.id === oai_settings.deepseek_model); |
| @@ -2110,11 +2191,7 @@ function saveModelList(data) { | ||
| 2110 | 2191 | if (oai_settings.chat_completion_source === chat_completion_sources.POLLINATIONS) { |
| 2111 | 2192 | $('#model_pollinations_select').empty(); |
| 2112 | 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 | }); |
| 2119 | 2196 | |
| 2120 | 2197 | const selectedModel = model_list.find(model => model.id === oai_settings.pollinations_model); |
| @@ -2303,181 +2380,134 @@ function saveModelList(data) { | ||
| 2303 | 2380 | } |
| 2304 | 2381 | } |
| 2305 | 2382 | |
| 2306 | -function 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. | |
| 2308 | - | |
| 2385 | + * @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, | |
| 2390 | +function sortModelsBy(data, property, source) { | |
| 2314 | - })); | |
| 2391 | + switch (source) { | |
| 2315 | - }; | |
| 2392 | + case chat_completion_sources.OPENROUTER: | |
| 2316 | - | |
| 2393 | + return data.sort((a, b) => { | |
| 2317 | 2394 | if (groupModelsproperty === 'context_length') { |
| 2318 | - model_list.forEach((models, vendor) => { | |
| 2395 | + return (b.context_length || 0) - (a.context_length || 0); | |
| 2319 | - const optgroup = $(`<optgroup label="${vendor}">`); | |
| 2396 | + } else if (property === 'pricing.input' || property === 'pricing.prompt') { | |
| 2320 | - | |
| 2397 | + return parseFloat(a.pricing?.prompt || 0) - parseFloat(b.pricing?.prompt || 0); | |
| 2321 | - models.forEach((model) => { | |
| 2398 | + } else if (property === 'pricing.output' || property === 'pricing.completion') { | |
| 2322 | - appendOption(model, optgroup); | |
| 2399 | + return parseFloat(a.pricing?.completion || 0) - parseFloat(b.pricing?.completion || 0); | |
| 2400 | + } else { | |
| 2401 | + return a?.name && b?.name ? a.name.localeCompare(b.name) : 0; | |
| 2402 | + } | |
| 2323 | 2403 | }); |
| 2324 | - | |
| 2404 | + case chat_completion_sources.CHUTES: | |
| 2325 | - $('#model_openrouter_select').append(optgroup); | |
| 2405 | + return data.sort((a, b) => { | |
| 2326 | - }); | |
| 2406 | + if (property === 'context_length') { | |
| 2327 | - } else { | |
| 2407 | + return (b.context_length || 0) - (a.context_length || 0); | |
| 2328 | - model_list.forEach((model) => { | |
| 2408 | + } else if (property === 'pricing.input' || property === 'pricing.prompt') { | |
| 2329 | - appendOption(model); | |
| 2409 | + return parseFloat(a.pricing?.input || 0) - parseFloat(b.pricing?.input || 0); | |
| 2330 | - }); | |
| 2410 | + } else if (property === 'pricing.output' || property === 'pricing.completion') { | |
| 2331 | - } | |
| 2411 | + return parseFloat(a.pricing?.output || 0) - parseFloat(b.pricing?.output || 0); | |
| 2332 | -} | |
| 2412 | + } else { | |
| 2333 | - | |
| 2413 | + return a?.id && b?.id ? a.id.localeCompare(b.id) : 0; | |
| 2334 | -const openRouterSortBy = (data, property = 'alphabetically') => { | |
| 2414 | + } | |
| 2335 | - return data.sort((a, b) => { | |
| 2336 | - if (property === 'context_length') { | |
| 2337 | - return b.context_length - a.context_length; | |
| 2338 | - } else if (property === 'pricing.prompt') { | |
| 2339 | - return parseFloat(a.pricing.prompt) - parseFloat(b.pricing.prompt); | |
| 2340 | - } else { | |
| 2341 | - // Alphabetically | |
| 2342 | - return a?.name && b?.name && a.name.localeCompare(b.name); | |
| 2343 | - } | |
| 2344 | - }); | |
| 2345 | -}; | |
| 2346 | - | |
| 2347 | -function 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 | - | |
| 2361 | -function chutesSortBy(data, property = 'alphabetically') { | |
| 2362 | - return data.sort((a, b) => { | |
| 2363 | - if (property === 'context_length') { | |
| 2364 | - return b.context_length - a.context_length; | |
| 2365 | - } else if (property === 'pricing.input') { | |
| 2366 | - const aPrice = parseFloat(a.pricing?.input || 0); | |
| 2367 | - const bPrice = parseFloat(b.pricing?.input || 0); | |
| 2368 | - return aPrice - bPrice; | |
| 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 { | |
| 2374 | - return a?.id && b?.id && a.id.localeCompare(b.id); | |
| 2375 | - } | |
| 2376 | - }); | |
| 2377 | -} | |
| 2378 | - | |
| 2379 | -function 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 | 2415 | }); |
| 2395 | - | |
| 2416 | + case chat_completion_sources.ELECTRONHUB: | |
| 2396 | - $('#model_electronhub_select').append(optgroup); | |
| 2417 | + return data.sort((a, b) => { | |
| 2397 | - }); | |
| 2418 | + if (property === 'context_length') { | |
| 2398 | - } else { | |
| 2419 | + return (b.tokens || 0) - (a.tokens || 0); | |
| 2399 | - model_list.forEach((model) => { | |
| 2420 | + } else if (property === 'pricing.input' || property === 'pricing.prompt') { | |
| 2400 | - appendOption(model); | |
| 2421 | + return parseFloat(a.pricing?.input || 0) - parseFloat(b.pricing?.input || 0); | |
| 2401 | - }); | |
| 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; | |
| 2426 | + } | |
| 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); | |
| 2436 | + } else { | |
| 2437 | + return a?.name && b?.name ? a.name.localeCompare(b.name) : 0; | |
| 2438 | + } | |
| 2439 | + }); | |
| 2440 | + case chat_completion_sources.AIMLAPI: | |
| 2441 | + return data.sort((a, b) => { | |
| 2442 | + if (property === 'context_length') { | |
| 2443 | + return (b.info?.contextLength || 0) - (a.info?.contextLength || 0); | |
| 2444 | + } else { | |
| 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; | |
| 2447 | + } | |
| 2448 | + }); | |
| 2449 | + default: | |
| 2450 | + return data; | |
| 2402 | 2451 | } |
| 2403 | 2452 | } |
| 2404 | 2453 | |
| 2405 | -function electronHubSortBy(data, property = 'alphabetically') { | |
| 2454 | +/** | |
| 2406 | - return data.sort((a, b) => { | |
| 2455 | + * Groups models by vendor for the given source. If not supported, returns a map with a single entry containing all models. | |
| 2407 | - if (property === 'context_length') { | |
| 2456 | + * @param {object[]} array Array of model objects | |
| 2408 | - return b.tokens - a.tokens; | |
| 2457 | + * @param {string} source Chat Completion source (e.g., 'openrouter') | |
| 2409 | - } else if (property === 'pricing.input') { | |
| 2458 | + * @returns {Map<string, object[]>} Map of vendor to array of models | |
| 2410 | - return parseFloat(a.pricing.input) - parseFloat(b.pricing.input); | |
| 2459 | + */ | |
| 2411 | - } else if (property === 'pricing.output') { | |
| 2460 | +function groupModelsByVendor(array, source) { | |
| 2412 | - return parseFloat(a.pricing.output) - parseFloat(b.pricing.output); | |
| 2461 | + switch (source) { | |
| 2413 | - } else { | |
| 2462 | + case chat_completion_sources.OPENROUTER: | |
| 2414 | - return a?.name && b?.name && a.name.localeCompare(b.name); | |
| 2463 | + return array.reduce((acc, curr) => { | |
| 2415 | - } | |
| 2464 | + const vendor = curr.id.split('/')[0]; | |
| 2416 | - }); | |
| 2465 | + if (!acc.has(vendor)) { | |
| 2417 | -} | |
| 2466 | + acc.set(vendor, []); | |
| 2418 | - | |
| 2467 | + } | |
| 2419 | -function electronHubGroupByVendor(array) { | |
| 2468 | + acc.get(vendor).push(curr); | |
| 2420 | - return array.reduce((acc, curr) => { | |
| 2469 | + return acc; | |
| 2421 | - const vendor = String(curr?.name || curr?.id || 'Other').split(':')[0].trim() || 'Other'; | |
| 2470 | + }, new Map()); | |
| 2422 | - | |
| 2471 | + case chat_completion_sources.ELECTRONHUB: | |
| 2423 | - if (!acc.has(vendor)) { | |
| 2472 | + return array.reduce((acc, curr) => { | |
| 2424 | - acc.set(vendor, []); | |
| 2473 | + const vendor = String(curr?.name || curr?.id || 'Other').split(':')[0].trim() || 'Other'; | |
| 2425 | - } | |
| 2474 | + if (!acc.has(vendor)) { | |
| 2426 | - | |
| 2475 | + acc.set(vendor, []); | |
| 2427 | - acc.get(vendor).push(curr); | |
| 2476 | + } | |
| 2428 | - | |
| 2477 | + acc.get(vendor).push(curr); | |
| 2429 | 2478 | return acc; |
| 2430 | 2479 | }, new Map()); |
| 2431 | -} | |
| 2480 | + case chat_completion_sources.NANOGPT: | |
| 2432 | - | |
| 2481 | + return array.reduce((acc, curr) => { | |
| 2433 | -function aimlapiGroupByVendor(array) { | |
| 2482 | + const vendorPart = /\//.test(curr.id) ? curr.id.split('/')[0] : curr.id.split('-')[0]; | |
| 2434 | - return array.reduce((acc, curr) => { | |
| 2483 | + const vendor = String(vendorPart?.trim()?.toLowerCase() || 'Other'); | |
| 2435 | - const vendor = curr.info.developer; | |
| 2484 | + if (!acc.has(vendor)) { | |
| 2436 | - | |
| 2485 | + acc.set(vendor, []); | |
| 2437 | - if (!acc.has(vendor)) { | |
| 2486 | + } | |
| 2438 | - acc.set(vendor, []); | |
| 2487 | + acc.get(vendor).push(curr); | |
| 2439 | - } | |
| 2488 | + return acc; | |
| 2440 | - | |
| 2489 | + }, new Map()); | |
| 2441 | - acc.get(vendor).push(curr); | |
| 2490 | + case chat_completion_sources.CHUTES: | |
| 2442 | - | |
| 2491 | + return array.reduce((acc, curr) => { | |
| 2443 | - return acc; | |
| 2492 | + const vendor = curr.id.split('/')[0]; | |
| 2444 | - }, new Map()); | |
| 2493 | + if (!acc.has(vendor)) { | |
| 2445 | -} | |
| 2494 | + acc.set(vendor, []); | |
| 2446 | - | |
| 2495 | + } | |
| 2447 | -function appendAimlapiOptions(model_list) { | |
| 2496 | + acc.get(vendor).push(curr); | |
| 2448 | - const appendOption = (model, parent = null) => { | |
| 2497 | + return acc; | |
| 2449 | - (parent || $('#model_aimlapi_select')).append( | |
| 2498 | + }, new Map()); | |
| 2450 | - $('<option>', { | |
| 2499 | + case chat_completion_sources.AIMLAPI: | |
| 2451 | - value: model.id, | |
| 2500 | + return array.reduce((acc, curr) => { | |
| 2452 | - text: model.info?.name || model.name || model.id, | |
| 2501 | + const vendor = curr.info?.developer || 'Other'; | |
| 2453 | - })); | |
| 2502 | + if (!acc.has(vendor)) { | |
| 2454 | - }; | |
| 2503 | + acc.set(vendor, []); | |
| 2455 | - | |
| 2504 | + } | |
| 2456 | - model_list.forEach((models, vendor) => { | |
| 2505 | + acc.get(vendor).push(curr); | |
| 2457 | - const optgroup = $(`<optgroup label="${vendor}">`); | |
| 2506 | + return acc; | |
| 2458 | - | |
| 2507 | + }, new Map()); | |
| 2459 | - models.forEach((model) => { | |
| 2508 | + default: | |
| 2460 | - appendOption(model, optgroup); | |
| 2509 | + return new Map([['', array]]); | |
| 2461 | - }); | |
| 2462 | - | |
| 2463 | - $('#model_aimlapi_select').append(optgroup); | |
| 2464 | - }); | |
| 2465 | -} | |
| 2466 | - | |
| 2467 | -function getAimlapiModelTemplate(option) { | |
| 2468 | - const model = model_list.find(x => x.id === option?.element?.value); | |
| 2469 | - | |
| 2470 | - if (!option.id || !model) { | |
| 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 | } |
| 2482 | 2512 | |
| 2483 | 2513 | /** |
| @@ -4151,6 +4181,10 @@ function migrateChatCompletionSettings(settings) { | ||
| 4151 | 4181 | { oldKey: 'use_makersuite_sysprompt', oldValue: true, newKey: 'use_sysprompt', newValue: true }, |
| 4152 | 4182 | { oldKey: 'mistralai_model', oldValue: /^(mistral-medium|mistral-small)$/, newKey: 'mistralai_model', newValue: (settings.mistralai_model + '-latest') }, |
| 4153 | 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 | ]; |
| 4155 | 4189 | |
| 4156 | 4190 | for (const migration of migrateMap) { |
| @@ -5845,18 +5879,6 @@ async function onModelChange() { | ||
| 5845 | 5879 | eventSource.emit(event_types.CHATCOMPLETION_MODEL_CHANGED, value); |
| 5846 | 5880 | } |
| 5847 | 5881 | |
| 5848 | -async function onOpenrouterModelSortChange() { | |
| 5849 | - await getStatusOpen(); | |
| 5850 | -} | |
| 5851 | - | |
| 5852 | -async function onChutesModelSortChange() { | |
| 5853 | - await getStatusOpen(); | |
| 5854 | -} | |
| 5855 | - | |
| 5856 | -async function onElectronHubModelSortChange() { | |
| 5857 | - await getStatusOpen(); | |
| 5858 | -} | |
| 5859 | - | |
| 5860 | 5882 | async function onNewPresetClick() { |
| 5861 | 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); |
| 5862 | 5884 | |
| @@ -6841,16 +6863,6 @@ export function initOpenAI() { | ||
| 6841 | 6863 | saveSettingsDebounced(); |
| 6842 | 6864 | }); |
| 6843 | 6865 | |
| 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 | 6866 | $('#openrouter_allow_fallbacks').on('input', function () { |
| 6855 | 6867 | oai_settings.openrouter_allow_fallbacks = !!$(this).prop('checked'); |
| 6856 | 6868 | updateOpenRouterProvidersWarning('#openrouter_providers_chat'); |
| @@ -6862,21 +6874,6 @@ export function initOpenAI() { | ||
| 6862 | 6874 | saveSettingsDebounced(); |
| 6863 | 6875 | }); |
| 6864 | 6876 | |
| 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 | 6877 | $('#squash_system_messages').on('input', function () { |
| 6881 | 6878 | oai_settings.squash_system_messages = !!$(this).prop('checked'); |
| 6882 | 6879 | saveSettingsDebounced(); |
| @@ -7142,6 +7139,18 @@ export function initOpenAI() { | ||
| 7142 | 7139 | saveSettingsDebounced(); |
| 7143 | 7140 | }); |
| 7144 | 7141 | |
| 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 | 7154 | $('#api_button_openai').on('click', onConnectButtonClick); |
| 7146 | 7155 | $('#openai_reverse_proxy').on('input', onReverseProxyInput); |
| 7147 | 7156 | $('#model_openai_select').on('change', onModelChange); |
| @@ -7177,11 +7186,6 @@ export function initOpenAI() { | ||
| 7177 | 7186 | $('#vertexai_validate_service_account').on('click', onVertexAIValidateServiceAccount); |
| 7178 | 7187 | $('#vertexai_clear_service_account').on('click', onVertexAIClearServiceAccount); |
| 7179 | 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 | 7189 | $('#model_ai21_select').on('change', onModelChange); |
| 7186 | 7190 | $('#model_mistralai_select').on('change', onModelChange); |
| 7187 | 7191 | $('#model_cohere_select').on('change', onModelChange); |