diff --git a/tools/ui/src/lib/components/app/models/ModelsManager/ModelsManagerModelsTable.svelte b/tools/ui/src/lib/components/app/models/ModelsManager/ModelsManagerModelsTable.svelte index 08ec980135..3440ff9898 100644 --- a/tools/ui/src/lib/components/app/models/ModelsManager/ModelsManagerModelsTable.svelte +++ b/tools/ui/src/lib/components/app/models/ModelsManager/ModelsManagerModelsTable.svelte @@ -19,6 +19,7 @@ Upload } from '@lucide/svelte'; import { + ActionIcon, DropdownMenuActions, Logo, ModelAvatar, @@ -346,10 +347,12 @@ {#snippet familyRow(family: ModelFamilyGroup)} {@const isExpanded = !collapsedFamilies.has(family.key)} {@const countLabel = `${family.entries.length} model${family.entries.length === 1 ? '' : 's'}`} + {@const options = family.entries.flatMap((entry) => entry.quants)} + {@const favorite = options.some((option) => isFavorite(option))}
toggleFamily(family.key)} onkeydown={(event) => event.key === 'Enter' && toggleFamily(family.key)} role="button" @@ -366,6 +369,41 @@ {family.label} {countLabel} + + event.stopPropagation()} + onkeydown={(event) => event.stopPropagation()} + role="presentation" + > + {#if favorite} + + modelsStore.setFavorites( + options.map((option) => option.model), + false + )} + tooltip="Remove family from favorites" + tooltipAsTitle + /> + {:else} + + modelsStore.setFavorites( + options.map((option) => option.model), + true + )} + tooltip="Add family to favorites" + tooltipAsTitle + /> + {/if} + diff --git a/tools/ui/src/lib/stores/models/index.svelte.ts b/tools/ui/src/lib/stores/models/index.svelte.ts index fd0e3d1a8c..6f1102091c 100644 --- a/tools/ui/src/lib/stores/models/index.svelte.ts +++ b/tools/ui/src/lib/stores/models/index.svelte.ts @@ -518,6 +518,27 @@ class ModelsStore implements ModelPropsHost, ModelStatusHost { } } + /** Add or remove several models at once, e.g. every quant of a family. */ + setFavorites(modelIds: string[], favorite: boolean): void { + const next = new SvelteSet(this.favoriteModelIds); + + for (const modelId of modelIds) { + if (favorite) { + next.add(modelId); + } else { + next.delete(modelId); + } + } + + this.favoriteModelIds = next; + + try { + localStorage.setItem(FAVORITE_MODELS_LOCALSTORAGE_KEY, JSON.stringify([...next])); + } catch { + toast.error('Failed to save favorite models to local storage'); + } + } + /** * Activate a backend for the selector tabs. Everything comes from memory: * the model list and router rows are prefetched at startup and the local @@ -571,21 +592,7 @@ class ModelsStore implements ModelPropsHost, ModelStatusHost { } toggleFavorite(modelId: string): void { - const next = new SvelteSet(this.favoriteModelIds); - - if (next.has(modelId)) { - next.delete(modelId); - } else { - next.add(modelId); - } - - this.favoriteModelIds = next; - - try { - localStorage.setItem(FAVORITE_MODELS_LOCALSTORAGE_KEY, JSON.stringify([...next])); - } catch { - toast.error('Failed to save favorite models to local storage'); - } + this.setFavorites([modelId], !this.favoriteModelIds.has(modelId)); } /** Models hidden from the selector stay in the manager, flagged and unhideable. */