|
@@ -0,0 +1,152 @@
|
|
|
|
|
+<script lang="ts">
|
|
|
|
|
+ import {
|
|
|
|
|
+ BookSearch,
|
|
|
|
|
+ Bot,
|
|
|
|
|
+ BotMessageSquare,
|
|
|
|
|
+ Brain,
|
|
|
|
|
+ BrainCircuit,
|
|
|
|
|
+ ChevronDown,
|
|
|
|
|
+ ChevronUp,
|
|
|
|
|
+ Component,
|
|
|
|
|
+ Key,
|
|
|
|
|
+ LibraryBig,
|
|
|
|
|
+ Toolbox,
|
|
|
|
|
+ Wrench,
|
|
|
|
|
+ } from '@lucide/svelte';
|
|
|
|
|
+
|
|
|
|
|
+ import { goto } from '$app/navigation';
|
|
|
|
|
+ import { resolve } from '$app/paths';
|
|
|
|
|
+ import { page } from '$app/state';
|
|
|
|
|
+ import type { ResolvedPathname } from '$app/types';
|
|
|
|
|
+
|
|
|
|
|
+ import * as DropdownMenu from '$lib/components/controls/dropdown-menu';
|
|
|
|
|
+ import * as Sidebar from '$lib/components/controls/sidebar';
|
|
|
|
|
+ import type { User } from '$lib/types/user';
|
|
|
|
|
+ import type { Workspace } from '$lib/types/workspace';
|
|
|
|
|
+
|
|
|
|
|
+ interface NavItem {
|
|
|
|
|
+ label: string;
|
|
|
|
|
+ icon: typeof Bot;
|
|
|
|
|
+ href: ResolvedPathname;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ interface NavItemGroup {
|
|
|
|
|
+ label: string;
|
|
|
|
|
+ items: NavItem[];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let {
|
|
|
|
|
+ workspaceId,
|
|
|
|
|
+ workspaces,
|
|
|
|
|
+ user,
|
|
|
|
|
+ }: { workspaceId: string | null; workspaces: Workspace[]; user: User | undefined } = $props();
|
|
|
|
|
+ const workspaceName = $derived(
|
|
|
|
|
+ workspaces.find((ws) => ws.id === workspaceId)?.displayName ?? 'Unknown Workspace',
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ const navItemGroups: NavItemGroup[] = [
|
|
|
|
|
+ {
|
|
|
|
|
+ label: 'Utilities',
|
|
|
|
|
+ items: [
|
|
|
|
|
+ { label: 'Agent Chat', icon: BotMessageSquare, href: resolve('/utilities/testing/agent') },
|
|
|
|
|
+ { label: 'Retrieval', icon: BookSearch, href: resolve('/utilities/testing/kb') },
|
|
|
|
|
+ { label: 'Tool Calling', icon: Wrench, href: resolve('/utilities/testing/tool') },
|
|
|
|
|
+ { label: 'Model Inference', icon: BrainCircuit, href: resolve('/utilities/testing/model') },
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ label: 'Workloads',
|
|
|
|
|
+ items: [
|
|
|
|
|
+ { label: 'Agents', icon: Bot, href: resolve('/workloads/agents') },
|
|
|
|
|
+ { label: 'Knowledge Bases', icon: LibraryBig, href: resolve('/workloads/kbs') },
|
|
|
|
|
+ { label: 'Tools', icon: Toolbox, href: resolve('/workloads/tools') },
|
|
|
|
|
+ { label: 'Models', icon: Brain, href: resolve('/workloads/models') },
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ label: 'Resources',
|
|
|
|
|
+ items: [
|
|
|
|
|
+ { label: 'Secrets', icon: Key, href: resolve('/resources/secrets') },
|
|
|
|
|
+ { label: 'Stacks', icon: Component, href: resolve('/resources/stacks') },
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ const isActive = (href: string) =>
|
|
|
|
|
+ href === '/' ? page.url.pathname === '/' : page.url.pathname.startsWith(href);
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<Sidebar.Root class="border-white/10 backdrop-blur-md" collapsible="icon">
|
|
|
|
|
+ <Sidebar.Header class="group-data-[collapsible=icon]:hidden">
|
|
|
|
|
+ <Sidebar.Menu>
|
|
|
|
|
+ <Sidebar.MenuItem>
|
|
|
|
|
+ <DropdownMenu.Root>
|
|
|
|
|
+ <DropdownMenu.Trigger>
|
|
|
|
|
+ {#snippet child({ props })}
|
|
|
|
|
+ <Sidebar.MenuButton class="whitespace-nowrap" {...props}>
|
|
|
|
|
+ {workspaceName}
|
|
|
|
|
+ <ChevronDown class="ms-auto" />
|
|
|
|
|
+ </Sidebar.MenuButton>
|
|
|
|
|
+ {/snippet}
|
|
|
|
|
+ </DropdownMenu.Trigger>
|
|
|
|
|
+ <DropdownMenu.Content class="w-(--bits-dropdown-menu-anchor-width)">
|
|
|
|
|
+ {#each workspaces as workspace (workspace.id)}
|
|
|
|
|
+ <DropdownMenu.Item>
|
|
|
|
|
+ <span>{workspace.displayName}</span>
|
|
|
|
|
+ </DropdownMenu.Item>
|
|
|
|
|
+ {/each}
|
|
|
|
|
+ </DropdownMenu.Content>
|
|
|
|
|
+ </DropdownMenu.Root>
|
|
|
|
|
+ </Sidebar.MenuItem>
|
|
|
|
|
+ </Sidebar.Menu>
|
|
|
|
|
+ </Sidebar.Header>
|
|
|
|
|
+ <Sidebar.Content>
|
|
|
|
|
+ {#each navItemGroups as group (group.label)}
|
|
|
|
|
+ <Sidebar.Group>
|
|
|
|
|
+ <Sidebar.GroupLabel>{group.label}</Sidebar.GroupLabel>
|
|
|
|
|
+ <Sidebar.GroupContent>
|
|
|
|
|
+ <Sidebar.Menu>
|
|
|
|
|
+ {#each group.items as item (item.href)}
|
|
|
|
|
+ <Sidebar.MenuItem>
|
|
|
|
|
+ <Sidebar.MenuButton isActive={isActive(item.href)} tooltipContent={item.label}>
|
|
|
|
|
+ {#snippet child({ props })}
|
|
|
|
|
+ <a href={item.href} {...props}>
|
|
|
|
|
+ <item.icon strokeWidth={3} />
|
|
|
|
|
+ <span>{item.label}</span>
|
|
|
|
|
+ </a>
|
|
|
|
|
+ {/snippet}
|
|
|
|
|
+ </Sidebar.MenuButton>
|
|
|
|
|
+ </Sidebar.MenuItem>
|
|
|
|
|
+ {/each}
|
|
|
|
|
+ </Sidebar.Menu>
|
|
|
|
|
+ </Sidebar.GroupContent>
|
|
|
|
|
+ </Sidebar.Group>
|
|
|
|
|
+ {/each}
|
|
|
|
|
+ </Sidebar.Content>
|
|
|
|
|
+ {#if user}
|
|
|
|
|
+ <Sidebar.Footer class="group-data-[collapsible=icon]:hidden">
|
|
|
|
|
+ <Sidebar.Menu>
|
|
|
|
|
+ <Sidebar.MenuItem>
|
|
|
|
|
+ <DropdownMenu.Root>
|
|
|
|
|
+ <DropdownMenu.Trigger>
|
|
|
|
|
+ {#snippet child({ props })}
|
|
|
|
|
+ <Sidebar.MenuButton
|
|
|
|
|
+ {...props}
|
|
|
|
|
+ class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
|
|
|
|
+ >
|
|
|
|
|
+ {user.name}
|
|
|
|
|
+ <ChevronUp class="ms-auto" />
|
|
|
|
|
+ </Sidebar.MenuButton>
|
|
|
|
|
+ {/snippet}
|
|
|
|
|
+ </DropdownMenu.Trigger>
|
|
|
|
|
+ <DropdownMenu.Content class="w-(--bits-dropdown-menu-anchor-width)" side="top">
|
|
|
|
|
+ <DropdownMenu.Item onclick={() => goto(resolve('/auth/logout'))}>
|
|
|
|
|
+ <span>Sign out</span>
|
|
|
|
|
+ </DropdownMenu.Item>
|
|
|
|
|
+ </DropdownMenu.Content>
|
|
|
|
|
+ </DropdownMenu.Root>
|
|
|
|
|
+ </Sidebar.MenuItem>
|
|
|
|
|
+ </Sidebar.Menu>
|
|
|
|
|
+ </Sidebar.Footer>
|
|
|
|
|
+ {/if}
|
|
|
|
|
+</Sidebar.Root>
|