Files
koboldcpp/tools/ui/src/lib/components/app/models/utils.ts
T
Aleksander Grygier cae63579b6 ui: Improve Chat Form Actions UI/UX (models selector, add panel) (#27746)
* ui : strip trailing container-format segments from parsed model names

* ui : show reasoning and modality icons on model options and search by modality

* ui : keep reasoning submenu visible regardless of model state

* ui : add show-org-name-in-trigger display setting

* ui : move model list into a submenu within the model selector

* ui : make model option hover and focus highlight override the active state

* ui : add raw model id tooltip to model selector options

* feat: Enable microphone input as default for audio models

* ui : fix eslint issues in chat form and model selector

* ui: show modality icons instead of file submenu in chat add menu

Assisted-by: pi

* chore: Format

* chore: Format

* ui: add ModelCapability enum and shared modality/capability icon constants

Assisted by: pi:GLM-5.3-Flash

* ui: derive modality badge icons and labels from shared constants

Assisted by: pi:GLM-5.3-Flash

* ui: split model option icons into capabilities and modalities

Replace the supportsThinking flag on ModelId with a capabilities object
keyed like ModelModalities, so future capabilities (tool calls, etc.)
slot in alongside reasoning. Icons and labels now come from the shared
CAPABILITY_ICONS/MODALITY_ICONS constants.

Assisted by: pi:GLM-5.3-Flash
2026-08-27 14:47:36 +02:00

102 lines
2.7 KiB
TypeScript

import { ModelModality } from '$lib/enums';
import type { ModelOption } from '$lib/types/models';
import { SvelteMap } from 'svelte/reactivity';
export interface ModelItem {
option: ModelOption;
flatIndex: number;
}
export interface OrgGroup {
orgName: string | null;
items: ModelItem[];
}
export interface GroupedModelOptions {
loaded: ModelItem[];
favorites: ModelItem[];
available: OrgGroup[];
}
function matchesModality(option: ModelOption, term: string): boolean {
const modalities = option.modalities;
if (!modalities) return false;
switch (term) {
case ModelModality.VISION.toLowerCase():
return modalities.vision;
case ModelModality.AUDIO.toLowerCase():
return modalities.audio;
case ModelModality.VIDEO.toLowerCase():
return modalities.video;
default:
return false;
}
}
export function filterModelOptions(options: ModelOption[], searchTerm: string): ModelOption[] {
const term = searchTerm.trim().toLowerCase();
if (!term) return options;
return options.filter(
(option) =>
option.model.toLowerCase().includes(term) ||
option.name?.toLowerCase().includes(term) ||
option.aliases?.some((alias: string) => alias.toLowerCase().includes(term)) ||
option.tags?.some((tag: string) => tag.toLowerCase().includes(term)) ||
matchesModality(option, term)
);
}
export function groupModelOptions(
filteredOptions: ModelOption[],
favoriteIds: Set<string>,
isModelLoaded: (model: string) => boolean
): GroupedModelOptions {
// Loaded models
const loaded: ModelItem[] = [];
for (let i = 0; i < filteredOptions.length; i++) {
if (isModelLoaded(filteredOptions[i].model)) {
loaded.push({ flatIndex: i, option: filteredOptions[i] });
}
}
// Favorites (excluding loaded)
const loadedModelIds = new Set(loaded.map((item) => item.option.model));
const favorites: ModelItem[] = [];
for (let i = 0; i < filteredOptions.length; i++) {
if (
favoriteIds.has(filteredOptions[i].model) &&
!loadedModelIds.has(filteredOptions[i].model)
) {
favorites.push({ flatIndex: i, option: filteredOptions[i] });
}
}
// Available models grouped by org (excluding loaded and favorites)
const available: OrgGroup[] = [];
const orgGroups = new SvelteMap<string, ModelItem[]>();
for (let i = 0; i < filteredOptions.length; i++) {
const option = filteredOptions[i];
if (loadedModelIds.has(option.model) || favoriteIds.has(option.model)) continue;
const key = option.parsedId?.orgName ?? '';
if (!orgGroups.has(key)) orgGroups.set(key, []);
orgGroups.get(key)!.push({ flatIndex: i, option });
}
for (const [orgName, items] of orgGroups) {
available.push({ items, orgName: orgName || null });
}
return { available, favorites, loaded };
}