Add model selection support for llama.cpp router mode (#4910) * Add model selection support for llama.cpp router mode - Add llamacpp_model setting to textgen-settings.js - Implement loadLlamaCppModels() function to fetch and populate models - Add onLlamaCppModelSelect() handler for model selection - Update status check to load llama.cpp models when connecting - Update getTextGenModel() to return selected llama.cpp model - Add model dropdown to HTML UI in llama.cpp section - Initialize event handlers and Select2 for better UX - Add llamacpp_model to preset manager for save/load support - Add llamacpp_model to slash commands support This implements model selection for llama.cpp router mode, allowing users to select from multiple models without restarting the server. Follows the same pattern as Ollama, Tabby, and vLLM implementations. * Correct spelling * Fix clear selection position --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>

7f98e3e21305ea41a917a2f4d10bc0d69a78b7c5

my-alt <250851737+my-alt-acct@users.noreply.github.com>

Signed
6 files changed, +66 -1Showing whitespace changes
public/css/select2-overrides.css+5 -0
@@ -16,6 +16,11 @@
16 user-select: none;16 user-select: none;
17}17}
1818
19.select2-container .select2-selection.select2-selection--single.select2-selection--clearable .select2-selection__clear {
20 top: 0;
21 right: 25px;
22}
23
19.select2-container .select2-selection .select2-selection__clear {24.select2-container .select2-selection .select2-selection__clear {
20 color: var(--SmartThemeBodyColor);25 color: var(--SmartThemeBodyColor);
21 font-size: 20px;26 font-size: 20px;
public/index.html+10 -0
@@ -2679,6 +2679,16 @@
2679 <small data-i18n="Example: http://127.0.0.1:8080">Example: http://127.0.0.1:8080</small>2679 <small data-i18n="Example: http://127.0.0.1:8080">Example: http://127.0.0.1:8080</small>
2680 <input id="llamacpp_api_url_text" class="text_pole wide100p" value="" autocomplete="off" data-server-history="llamacpp">2680 <input id="llamacpp_api_url_text" class="text_pole wide100p" value="" autocomplete="off" data-server-history="llamacpp">
2681 </div>2681 </div>
2682 <div>
2683 <h4>
2684 <span data-i18n="llama.cpp Model">llama.cpp Model</span>
2685 </h4>
2686 <select id="llamacpp_model">
2687 <option value="" data-i18n="-- Connect to the API --">
2688 -- Connect to the API --
2689 </option>
2690 </select>
2691 </div>
2682 </div>2692 </div>
2683 <div data-tg-type="ollama">2693 <div data-tg-type="ollama">
2684 <div class="flex-container flexFlowColumn">2694 <div class="flex-container flexFlowColumn">
public/scripts/preset-manager.js+1 -0
@@ -701,6 +701,7 @@ class PresetManager {
701 'ollama_model',701 'ollama_model',
702 'vllm_model',702 'vllm_model',
703 'aphrodite_model',703 'aphrodite_model',
704 'llamacpp_model',
704 'server_urls',705 'server_urls',
705 'type',706 'type',
706 'custom_model',707 'custom_model',
public/scripts/slash-commands.js+1 -0
@@ -4834,6 +4834,7 @@ function getModelOptions(quiet) {
4834 { id: 'aphrodite_model', api: 'textgenerationwebui', type: textgen_types.APHRODITE },4834 { id: 'aphrodite_model', api: 'textgenerationwebui', type: textgen_types.APHRODITE },
4835 { id: 'ollama_model', api: 'textgenerationwebui', type: textgen_types.OLLAMA },4835 { id: 'ollama_model', api: 'textgenerationwebui', type: textgen_types.OLLAMA },
4836 { id: 'tabby_model', api: 'textgenerationwebui', type: textgen_types.TABBY },4836 { id: 'tabby_model', api: 'textgenerationwebui', type: textgen_types.TABBY },
4837 { id: 'llamacpp_model', api: 'textgenerationwebui', type: textgen_types.LLAMACPP },
4837 { id: 'featherless_model', api: 'textgenerationwebui', type: textgen_types.FEATHERLESS },4838 { id: 'featherless_model', api: 'textgenerationwebui', type: textgen_types.FEATHERLESS },
4838 { id: 'model_openai_select', api: 'openai', type: chat_completion_sources.OPENAI },4839 { id: 'model_openai_select', api: 'openai', type: chat_completion_sources.OPENAI },
4839 { id: 'model_claude_select', api: 'openai', type: chat_completion_sources.CLAUDE },4840 { id: 'model_claude_select', api: 'openai', type: chat_completion_sources.CLAUDE },
public/scripts/textgen-models.js+39 -0
@@ -17,6 +17,7 @@ let vllmModels = [];
17let aphroditeModels = [];17let aphroditeModels = [];
18let featherlessModels = [];18let featherlessModels = [];
19let tabbyModels = [];19let tabbyModels = [];
20let llamacppModels = [];
20export let openRouterModels = [];21export let openRouterModels = [];
2122
22/**23/**
@@ -139,6 +140,30 @@ export async function loadTabbyModels(data) {
139 }140 }
140}141}
141142
143export async function loadLlamaCppModels(data) {
144 if (!Array.isArray(data)) {
145 console.error('Invalid llama.cpp models data', data);
146 return;
147 }
148
149 llamacppModels = data;
150 llamacppModels.sort((a, b) => a.id.localeCompare(b.id));
151 llamacppModels.unshift({ id: '' });
152
153 if (!llamacppModels.find(x => x.id === textgen_settings.llamacpp_model)) {
154 textgen_settings.llamacpp_model = llamacppModels[0]?.id || '';
155 }
156
157 $('#llamacpp_model').empty();
158 for (const model of llamacppModels) {
159 const option = document.createElement('option');
160 option.value = model.id;
161 option.text = model.id;
162 option.selected = model.id === textgen_settings.llamacpp_model;
163 $('#llamacpp_model').append(option);
164 }
165}
166
142export async function loadTogetherAIModels(data) {167export async function loadTogetherAIModels(data) {
143 if (!Array.isArray(data)) {168 if (!Array.isArray(data)) {
144 console.error('Invalid Together AI models data', data);169 console.error('Invalid Together AI models data', data);
@@ -637,6 +662,12 @@ function onTabbyModelSelect() {
637 $('#api_button_textgenerationwebui').trigger('click');662 $('#api_button_textgenerationwebui').trigger('click');
638}663}
639664
665function onLlamaCppModelSelect() {
666 const modelId = String($('#llamacpp_model').val());
667 textgen_settings.llamacpp_model = modelId;
668 $('#api_button_textgenerationwebui').trigger('click');
669}
670
640function onOpenRouterModelSelect() {671function onOpenRouterModelSelect() {
641 const modelId = String($('#openrouter_model').val());672 const modelId = String($('#openrouter_model').val());
642 textgen_settings.openrouter_model = modelId;673 textgen_settings.openrouter_model = modelId;
@@ -952,6 +983,7 @@ export function initTextGenModels() {
952 $('#aphrodite_model').on('change', onAphroditeModelSelect);983 $('#aphrodite_model').on('change', onAphroditeModelSelect);
953 $('#tabby_download_model').on('click', downloadTabbyModel);984 $('#tabby_download_model').on('click', downloadTabbyModel);
954 $('#tabby_model').on('change', onTabbyModelSelect);985 $('#tabby_model').on('change', onTabbyModelSelect);
986 $('#llamacpp_model').on('change', onLlamaCppModelSelect);
955 $('#featherless_model').on('change', () => onFeatherlessModelSelect(String($('#featherless_model').val())));987 $('#featherless_model').on('change', () => onFeatherlessModelSelect(String($('#featherless_model').val())));
956988
957 const providersSelect = $('.openrouter_providers');989 const providersSelect = $('.openrouter_providers');
@@ -990,6 +1022,13 @@ export function initTextGenModels() {
990 width: '100%',1022 width: '100%',
991 allowClear: true,1023 allowClear: true,
992 });1024 });
1025 $('#llamacpp_model').select2({
1026 placeholder: t`[Currently loaded]`,
1027 searchInputPlaceholder: t`Search models...`,
1028 searchInputCssClass: 'text_pole',
1029 width: '100%',
1030 allowClear: true,
1031 });
993 $('#model_infermaticai_select').select2({1032 $('#model_infermaticai_select').select2({
994 placeholder: t`Select a model`,1033 placeholder: t`Select a model`,
995 searchInputPlaceholder: t`Search models...`,1034 searchInputPlaceholder: t`Search models...`,
public/scripts/textgen-settings.js+10 -1
@@ -23,7 +23,7 @@ import { power_user, registerDebugFunction } from './power-user.js';
23import { getActiveManualApiSamplers, loadApiSelectedSamplers, isSamplerManualPriorityEnabled } from './samplerSelect.js';23import { getActiveManualApiSamplers, loadApiSelectedSamplers, isSamplerManualPriorityEnabled } from './samplerSelect.js';
24import { SECRET_KEYS, writeSecret } from './secrets.js';24import { SECRET_KEYS, writeSecret } from './secrets.js';
25import { getEventSourceStream } from './sse-stream.js';25import { getEventSourceStream } from './sse-stream.js';
26import { getCurrentDreamGenModelTokenizer, getCurrentOpenRouterModelTokenizer, loadAphroditeModels, loadDreamGenModels, loadFeatherlessModels, loadGenericModels, loadInfermaticAIModels, loadMancerModels, loadOllamaModels, loadOpenRouterModels, loadTabbyModels, loadTogetherAIModels, loadVllmModels } from './textgen-models.js';26import { getCurrentDreamGenModelTokenizer, getCurrentOpenRouterModelTokenizer, loadAphroditeModels, loadDreamGenModels, loadFeatherlessModels, loadGenericModels, loadInfermaticAIModels, loadLlamaCppModels, loadMancerModels, loadOllamaModels, loadOpenRouterModels, loadTabbyModels, loadTogetherAIModels, loadVllmModels } from './textgen-models.js';
27import { ENCODE_TOKENIZERS, TEXTGEN_TOKENIZERS, TOKENIZER_SUPPORTED_KEY, getTextTokens, tokenizers } from './tokenizers.js';27import { ENCODE_TOKENIZERS, TEXTGEN_TOKENIZERS, TOKENIZER_SUPPORTED_KEY, getTextTokens, tokenizers } from './tokenizers.js';
28import { AbortReason } from './util/AbortReason.js';28import { AbortReason } from './util/AbortReason.js';
29import { getSortableDelay, onlyUnique, arraysEqual, isObject } from './utils.js';29import { getSortableDelay, onlyUnique, arraysEqual, isObject } from './utils.js';
@@ -214,6 +214,7 @@ export const textgenerationwebui_settings = {
214 aphrodite_model: '',214 aphrodite_model: '',
215 dreamgen_model: 'lucid-v1-extra-large/text',215 dreamgen_model: 'lucid-v1-extra-large/text',
216 tabby_model: '',216 tabby_model: '',
217 llamacpp_model: '',
217 sampler_order: KOBOLDCPP_ORDER,218 sampler_order: KOBOLDCPP_ORDER,
218 logit_bias: [],219 logit_bias: [],
219 n: 1,220 n: 1,
@@ -701,6 +702,9 @@ async function getStatusTextgen() {
701 } else if (textgenerationwebui_settings.type === textgen_types.TABBY) {702 } else if (textgenerationwebui_settings.type === textgen_types.TABBY) {
702 loadTabbyModels(data?.data);703 loadTabbyModels(data?.data);
703 setOnlineStatus(textgenerationwebui_settings.tabby_model || data?.result);704 setOnlineStatus(textgenerationwebui_settings.tabby_model || data?.result);
705 } else if (textgenerationwebui_settings.type === textgen_types.LLAMACPP) {
706 loadLlamaCppModels(data?.data);
707 setOnlineStatus(textgenerationwebui_settings.llamacpp_model || data?.result || t`Connected`);
704 } else if (textgenerationwebui_settings.type === textgen_types.GENERIC) {708 } else if (textgenerationwebui_settings.type === textgen_types.GENERIC) {
705 loadGenericModels(data?.data);709 loadGenericModels(data?.data);
706 setOnlineStatus(textgenerationwebui_settings.generic_model || data?.result || t`Connected`);710 setOnlineStatus(textgenerationwebui_settings.generic_model || data?.result || t`Connected`);
@@ -1462,6 +1466,11 @@ export function getTextGenModel(settings = null) {
1462 return settings.tabby_model;1466 return settings.tabby_model;
1463 }1467 }
1464 break;1468 break;
1469 case LLAMACPP:
1470 if (settings.llamacpp_model) {
1471 return settings.llamacpp_model;
1472 }
1473 break;
1465 default:1474 default:
1466 return undefined;1475 return undefined;
1467 }1476 }