ui: reorganize sidebar and add Settings page

Reorder sidebar menu to Activity, Playground, Models, Logs. Remove the
ll header icon and replace it with the connection status indicator
moved from the footer. Add a Settings page (gear icon) at the bottom
that surfaces the build information that was previously hidden behind
the status indicator's tooltip.

- move ConnectionStatus into Sidebar.Header, drop build-info tooltip
- add Settings.svelte route showing version/commit/build date
- register /settings route and title in App.svelte
This commit is contained in:
Benson Wong
2026-06-28 03:53:14 +00:00
parent d207a059a4
commit a0578f0007
4 changed files with 113 additions and 75 deletions
+3
View File
@@ -9,6 +9,7 @@
import Performance from "./routes/Performance.svelte"; import Performance from "./routes/Performance.svelte";
import Playground from "./routes/Playground.svelte"; import Playground from "./routes/Playground.svelte";
import PlaygroundStub from "./routes/PlaygroundStub.svelte"; import PlaygroundStub from "./routes/PlaygroundStub.svelte";
import Settings from "./routes/Settings.svelte";
import * as Sidebar from "$lib/components/ui/sidebar/index.js"; import * as Sidebar from "$lib/components/ui/sidebar/index.js";
import * as Tooltip from "$lib/components/ui/tooltip/index.js"; import * as Tooltip from "$lib/components/ui/tooltip/index.js";
import { Separator } from "$lib/components/ui/separator/index.js"; import { Separator } from "$lib/components/ui/separator/index.js";
@@ -23,6 +24,7 @@
"/models/:id": ModelDetail, "/models/:id": ModelDetail,
"/logs": LogViewer, "/logs": LogViewer,
"/activity": Activity, "/activity": Activity,
"/settings": Settings,
"/performance": Performance, "/performance": Performance,
"*": PlaygroundStub, "*": PlaygroundStub,
}; };
@@ -32,6 +34,7 @@
"/models": "Models", "/models": "Models",
"/activity": "Activity", "/activity": "Activity",
"/logs": "Logs", "/logs": "Logs",
"/settings": "Settings",
"/performance": "Performance", "/performance": "Performance",
}; };
+77 -71
View File
@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { link } from "svelte-spa-router"; import { link } from "svelte-spa-router";
import { House, Boxes, Activity, ScrollText, Gauge, Sun, Moon, Monitor, ChevronRight } from "@lucide/svelte"; import { House, Boxes, Activity, ScrollText, Gauge, Sun, Moon, Monitor, ChevronRight, Settings } from "@lucide/svelte";
import * as Sidebar from "$lib/components/ui/sidebar/index.js"; import * as Sidebar from "$lib/components/ui/sidebar/index.js";
import * as Collapsible from "$lib/components/ui/collapsible/index.js"; import * as Collapsible from "$lib/components/ui/collapsible/index.js";
import { Button } from "$lib/components/ui/button/index.js"; import { Button } from "$lib/components/ui/button/index.js";
@@ -53,10 +53,8 @@
<Sidebar.Root collapsible="icon"> <Sidebar.Root collapsible="icon">
<Sidebar.Header> <Sidebar.Header>
<div class="flex items-center gap-2 px-2 py-1.5"> <div class="flex items-center gap-2 px-2 py-1.5">
<div <div class="flex shrink-0 items-center justify-center">
class="bg-primary text-primary-foreground flex aspect-square size-8 shrink-0 items-center justify-center rounded-lg font-bold" <ConnectionStatus />
>
ll
</div> </div>
<h1 <h1
contenteditable="true" contenteditable="true"
@@ -74,61 +72,14 @@
<Sidebar.GroupContent> <Sidebar.GroupContent>
<Sidebar.Menu class="gap-1"> <Sidebar.Menu class="gap-1">
<Sidebar.MenuItem> <Sidebar.MenuItem>
<Collapsible.Root <Sidebar.MenuButton isActive={isActive("/activity", $currentRoute)} tooltipContent="Activity">
open={$modelsMenuOpen} {#snippet child({ props })}
onOpenChange={(v) => modelsMenuOpen.set(v)} <a href="/activity" use:link {...props}>
class="gap-0" <Activity />
> <span>Activity</span>
<Sidebar.MenuButton </a>
isActive={$currentRoute.startsWith("/models")} {/snippet}
tooltipContent="Models" </Sidebar.MenuButton>
>
{#snippet child({ props })}
<a href="/models" use:link {...props}>
<Boxes />
<span>Models</span>
<span
class="ml-auto transition-transform duration-200 {$modelsMenuOpen ? 'rotate-90' : ''}"
role="button"
tabindex="0"
aria-label="Toggle models section"
onclick={(e) => {
e.preventDefault();
e.stopPropagation();
modelsMenuOpen.update((v) => !v);
}}
onkeydown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
e.stopPropagation();
modelsMenuOpen.update((v) => !v);
}
}}
>
<ChevronRight />
</span>
</a>
{/snippet}
</Sidebar.MenuButton>
<Collapsible.Content>
<Sidebar.MenuSub>
{#each $models as model (model.id)}
<Sidebar.MenuSubItem>
<Sidebar.MenuSubButton
isActive={$currentRoute === `/models/${encodeURIComponent(model.id)}`}
>
{#snippet child({ props })}
<a href="/models/{encodeURIComponent(model.id)}" use:link {...props}>
<span class={`size-2 shrink-0 rounded-full ${dotClass[statusDotColor(model)]}`}></span>
<span class="flex-1 truncate">{model.id}</span>
</a>
{/snippet}
</Sidebar.MenuSubButton>
</Sidebar.MenuSubItem>
{/each}
</Sidebar.MenuSub>
</Collapsible.Content>
</Collapsible.Root>
</Sidebar.MenuItem> </Sidebar.MenuItem>
<Sidebar.MenuItem> <Sidebar.MenuItem>
@@ -194,14 +145,61 @@
</Sidebar.MenuItem> </Sidebar.MenuItem>
<Sidebar.MenuItem> <Sidebar.MenuItem>
<Sidebar.MenuButton isActive={isActive("/activity", $currentRoute)} tooltipContent="Activity"> <Collapsible.Root
{#snippet child({ props })} open={$modelsMenuOpen}
<a href="/activity" use:link {...props}> onOpenChange={(v) => modelsMenuOpen.set(v)}
<Activity /> class="gap-0"
<span>Activity</span> >
</a> <Sidebar.MenuButton
{/snippet} isActive={$currentRoute.startsWith("/models")}
</Sidebar.MenuButton> tooltipContent="Models"
>
{#snippet child({ props })}
<a href="/models" use:link {...props}>
<Boxes />
<span>Models</span>
<span
class="ml-auto transition-transform duration-200 {$modelsMenuOpen ? 'rotate-90' : ''}"
role="button"
tabindex="0"
aria-label="Toggle models section"
onclick={(e) => {
e.preventDefault();
e.stopPropagation();
modelsMenuOpen.update((v) => !v);
}}
onkeydown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
e.stopPropagation();
modelsMenuOpen.update((v) => !v);
}
}}
>
<ChevronRight />
</span>
</a>
{/snippet}
</Sidebar.MenuButton>
<Collapsible.Content>
<Sidebar.MenuSub>
{#each $models as model (model.id)}
<Sidebar.MenuSubItem>
<Sidebar.MenuSubButton
isActive={$currentRoute === `/models/${encodeURIComponent(model.id)}`}
>
{#snippet child({ props })}
<a href="/models/{encodeURIComponent(model.id)}" use:link {...props}>
<span class={`size-2 shrink-0 rounded-full ${dotClass[statusDotColor(model)]}`}></span>
<span class="flex-1 truncate">{model.id}</span>
</a>
{/snippet}
</Sidebar.MenuSubButton>
</Sidebar.MenuSubItem>
{/each}
</Sidebar.MenuSub>
</Collapsible.Content>
</Collapsible.Root>
</Sidebar.MenuItem> </Sidebar.MenuItem>
<Sidebar.MenuItem> <Sidebar.MenuItem>
@@ -236,9 +234,17 @@
<div <div
class="flex items-center justify-between gap-2 px-1 group-data-[collapsible=icon]:flex-col-reverse" class="flex items-center justify-between gap-2 px-1 group-data-[collapsible=icon]:flex-col-reverse"
> >
<div class="flex items-center gap-2 px-1"> <Sidebar.MenuButton
<ConnectionStatus /> isActive={isActive("/settings", $currentRoute)}
</div> tooltipContent="Settings"
>
{#snippet child({ props })}
<a href="/settings" use:link {...props}>
<Settings />
<span>Settings</span>
</a>
{/snippet}
</Sidebar.MenuButton>
<Button <Button
variant="ghost" variant="ghost"
size="icon" size="icon"
@@ -1,6 +1,5 @@
<script lang="ts"> <script lang="ts">
import { connectionState } from "../stores/theme"; import { connectionState } from "../stores/theme";
import { versionInfo } from "../stores/api";
let eventStatusColor = $derived.by(() => { let eventStatusColor = $derived.by(() => {
switch ($connectionState) { switch ($connectionState) {
@@ -14,9 +13,7 @@
} }
}); });
let tooltipText = $derived( let tooltipText = $derived(`Event Stream: ${$connectionState ?? "unknown"}`);
`Event Stream: ${$connectionState ?? "unknown"}\nAPI Version: ${$versionInfo?.version ?? "unknown"}\nCommit Hash: ${$versionInfo?.commit?.substring(0, 7) ?? "unknown"}\nBuild Date: ${$versionInfo?.build_date ?? "unknown"}`
);
</script> </script>
<div class="flex items-center" title={tooltipText}> <div class="flex items-center" title={tooltipText}>
+32
View File
@@ -0,0 +1,32 @@
<script lang="ts">
import { connectionState } from "../stores/theme";
import { versionInfo } from "../stores/api";
</script>
<div class="p-2">
<div class="mt-4 mb-4">
<h3 class="text-lg font-semibold">Settings</h3>
</div>
<div class="rounded-lg border p-4 space-y-2 max-w-md">
<h4 class="text-sm font-semibold text-muted-foreground">Build Information</h4>
<dl class="text-sm space-y-1">
<div class="flex justify-between gap-4">
<dt class="text-muted-foreground">Event Stream</dt>
<dd class="font-medium">{$connectionState ?? "unknown"}</dd>
</div>
<div class="flex justify-between gap-4">
<dt class="text-muted-foreground">API Version</dt>
<dd class="font-medium">{$versionInfo?.version ?? "unknown"}</dd>
</div>
<div class="flex justify-between gap-4">
<dt class="text-muted-foreground">Commit Hash</dt>
<dd class="font-medium">{$versionInfo?.commit?.substring(0, 7) ?? "unknown"}</dd>
</div>
<div class="flex justify-between gap-4">
<dt class="text-muted-foreground">Build Date</dt>
<dd class="font-medium">{$versionInfo?.build_date ?? "unknown"}</dd>
</div>
</dl>
</div>
</div>