diff --git a/ui-svelte/src/App.svelte b/ui-svelte/src/App.svelte
index 4ba3b478..895a057f 100644
--- a/ui-svelte/src/App.svelte
+++ b/ui-svelte/src/App.svelte
@@ -13,6 +13,7 @@
import { enableAPIEvents, checkPerformanceEnabled } from "./stores/api";
import { initScreenWidth, initSystemThemeListener, isDarkMode, appTitle, connectionState } from "./stores/theme";
import { currentRoute } from "./stores/route";
+ import { selectedPlaygroundTab, playgroundTabs } from "./stores/playground";
const routes = {
"/": PlaygroundStub,
@@ -31,7 +32,13 @@
"/performance": "Performance",
};
- let sectionTitle = $derived(routeTitles[$currentRoute] ?? "Playground");
+ let sectionTitle = $derived.by(() => {
+ if ($currentRoute === "/") {
+ const tab = playgroundTabs.find((t) => t.id === $selectedPlaygroundTab);
+ return `Playground / ${tab?.label ?? ""}`;
+ }
+ return routeTitles[$currentRoute] ?? "Playground";
+ });
function handleRouteLoaded(event: { detail: { route: string | RegExp } }) {
const route = event.detail.route;
diff --git a/ui-svelte/src/components/AppSidebar.svelte b/ui-svelte/src/components/AppSidebar.svelte
index 3e9f56db..356981f7 100644
--- a/ui-svelte/src/components/AppSidebar.svelte
+++ b/ui-svelte/src/components/AppSidebar.svelte
@@ -1,12 +1,14 @@
+
+
+ {@render children?.()}
+
diff --git a/ui-svelte/src/lib/components/ui/collapsible/collapsible-trigger.svelte b/ui-svelte/src/lib/components/ui/collapsible/collapsible-trigger.svelte
new file mode 100644
index 00000000..5f0f915f
--- /dev/null
+++ b/ui-svelte/src/lib/components/ui/collapsible/collapsible-trigger.svelte
@@ -0,0 +1,19 @@
+
+
+
+ {@render children?.()}
+
diff --git a/ui-svelte/src/lib/components/ui/collapsible/collapsible.svelte b/ui-svelte/src/lib/components/ui/collapsible/collapsible.svelte
new file mode 100644
index 00000000..7ad7e756
--- /dev/null
+++ b/ui-svelte/src/lib/components/ui/collapsible/collapsible.svelte
@@ -0,0 +1,19 @@
+
+
+
+ {@render children?.()}
+
diff --git a/ui-svelte/src/lib/components/ui/collapsible/index.ts b/ui-svelte/src/lib/components/ui/collapsible/index.ts
new file mode 100644
index 00000000..60c0938c
--- /dev/null
+++ b/ui-svelte/src/lib/components/ui/collapsible/index.ts
@@ -0,0 +1,13 @@
+import Root from "./collapsible.svelte";
+import Trigger from "./collapsible-trigger.svelte";
+import Content from "./collapsible-content.svelte";
+
+export {
+ Root,
+ Trigger,
+ Content,
+ //
+ Root as Collapsible,
+ Trigger as CollapsibleTrigger,
+ Content as CollapsibleContent,
+};
diff --git a/ui-svelte/src/lib/components/ui/sidebar/sidebar-menu-button.svelte b/ui-svelte/src/lib/components/ui/sidebar/sidebar-menu-button.svelte
index e0c9c25e..d8e7f78e 100644
--- a/ui-svelte/src/lib/components/ui/sidebar/sidebar-menu-button.svelte
+++ b/ui-svelte/src/lib/components/ui/sidebar/sidebar-menu-button.svelte
@@ -2,7 +2,7 @@
import { tv, type VariantProps } from "tailwind-variants";
export const sidebarMenuButtonVariants = tv({
- base: "ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground data-active:bg-sidebar-accent data-active:text-sidebar-accent-foreground data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground gap-2 rounded-md p-2 text-left text-sm transition-[width,height,padding] group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! focus-visible:ring-2 data-active:font-medium peer/menu-button group/menu-button flex w-full items-center overflow-hidden outline-hidden disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0 [&>span:last-child]:truncate",
+ base: "ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground data-active:font-medium data-active:text-sidebar-accent-foreground data-open:hover:bg-sidebar-accent data-open:hover:text-sidebar-accent-foreground gap-2 rounded-md p-2 text-left text-sm transition-[width,height,padding] group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! focus-visible:ring-2 peer/menu-button group/menu-button flex w-full items-center overflow-hidden outline-hidden disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0 [&>span:last-child]:truncate",
variants: {
variant: {
default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
diff --git a/ui-svelte/src/lib/components/ui/sidebar/sidebar-menu-sub-button.svelte b/ui-svelte/src/lib/components/ui/sidebar/sidebar-menu-sub-button.svelte
index 09ff228f..4a805973 100644
--- a/ui-svelte/src/lib/components/ui/sidebar/sidebar-menu-sub-button.svelte
+++ b/ui-svelte/src/lib/components/ui/sidebar/sidebar-menu-sub-button.svelte
@@ -19,7 +19,7 @@
const mergedProps = $derived({
class: cn(
- "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground data-active:bg-sidebar-accent data-active:text-sidebar-accent-foreground h-7 gap-2 rounded-md px-2 focus-visible:ring-2 data-[size=md]:text-sm data-[size=sm]:text-xs [&>svg]:size-4 flex min-w-0 -translate-x-px items-center overflow-hidden outline-hidden group-data-[collapsible=icon]:hidden disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:shrink-0",
+ "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground data-active:font-medium data-active:text-sidebar-accent-foreground h-7 gap-2 rounded-md px-2 focus-visible:ring-2 data-[size=md]:text-sm data-[size=sm]:text-xs [&>svg]:size-4 flex min-w-0 -translate-x-px items-center overflow-hidden outline-hidden group-data-[collapsible=icon]:hidden disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:shrink-0",
className
),
"data-slot": "sidebar-menu-sub-button",
diff --git a/ui-svelte/src/lib/components/ui/sidebar/sidebar.svelte b/ui-svelte/src/lib/components/ui/sidebar/sidebar.svelte
index 9c90d4f8..dc9bf743 100644
--- a/ui-svelte/src/lib/components/ui/sidebar/sidebar.svelte
+++ b/ui-svelte/src/lib/components/ui/sidebar/sidebar.svelte
@@ -25,7 +25,7 @@
{#if collapsible === "none"}
button]:hidden",
+ "bg-background text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden",
className
)}
style="--sidebar-width: {SIDEBAR_WIDTH_MOBILE};"
@@ -99,7 +99,7 @@
{@render children?.()}
diff --git a/ui-svelte/src/routes/Playground.svelte b/ui-svelte/src/routes/Playground.svelte
index b4ba7b47..edfd2c24 100644
--- a/ui-svelte/src/routes/Playground.svelte
+++ b/ui-svelte/src/routes/Playground.svelte
@@ -1,5 +1,4 @@
-
-
- $selectedTabStore, (v) => selectedTabStore.set(v as Tab)}>
-
- {#each tabs as tab (tab.id)}
- {tab.label}
- {/each}
-
-
-
-
-
+
-
+
-
+
-
+
-
+
-
+
-
diff --git a/ui-svelte/src/stores/playground.ts b/ui-svelte/src/stores/playground.ts
new file mode 100644
index 00000000..3e2b92b3
--- /dev/null
+++ b/ui-svelte/src/stores/playground.ts
@@ -0,0 +1,16 @@
+import { persistentStore } from "./persistent";
+
+export type PlaygroundTab = "chat" | "images" | "speech" | "audio" | "rerank" | "concurrency";
+
+export const playgroundTabs: { id: PlaygroundTab; label: string }[] = [
+ { id: "chat", label: "Chat" },
+ { id: "images", label: "Images" },
+ { id: "speech", label: "Speech" },
+ { id: "audio", label: "Transcription" },
+ { id: "rerank", label: "Rerank" },
+ { id: "concurrency", label: "Load Test" },
+];
+
+export const selectedPlaygroundTab = persistentStore
("playground-selected-tab", "chat");
+
+export const playgroundMenuOpen = persistentStore("playground-menu-open", true);