bar.svelte 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. <script lang="ts">
  2. import {
  3. BookSearch,
  4. Bot,
  5. BotMessageSquare,
  6. Brain,
  7. BrainCircuit,
  8. ChevronDown,
  9. ChevronUp,
  10. Component,
  11. Key,
  12. LibraryBig,
  13. Toolbox,
  14. Wrench,
  15. } from '@lucide/svelte';
  16. import { goto } from '$app/navigation';
  17. import { resolve } from '$app/paths';
  18. import { page } from '$app/state';
  19. import type { ResolvedPathname } from '$app/types';
  20. import * as DropdownMenu from '$lib/components/controls/dropdown-menu';
  21. import * as Sidebar from '$lib/components/controls/sidebar';
  22. import type { User } from '$lib/types/user';
  23. import type { Workspace } from '$lib/types/workspace';
  24. interface NavItem {
  25. label: string;
  26. icon: typeof Bot;
  27. href: ResolvedPathname;
  28. }
  29. interface NavItemGroup {
  30. label: string;
  31. items: NavItem[];
  32. }
  33. let {
  34. workspaceId,
  35. workspaces,
  36. user,
  37. }: { workspaceId: string | null; workspaces: Workspace[]; user: User | undefined } = $props();
  38. const workspaceName = $derived(
  39. workspaces.find((ws) => ws.id === workspaceId)?.displayName ?? 'Unknown Workspace',
  40. );
  41. const navItemGroups: NavItemGroup[] = [
  42. {
  43. label: 'Utilities',
  44. items: [
  45. { label: 'Agent Chat', icon: BotMessageSquare, href: resolve('/utilities/testing/agent') },
  46. { label: 'Retrieval', icon: BookSearch, href: resolve('/utilities/testing/kb') },
  47. { label: 'Tool Calling', icon: Wrench, href: resolve('/utilities/testing/tool') },
  48. { label: 'Model Inference', icon: BrainCircuit, href: resolve('/utilities/testing/model') },
  49. ],
  50. },
  51. {
  52. label: 'Workloads',
  53. items: [
  54. { label: 'Agents', icon: Bot, href: resolve('/workloads/agents') },
  55. { label: 'Knowledge Bases', icon: LibraryBig, href: resolve('/workloads/kbs') },
  56. { label: 'Tools', icon: Toolbox, href: resolve('/workloads/tools') },
  57. { label: 'Models', icon: Brain, href: resolve('/workloads/models') },
  58. ],
  59. },
  60. {
  61. label: 'Resources',
  62. items: [
  63. { label: 'Secrets', icon: Key, href: resolve('/resources/secrets') },
  64. { label: 'Stacks', icon: Component, href: resolve('/resources/stacks') },
  65. ],
  66. },
  67. ];
  68. const isActive = (href: string) =>
  69. href === '/' ? page.url.pathname === '/' : page.url.pathname.startsWith(href);
  70. </script>
  71. <Sidebar.Root class="border-white/10 backdrop-blur-md" collapsible="icon">
  72. <Sidebar.Header class="group-data-[collapsible=icon]:hidden">
  73. <Sidebar.Menu>
  74. <Sidebar.MenuItem>
  75. <DropdownMenu.Root>
  76. <DropdownMenu.Trigger>
  77. {#snippet child({ props })}
  78. <Sidebar.MenuButton class="whitespace-nowrap" {...props}>
  79. {workspaceName}
  80. <ChevronDown class="ms-auto" />
  81. </Sidebar.MenuButton>
  82. {/snippet}
  83. </DropdownMenu.Trigger>
  84. <DropdownMenu.Content class="w-(--bits-dropdown-menu-anchor-width)">
  85. {#each workspaces as workspace (workspace.id)}
  86. <DropdownMenu.Item>
  87. <span>{workspace.displayName}</span>
  88. </DropdownMenu.Item>
  89. {/each}
  90. </DropdownMenu.Content>
  91. </DropdownMenu.Root>
  92. </Sidebar.MenuItem>
  93. </Sidebar.Menu>
  94. </Sidebar.Header>
  95. <Sidebar.Content>
  96. {#each navItemGroups as group (group.label)}
  97. <Sidebar.Group>
  98. <Sidebar.GroupLabel>{group.label}</Sidebar.GroupLabel>
  99. <Sidebar.GroupContent>
  100. <Sidebar.Menu>
  101. {#each group.items as item (item.href)}
  102. <Sidebar.MenuItem>
  103. <Sidebar.MenuButton isActive={isActive(item.href)} tooltipContent={item.label}>
  104. {#snippet child({ props })}
  105. <a href={item.href} {...props}>
  106. <item.icon strokeWidth={3} />
  107. <span>{item.label}</span>
  108. </a>
  109. {/snippet}
  110. </Sidebar.MenuButton>
  111. </Sidebar.MenuItem>
  112. {/each}
  113. </Sidebar.Menu>
  114. </Sidebar.GroupContent>
  115. </Sidebar.Group>
  116. {/each}
  117. </Sidebar.Content>
  118. {#if user}
  119. <Sidebar.Footer class="group-data-[collapsible=icon]:hidden">
  120. <Sidebar.Menu>
  121. <Sidebar.MenuItem>
  122. <DropdownMenu.Root>
  123. <DropdownMenu.Trigger>
  124. {#snippet child({ props })}
  125. <Sidebar.MenuButton
  126. {...props}
  127. class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
  128. >
  129. {user.name}
  130. <ChevronUp class="ms-auto" />
  131. </Sidebar.MenuButton>
  132. {/snippet}
  133. </DropdownMenu.Trigger>
  134. <DropdownMenu.Content class="w-(--bits-dropdown-menu-anchor-width)" side="top">
  135. <DropdownMenu.Item onclick={() => goto(resolve('/auth/logout'))}>
  136. <span>Sign out</span>
  137. </DropdownMenu.Item>
  138. </DropdownMenu.Content>
  139. </DropdownMenu.Root>
  140. </Sidebar.MenuItem>
  141. </Sidebar.Menu>
  142. </Sidebar.Footer>
  143. {/if}
  144. </Sidebar.Root>