From 552dd6ab38ddc2f69b2d37965742e659abd1a9b9 Mon Sep 17 00:00:00 2001 From: Aleksander Grygier Date: Sat, 19 Sep 2026 20:49:26 +0200 Subject: [PATCH] ui : mark favorites with a colored heart in the model list Assisted-by: pi:llama.cpp/DeepSeek-V4.1-Flash --- .../ModelsSelectorDropdown.svelte | 9 ++- .../ModelsSelector/ModelsSelectorList.svelte | 15 ++-- .../ModelsSelectorOption.svelte | 71 +++++++++++++------ 3 files changed, 64 insertions(+), 31 deletions(-) diff --git a/tools/ui/src/lib/components/app/models/ModelsSelector/ModelsSelectorDropdown.svelte b/tools/ui/src/lib/components/app/models/ModelsSelector/ModelsSelectorDropdown.svelte index dd2feb1f35..32834bf6ad 100644 --- a/tools/ui/src/lib/components/app/models/ModelsSelector/ModelsSelectorDropdown.svelte +++ b/tools/ui/src/lib/components/app/models/ModelsSelector/ModelsSelectorDropdown.svelte @@ -323,7 +323,11 @@

{ms.emptyMessage}

{/if} - {#snippet modelOption(item: ModelItem, hideOrgName: boolean)} + {#snippet modelOption( + item: ModelItem, + hideOrgName: boolean, + showFavIndicator: boolean + )} {@const { option } = item} {@const isSelected = currentModel === option.model || ms.activeId === option.id} {@const isHighlighted = option.id === highlightedId} @@ -345,6 +349,7 @@ onSelect={ms.handleSelect} {option} showBaseModelAvatar + {showFavIndicator} /> {/snippet} @@ -358,7 +363,7 @@ onProviderOpen={ms.openProvider} onSelect={ms.handleSelect} renderOption={modelOption} - sectionHeaderClass="[&:not(:first-child)]:mt-3 mb-1 px-2 py-2 text-[13px] font-semibold text-foreground select-none" + sectionHeaderClass="[&:not(:first-child)]:mt-3 mb-1 px-2 py-2 text-[13px] font-semibold text-foreground/80 select-none" {showOrgName} /> diff --git a/tools/ui/src/lib/components/app/models/ModelsSelector/ModelsSelectorList.svelte b/tools/ui/src/lib/components/app/models/ModelsSelector/ModelsSelectorList.svelte index 5deb5b4a98..cff607c8b6 100644 --- a/tools/ui/src/lib/components/app/models/ModelsSelector/ModelsSelectorList.svelte +++ b/tools/ui/src/lib/components/app/models/ModelsSelector/ModelsSelectorList.svelte @@ -16,8 +16,8 @@ sectionHeaderClass?: string; onSelect: (modelId: string) => void; onInfoClick: (modelName: string) => void; - renderOption?: import('svelte').Snippet<[ModelItem, boolean]>; - /** Favorite models of every backend; shown on the favorites tab. */ + renderOption?: import('svelte').Snippet<[ModelItem, boolean, boolean]>; + /** Favorite models of every backend, listed in their own section. */ favorites?: ModelItem[]; /** Show the organization name in every model id of the list. */ showOrgName?: boolean; @@ -62,7 +62,7 @@ } -{#snippet defaultOption(item: ModelItem, hideOrgName: boolean)} +{#snippet defaultOption(item: ModelItem, hideOrgName: boolean, showFavIndicator: boolean)} {@const { option } = item} {@const isSelected = currentModel === option.model || activeId === option.id} {@const isFav = modelsStore.favoriteModelIds.has(option.model)} @@ -78,6 +78,7 @@ {onSelect} {option} showBaseModelAvatar + {showFavIndicator} /> {/snippet} @@ -89,7 +90,7 @@

{#each favorites as item (`fav-${item.option.id}`)} - {@render render(item, !showOrgName)} + {@render render(item, !showOrgName, false)} {/each} {/if} @@ -111,12 +112,12 @@ {#each groups.loaded as item (`loaded-${item.option.id}`)} - {@render render(item, !showOrgName)} + {@render render(item, !showOrgName, true)} {/each} {#each groups.available as group (group.orgName)} {#each group.items as item (item.option.id)} - {@render render(item, !showOrgName)} + {@render render(item, !showOrgName, true)} {/each} {/each} @@ -149,7 +150,7 @@ {#if provider.items.length > 0} {#each provider.items as item (`${provider.backendId}-${item.option.id}`)} - {@render render(item, !showOrgName)} + {@render render(item, !showOrgName, true)} {/each} {#if onProviderOpen && provider.matched > provider.items.length} diff --git a/tools/ui/src/lib/components/app/models/ModelsSelector/ModelsSelectorOption.svelte b/tools/ui/src/lib/components/app/models/ModelsSelector/ModelsSelectorOption.svelte index b667d20d34..d49253aa34 100644 --- a/tools/ui/src/lib/components/app/models/ModelsSelector/ModelsSelectorOption.svelte +++ b/tools/ui/src/lib/components/app/models/ModelsSelector/ModelsSelectorOption.svelte @@ -26,6 +26,9 @@ isSelected: boolean; isHighlighted: boolean; isFav: boolean; + /** Show the filled heart at rest. Off inside the favorites section, where + * every row is a favorite. */ + showFavIndicator?: boolean; hideOrgName?: boolean; onSelect: (modelId: string) => void; onMouseEnter: () => void; @@ -45,7 +48,8 @@ onMouseEnter, onSelect, option, - showBaseModelAvatar = false + showBaseModelAvatar = false, + showFavIndicator = true }: Props = $props(); // row actions follow the backend that serves the row, not the selected one @@ -163,18 +167,53 @@
e.stopPropagation()} > + + {#if isLoaded && onInfoClick} + + + onInfoClick(option.model)} + tooltip="Model information" + tooltipAsTitle + /> + + {/if} + {#if isFav} - modelsStore.toggleFavorite(option.model)} - tooltip="Remove from favorites" - tooltipAsTitle - /> + + + + modelsStore.toggleFavorite(option.model)} + tooltip="Remove from favorites" + tooltipAsTitle + /> + + + + {:else} {/if} - - - {#if isLoaded && onInfoClick} - onInfoClick(option.model)} - tooltip="Model information" - tooltipAsTitle - /> - {/if}
{#if !canLoad}