diff --git a/tools/ui/src/lib/components/app/misc/index.ts b/tools/ui/src/lib/components/app/misc/index.ts
index e695f242e4..9c48eadc3d 100644
--- a/tools/ui/src/lib/components/app/misc/index.ts
+++ b/tools/ui/src/lib/components/app/misc/index.ts
@@ -59,6 +59,7 @@ export { default as CodeBlockActions } from './CodeBlockActions.svelte';
* `class` and `style` props and inherits color via `currentColor`.
*/
export { default as Logo } from './Logo.svelte';
+
/**
* **CollapsibleSection** - Section with a trigger that expands in place
*
diff --git a/tools/ui/src/lib/components/app/models/ModelCapabilities.svelte b/tools/ui/src/lib/components/app/models/ModelCapabilities.svelte
index 67cccade4c..295947da3c 100644
--- a/tools/ui/src/lib/components/app/models/ModelCapabilities.svelte
+++ b/tools/ui/src/lib/components/app/models/ModelCapabilities.svelte
@@ -1,5 +1,6 @@
- {#if ms.loading && ms.options.length === 0 && ms.isRouter}
+ {#if ms.loading && ms.options.length === 0 && ms.isMultiModel}
Loading models…
- {:else if ms.options.length === 0 && ms.isRouter}
+ {:else if ms.options.length === 0 && ms.isMultiModel}
{#if currentModel}
-
+
{:else}
-
No models available.
+
+ No models yet. Add a backend to get started.
+
{/if}
{:else}
- {@const selectedOption = ms.getDisplayOption()}
- {@const triggerModel = selectedOption?.model}
{@const triggerStatus = triggerModel
? modelsStore.routerModels.find((m) => m.id === triggerModel)?.status?.value
: undefined}
@@ -224,7 +252,7 @@
? Math.round(modelLoadFraction(modelsStore.status.getLoadProgress(triggerModel)) * 100)
: 0}
- {#if ms.isRouter}
+ {#if ms.isMultiModel}
@@ -234,35 +262,36 @@
{...props}
class={[
`relative inline-grid cursor-pointer grid-cols-[1fr_auto_1fr] items-center gap-1 rounded-sm bg-background px-1.5 py-1 text-xs shadow-sm transition hover:bg-muted-foreground/20 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-60 dark:bg-muted-foreground/15 dark:text-secondary-foreground`,
- !ms.isCurrentModelInCache
- ? 'bg-red-400/10 !text-red-400 hover:bg-red-400/20 hover:text-red-400'
- : forceForegroundText
- ? 'text-foreground'
- : ms.isHighlightedCurrentModelActive
+ error
+ ? 'border-destructive/40 bg-destructive/10 !text-destructive hover:bg-destructive/20'
+ : !ms.isCurrentModelInCache
+ ? 'bg-red-400/10 !text-red-400 hover:bg-red-400/20 hover:text-red-400'
+ : forceForegroundText
? 'text-foreground'
- : 'text-foreground',
+ : ms.isHighlightedCurrentModelActive
+ ? 'text-foreground'
+ : 'text-foreground',
isOpen && 'text-foreground',
'max-w-[min(calc(100vw-4rem) md:max-w-[min(calc(100cqw-9rem),25rem)]'
]}
disabled={disabled || ms.updating}
>
-
+
-
+
{#if selectedOption}
{:else}
Select model
{/if}
-
- {#if reasoning.isReasoningActive}
-
- {/if}
{#if ms.updating || ms.isLoadingModel}
@@ -287,94 +316,98 @@
event.preventDefault()}
>
-
-
-
-
- {#if selectedOption}
-
- {:else}
- No model
- {/if}
-
-
-
- ms.setSearchTerm(v)}
- onSearchKeyDown={handleSearchKeyDown}
- placeholder="Search models..."
- searchValue={ms.searchTerm}
- >
-
- {#if !ms.isCurrentModelInCache && currentModel}
-
-
-
-
- (not available)
-
- {/if}
-
- {#if ms.filteredOptions.length === 0}
-
No models found.
- {/if}
-
- {#snippet modelOption(item: ModelItem, hideOrgName: boolean)}
- {@const { option } = item}
- {@const isSelected = currentModel === option.model || ms.activeId === option.id}
- {@const isHighlighted = option.id === highlightedId}
- {@const isFav = ms.isFavorite(option.model)}
-
-
{
- if (event.key === KeyboardKey.ENTER || event.key === KeyboardKey.SPACE) {
- event.preventDefault();
- void handleModelKeyAction(option.id, event.altKey);
- }
- }}
- onMouseEnter={() => (highlightedId = option.id)}
- onSelect={ms.handleSelect}
- {option}
- />
- {/snippet}
-
- ms.setSearchTerm(v)}
+ onSearchKeyDown={handleSearchKeyDown}
+ placeholder="Search models..."
+ searchClass="bg-transparent"
+ searchValue={ms.searchTerm}
+ >
+
+
+ {#if !ms.isCurrentModelInCache && currentModel}
+
+
+
-
-
-
-
-
+ (not available)
+
+ {/if}
+
+ {#if ms.isEmpty}
+ {ms.emptyMessage}
+ {/if}
+
+ {#snippet modelOption(item: ModelItem, hideOrgName: boolean)}
+ {@const { option } = item}
+ {@const isSelected = currentModel === option.model || ms.activeId === option.id}
+ {@const isHighlighted = option.id === highlightedId}
+ {@const isFav = ms.isFavorite(option.model)}
+
+ {
+ if (event.key === KeyboardKey.ENTER || event.key === KeyboardKey.SPACE) {
+ event.preventDefault();
+ void handleModelKeyAction(option.id, event.altKey);
+ }
+ }}
+ onMouseEnter={() => (highlightedId = option.id)}
+ onSelect={ms.handleSelect}
+ {option}
+ showBaseModelAvatar={!settingsStore.config.groupModelsByFamily}
+ showRepoOrgAvatar={settingsStore.config.groupModelsByFamily}
+ />
+ {/snippet}
+
+
+
+
+ {#snippet footer()}
+
+
+
+
+
+ Manage models
+
+
+ {/snippet}
+
{:else}
@@ -387,7 +420,7 @@
class={[
`inline-flex cursor-pointer items-center gap-1.5 rounded-sm bg-background px-1.5 py-1 text-xs shadow-sm transition hover:bg-muted-foreground/20 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-60 dark:bg-muted-foreground/15 dark:text-secondary-foreground`,
!ms.isCurrentModelInCache
- ? 'bg-red-400/10 !text-red-400 hover:bg-red-400/20 hover:text-red-400'
+ ? 'bg-red-400/10 text-red-400! hover:bg-red-400/20 hover:text-red-400'
: forceForegroundText
? 'text-foreground'
: ms.isHighlightedCurrentModelActive
@@ -399,21 +432,20 @@
onclick={() => ms.handleOpenChange(true)}
style="max-width: min(calc(100cqw - 6.5rem), 32rem)"
>
-
+
{#if selectedOption}
{/if}
- {#if reasoning.isReasoningActive}
-
- {/if}
-
{#if ms.updating}
{/if}
@@ -423,18 +455,15 @@
{#if selectedOption}
- {triggerTooltipLabel(selectedOption)}
-
+
{triggerTooltipLabel(selectedOption)}
+
{/if}
{/if}
{/if}
-{#if ms.showModelDialog}
-