From 136dcdc25f82edd77c76d5b3d4b68a00001efbb9 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 27 Jun 2026 11:49:16 +0000 Subject: [PATCH] ui: migrate Models panel and Playground to shadcn - ModelsPanel uses Card, Button, Badge and a dropdown menu for actions - Playground uses shadcn Tabs for the switcher while keeping every interface mounted to preserve state Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01UmuGqwNBJNEAMaWsdCDqUC --- ui-svelte/src/components/ModelsPanel.svelte | 208 +++++++++----------- ui-svelte/src/routes/Playground.svelte | 97 ++------- 2 files changed, 113 insertions(+), 192 deletions(-) diff --git a/ui-svelte/src/components/ModelsPanel.svelte b/ui-svelte/src/components/ModelsPanel.svelte index 7c884391..f0fb3f3c 100644 --- a/ui-svelte/src/components/ModelsPanel.svelte +++ b/ui-svelte/src/components/ModelsPanel.svelte @@ -1,11 +1,15 @@ -
-
-
-

Models

+ + +
+ Models + {#if $isNarrow} -
- - {#if menuOpen} -
- - - -
- {/if} + + + {#snippet child({ props })} + + {/snippet} + + + + + {$showIdorNameStore === "id" ? "Show Name" : "Show ID"} + + + {#if $showUnlistedStore}{:else}{/if} + {$showUnlistedStore ? "Hide Unlisted" : "Show Unlisted"} + + + + + {isUnloading ? "Unloading..." : "Unload All"} + + + + {:else} +
+ + +
{/if}
- {#if !$isNarrow} -
-
- + - -
- -
- {/if} -
- -
- - - - - - + +
{$showIdorNameStore === "id" ? "Model ID" : "Name"}State
+ + + + + {#each filteredModels.regularModels as model (model.id)} - - + - - @@ -212,19 +188,19 @@
{$showIdorNameStore === "id" ? "Model ID" : "Name"}State
- +
+ {getModelDisplay(model)} {#if model.description} -

{model.description}

+

{model.description}

{/if} {#if model.aliases && model.aliases.length > 0} -

Aliases: {model.aliases.join(", ")}

+

Aliases: {model.aliases.join(", ")}

{/if}
+ {#if model.state === "stopped" && pendingLoads[model.id]} - + {:else if model.state === "stopped"} - + {:else} - + {/if} + {#if model.state === "stopped" && pendingLoads[model.id]} - queued + queued {:else} - {model.state} + {model.state} {/if}
{#if Object.keys(filteredModels.peerModelsByPeerId).length > 0} -

Peer Models

+

Peer Models

{#each Object.entries(filteredModels.peerModelsByPeerId).sort(([a], [b]) => a.localeCompare(b)) as [peerId, peerModels] (peerId)}
- - - - +
{peerId}
+ + + {#each peerModels as model (model.id)} - - + @@ -234,5 +210,5 @@ {/each} {/if} - - + + diff --git a/ui-svelte/src/routes/Playground.svelte b/ui-svelte/src/routes/Playground.svelte index c3375311..b4ba7b47 100644 --- a/ui-svelte/src/routes/Playground.svelte +++ b/ui-svelte/src/routes/Playground.svelte @@ -6,11 +6,12 @@ import SpeechInterface from "../components/playground/SpeechInterface.svelte"; import RerankInterface from "../components/playground/RerankInterface.svelte"; import ConcurrencyInterface from "../components/playground/ConcurrencyInterface.svelte"; + import * as Card from "$lib/components/ui/card/index.js"; + import * as Tabs from "$lib/components/ui/tabs/index.js"; type Tab = "chat" | "images" | "speech" | "audio" | "rerank" | "concurrency"; const selectedTabStore = persistentStore("playground-selected-tab", "chat"); - let mobileMenuOpen = $state(false); const tabs: { id: Tab; label: string }[] = [ { id: "chat", label: "Chat" }, @@ -20,95 +21,39 @@ { id: "rerank", label: "Rerank" }, { id: "concurrency", label: "Load Test" }, ]; - - function selectTab(tab: Tab) { - selectedTabStore.set(tab); - mobileMenuOpen = false; - } - - function getTabLabel(tabId: Tab): string { - return tabs.find((t) => t.id === tabId)?.label || ""; - } -
- -
- -
- - {#if mobileMenuOpen} -
- {#each tabs as tab (tab.id)} - - {/each} -
- {/if} -
- - - + + +
+ $selectedTabStore, (v) => selectedTabStore.set(v as Tab)}> + + {#each tabs as tab (tab.id)} + {tab.label} + {/each} + +
- -
-
+ +
+
-
+
-
+
-
+
-
+
-
+
-
- - +
{peerId}
+
{model.id}