OpenRouter: match both id/text in select2 (#4237) * OpenRouter: match both id/text in select2 * Update public/scripts/utils.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

e1a55408b60e068b648f2d0c53992aa26e357a75

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
3 files changed, +54 -1Showing whitespace changes
public/scripts/openai.js+2 -0
@@ -61,6 +61,7 @@ import {
6161 parseJsonFile,
6262 resetScrollHeight,
6363 stringFormat,
64+ textValueMatcher,
6465 uuidv4,
6566} from './utils.js';
6667import { countTokensOpenAIAsync, getTokenizerModel } from './tokenizers.js';
@@ -6348,6 +6349,7 @@ export function initOpenAI() {
63486349 searchInputCssClass: 'text_pole',
63496350 width: '100%',
63506351 templateResult: getOpenRouterModelTemplate,
6352+ matcher: textValueMatcher,
63516353 });
63526354 $('#model_aimlapi_select').select2({
63536355 placeholder: t`Select a model`,
public/scripts/textgen-models.js+2 -1
@@ -7,7 +7,7 @@ import { renderTemplateAsync } from './templates.js';
77import { POPUP_TYPE, callGenericPopup } from './popup.js';
88import { t } from './i18n.js';
99import { accountStorage } from './util/AccountStorage.js';
1010import { localizePagination, PAGINATION_TEMPLATE, textValueMatcher } from './utils.js';
1111
1212let mancerModels = [];
1313let togetherModels = [];
@@ -1005,6 +1005,7 @@ export function initTextGenModels() {
10051005 searchInputCssClass: 'text_pole',
10061006 width: '100%',
10071007 templateResult: getOpenRouterModelTemplate,
1008+ matcher: textValueMatcher,
10081009 });
10091010 $('#vllm_model').select2({
10101011 placeholder: t`Select a model`,
public/scripts/utils.js+50 -0
@@ -2483,3 +2483,53 @@ export function clearInfoBlock(target) {
24832483 infoBlock.innerHTML = '';
24842484 }
24852485}
2486+
2487+/**
2488+ * Provides a matcher function for select2 that matches both the text and value of options.
2489+ * @param {import('select2').SearchOptions} params
2490+ * @param {import('select2').OptGroupData|import('select2').OptionData} data
2491+ * @return {import('select2').OptGroupData|import('select2').OptionData|null}
2492+ */
2493+export function textValueMatcher(params, data) {
2494+ // Always return the object if there is nothing to compare
2495+ if (params.term == null || params.term.trim() === '') {
2496+ return data;
2497+ }
2498+
2499+ // Do a recursive check for options with children
2500+ if (data.children && data.children.length > 0) {
2501+ // Clone the data object if there are children
2502+ // This is required as we modify the object to remove any non-matches
2503+ const match = $.extend(true, {}, data);
2504+
2505+ // Check each child of the option
2506+ for (let c = data.children.length - 1; c >= 0; c--) {
2507+ const child = data.children[c];
2508+
2509+ const matches = textValueMatcher(params, child);
2510+
2511+ // If there wasn't a match, remove the object in the array
2512+ if (matches == null) {
2513+ match.children.splice(c, 1);
2514+ }
2515+ }
2516+
2517+ // If any children matched, return the new object
2518+ if (match.children.length > 0) {
2519+ return match;
2520+ }
2521+
2522+ // If there were no matching children, check just the plain object
2523+ return textValueMatcher(params, match);
2524+ }
2525+
2526+ const textMatch = compareIgnoreCaseAndAccents(data.text, params.term, (a, b) => a.indexOf(b) > -1);
2527+ const valueMatch = data.element instanceof HTMLOptionElement && compareIgnoreCaseAndAccents(data.element.value, params.term, (a, b) => a.indexOf(b) > -1);
2528+
2529+ if (textMatch || valueMatch) {
2530+ return data;
2531+ }
2532+
2533+ // If it doesn't contain the term, don't return anything
2534+ return null;
2535+}