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 @@
{#if !canLoad}