2 import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
3 import { PencilRuler, ChevronDown, ChevronRight, Loader2, Info, Check } from '@lucide/svelte';
4 import { Checkbox } from '$lib/components/ui/checkbox';
5 import * as Collapsible from '$lib/components/ui/collapsible';
6 import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
7 import * as Tooltip from '$lib/components/ui/tooltip';
8 import { toolsStore } from '$lib/stores/tools.svelte';
9 import { CLI_FLAGS } from '$lib/constants';
10 import { mcpStore } from '$lib/stores/mcp.svelte';
11 import { useToolsPanel } from '$lib/hooks/use-tools-panel.svelte';
13 const toolsPanel = useToolsPanel();
14 const hasMcpServersAvailable = $derived(mcpStore.getServers().length > 0);
17 <DropdownMenu.Sub onOpenChange={(open) => open && toolsPanel.handleOpen()}>
18 <DropdownMenu.SubTrigger class="flex cursor-pointer items-center gap-2">
19 <PencilRuler class={ICON_CLASS_DEFAULT} />
22 </DropdownMenu.SubTrigger>
24 <DropdownMenu.SubContent class="w-72 p-0">
25 {#if toolsPanel.totalToolCount === 0}
26 {#if toolsStore.loading}
27 <div class="px-3 py-4 text-center text-sm text-muted-foreground">
28 <Loader2 class="mx-auto mb-1 {ICON_CLASS_DEFAULT} animate-spin" />
32 {:else if toolsStore.isToolsEndpointUnreachable}
33 <div class="grid gap-2.5 px-3 py-4 text-sm text-muted-foreground">
34 <span class="flex gap-2">
35 <Info class="mt-0.5 {ICON_CLASS_DEFAULT} shrink-0" />
38 Run llama-server with <code>{CLI_FLAGS.TOOLS}</code> flag to enable
40 <strong>Built-in Tools</strong>.
44 <span class="flex gap-2">
45 <Info class="mt-0.5 {ICON_CLASS_DEFAULT} shrink-0" />
48 {hasMcpServersAvailable ? 'Enable' : 'Add'} MCP Server(s) to access
50 <strong>MCP Tools</strong>.
54 {:else if toolsStore.error}
55 <div class="px-3 py-4 text-center text-sm text-muted-foreground">Failed to load tools</div>
56 {:else if toolsPanel.noToolsInfoMessage}
57 <div class="flex gap-2 px-3 py-4 text-sm text-muted-foreground">
58 <Info class="mt-0.5 {ICON_CLASS_DEFAULT} shrink-0" />
60 <span>{toolsPanel.noToolsInfoMessage}</span>
63 <div class="px-3 py-4 text-center text-sm text-muted-foreground">No tools available</div>
66 <div class="max-h-80 overflow-y-auto p-2 pr-1">
67 {#each toolsPanel.activeGroups as group (group.key)}
68 {@const isExpanded = toolsPanel.expandedGroups.has(group.key)}
69 {@const checked = toolsPanel.isGroupChecked(group)}
70 {@const favicon = toolsPanel.getFavicon(group)}
74 onOpenChange={() => toolsPanel.toggleGroupExpanded(group.key)}
76 <div class="flex items-center gap-1">
78 class="flex min-w-0 flex-1 items-center gap-2 rounded px-2 py-1.5 text-sm hover:bg-muted/50"
81 <ChevronDown class="h-3.5 w-3.5 shrink-0" />
83 <ChevronRight class="h-3.5 w-3.5 shrink-0" />
86 <span class="inline-flex min-w-0 items-center gap-1.5 font-medium">
91 class="{ICON_CLASS_DEFAULT} shrink-0 rounded-sm"
93 (e.currentTarget as HTMLImageElement).style.display = 'none';
98 <span class="truncate">{group.label}</span>
101 <span class="ml-auto shrink-0 text-xs text-muted-foreground">
102 {toolsPanel.getEnabledToolCount(group)}/{group.tools.length}
104 </Collapsible.Trigger>
108 {#snippet child({ props })}
112 onCheckedChange={() => toolsPanel.toggleGroupByKey(group.key)}
113 class="mr-2 {ICON_CLASS_DEFAULT} shrink-0"
118 <Tooltip.Content side="right">
120 {checked ? 'Disable' : 'Enable'}
121 {group.tools.length} tool{group.tools.length !== 1 ? 's' : ''}
127 <Collapsible.Content>
128 <div class="ml-4 flex flex-col gap-0.5 border-l border-border/50 pl-2">
129 {#each group.tools as entry (entry.key)}
130 {@const enabled = toolsStore.isToolEnabled(entry.key)}
133 class="flex w-full items-center gap-2 rounded px-2 py-1.5 text-left text-sm transition-colors hover:bg-muted/50"
134 onclick={() => toolsStore.toggleTool(entry.key)}
138 data-state={enabled ? 'checked' : 'unchecked'}
139 class="flex size-4 shrink-0 items-center justify-center rounded-[4px] border border-input data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground"
142 <Check class="size-3.5" />
146 <span class="min-w-0 flex-1 truncate font-mono text-[12px]">
147 {entry.definition.function.name}
152 </Collapsible.Content>
157 </DropdownMenu.SubContent>