]> git.djapps.eu Git - pkg/ggml/sources/llama.cpp/blob
2b708aae539ee8992c648c73f4ed7dd41b0cf12e
[pkg/ggml/sources/llama.cpp] /
1 <script lang="ts">
2 import type { Snippet } from 'svelte';
3 import * as Tooltip from '$lib/components/ui/tooltip';
4 import * as Sheet from '$lib/components/ui/sheet';
5 import * as Collapsible from '$lib/components/ui/collapsible';
6 import { File, MessageSquare, Zap, FolderOpen } from '@lucide/svelte';
7 import { Switch } from '$lib/components/ui/switch';
8 import { Checkbox } from '$lib/components/ui/checkbox';
9 import { TOOLTIP_DELAY_DURATION } from '$lib/constants';
10 import { ATTACHMENT_FILE_ITEMS } from '$lib/constants/attachment-menu';
11 import { useAttachmentMenu } from '$lib/hooks/use-attachment-menu.svelte';
12 import { useToolsPanel } from '$lib/hooks/use-tools-panel.svelte';
13 import { conversationsStore } from '$lib/stores/conversations.svelte';
14 import { mcpStore } from '$lib/stores/mcp.svelte';
15 import { McpLogo } from '$lib/components/app';
16 import { PencilRuler, ChevronDown, ChevronRight } from '@lucide/svelte';
17 import { HealthCheckStatus } from '$lib/enums';
18 import { AttachmentAction } from '$lib/enums/attachment.enums';
19
20 interface Props {
21 class?: string;
22 disabled?: boolean;
23 hasAudioModality?: boolean;
24 hasVideoModality?: boolean;
25 hasVisionModality?: boolean;
26 hasMcpPromptsSupport?: boolean;
27 hasMcpResourcesSupport?: boolean;
28 onFileUpload?: () => void;
29 onSystemPromptClick?: () => void;
30 onMcpPromptClick?: () => void;
31 onMcpResourcesClick?: () => void;
32 trigger: Snippet<[{ disabled: boolean; onclick?: () => void }]>;
33 }
34
35 let {
36 class: className = '',
37 disabled = false,
38 hasAudioModality = false,
39 hasVisionModality = false,
40 hasVideoModality = false,
41 hasMcpPromptsSupport = false,
42 hasMcpResourcesSupport = false,
43 onFileUpload,
44 onSystemPromptClick,
45 onMcpPromptClick,
46 onMcpResourcesClick,
47 trigger
48 }: Props = $props();
49
50 let sheetOpen = $state(false);
51 let filesExpanded = $state(true);
52 let toolsExpanded = $state(false);
53 let mcpExpanded = $state(false);
54
55 const attachmentMenu = useAttachmentMenu(
56 () => ({
57 hasVisionModality,
58 hasAudioModality,
59 hasVideoModality,
60 hasMcpPromptsSupport,
61 hasMcpResourcesSupport
62 }),
63 () => ({ onFileUpload, onSystemPromptClick, onMcpPromptClick, onMcpResourcesClick }),
64 () => {
65 sheetOpen = false;
66 }
67 );
68
69 const toolsPanel = useToolsPanel();
70
71 const sheetItemClass =
72 'flex w-full items-center gap-3 rounded-md px-3 py-2.5 text-left text-sm transition-colors hover:bg-accent active:bg-accent disabled:cursor-not-allowed disabled:opacity-50';
73
74 const sheetItemRowClass =
75 'flex w-full items-center justify-between gap-2 rounded-md px-3 py-2 text-left text-sm transition-colors hover:bg-accent';
76
77 function getEnabledMcpServers() {
78 return mcpStore.getServersSorted().filter((s) => s.enabled);
79 }
80 </script>
81
82 <div class="flex items-center gap-1 {className}">
83 <Sheet.Root bind:open={sheetOpen}>
84 {@render trigger({ disabled, onclick: () => (sheetOpen = true) })}
85
86 <Sheet.Content side="bottom" class="max-h-[85vh] gap-0 overflow-y-auto">
87 <Sheet.Header>
88 <Sheet.Title>Add to chat</Sheet.Title>
89
90 <Sheet.Description class="sr-only">
91 Add files, system prompt or configure MCP servers
92 </Sheet.Description>
93 </Sheet.Header>
94
95 <div class="flex flex-col gap-1 px-1.5 pb-2">
96 <Collapsible.Root open={filesExpanded} onOpenChange={(open) => (filesExpanded = open)}>
97 <Collapsible.Trigger class={sheetItemClass}>
98 {#if filesExpanded}
99 <ChevronDown class="h-4 w-4 shrink-0" />
100 {:else}
101 <ChevronRight class="h-4 w-4 shrink-0" />
102 {/if}
103
104 <File class="h-4 w-4 shrink-0" />
105
106 <span class="flex-1">Add files</span>
107 </Collapsible.Trigger>
108
109 <Collapsible.Content>
110 <div class="flex flex-col gap-0.5 pl-4">
111 {#each ATTACHMENT_FILE_ITEMS as item (item.id)}
112 {@const enabled = attachmentMenu.isItemEnabled(item.enabledWhen)}
113 {#if enabled}
114 <button
115 type="button"
116 class={sheetItemClass}
117 onclick={() => attachmentMenu.callbacks[item.action]()}
118 >
119 <item.icon class="h-4 w-4 shrink-0" />
120
121 <span>{item.label}</span>
122 </button>
123 {:else if item.disabledTooltip}
124 <Tooltip.Root delayDuration={TOOLTIP_DELAY_DURATION}>
125 <Tooltip.Trigger>
126 <button type="button" class={sheetItemClass} disabled>
127 <item.icon class="h-4 w-4 shrink-0" />
128
129 <span>{item.label}</span>
130 </button>
131 </Tooltip.Trigger>
132
133 <Tooltip.Content side="right">
134 <p>{item.disabledTooltip}</p>
135 </Tooltip.Content>
136 </Tooltip.Root>
137 {/if}
138 {/each}
139 </div>
140 </Collapsible.Content>
141 </Collapsible.Root>
142
143 <Collapsible.Root open={mcpExpanded} onOpenChange={(open) => (mcpExpanded = open)}>
144 <Collapsible.Trigger class={sheetItemClass}>
145 {#if mcpExpanded}
146 <ChevronDown class="h-4 w-4 shrink-0" />
147 {:else}
148 <ChevronRight class="h-4 w-4 shrink-0" />
149 {/if}
150
151 <McpLogo class="inline h-4 w-4 shrink-0" />
152
153 <span class="flex-1">MCP Servers</span>
154
155 <span class="text-xs text-muted-foreground">
156 {getEnabledMcpServers().length} server{getEnabledMcpServers().length !== 1 ? 's' : ''}
157 </span>
158 </Collapsible.Trigger>
159
160 <Collapsible.Content>
161 <div class="flex flex-col gap-0.5 pl-4">
162 {#each getEnabledMcpServers() as server (server.id)}
163 {@const healthState = mcpStore.getHealthCheckState(server.id)}
164 {@const hasError = healthState.status === HealthCheckStatus.ERROR}
165 {@const displayName = mcpStore.getServerLabel(server)}
166 {@const faviconUrl = mcpStore.getServerFavicon(server.id)}
167 {@const isEnabled = conversationsStore.isMcpServerEnabledForChat(server.id)}
168
169 <button
170 type="button"
171 class={sheetItemRowClass}
172 onclick={() => !hasError && conversationsStore.toggleMcpServerForChat(server.id)}
173 disabled={hasError}
174 >
175 <div class="flex min-w-0 flex-1 items-center gap-2">
176 {#if faviconUrl}
177 <img
178 src={faviconUrl}
179 alt=""
180 class="h-4 w-4 shrink-0 rounded-sm"
181 onerror={(e) => {
182 (e.currentTarget as HTMLImageElement).style.display = 'none';
183 }}
184 />
185 {/if}
186
187 <span class="min-w-0 truncate text-sm">{displayName}</span>
188 </div>
189
190 {#if hasError}
191 <span
192 class="shrink-0 rounded bg-destructive/15 px-1.5 py-0.5 text-xs text-destructive"
193 >
194 Error
195 </span>
196 {:else}
197 <Switch
198 checked={isEnabled}
199 onCheckedChange={() => conversationsStore.toggleMcpServerForChat(server.id)}
200 />
201 {/if}
202 </button>
203 {/each}
204
205 {#if getEnabledMcpServers().length === 0}
206 <div class="px-3 py-2 text-center text-sm text-muted-foreground">
207 No MCP servers configured
208 </div>
209 {/if}
210 </div>
211 </Collapsible.Content>
212 </Collapsible.Root>
213
214 {#if toolsPanel.totalToolCount > 0}
215 <Collapsible.Root open={toolsExpanded} onOpenChange={(open) => (toolsExpanded = open)}>
216 <Collapsible.Trigger class={sheetItemClass}>
217 {#if toolsExpanded}
218 <ChevronDown class="h-4 w-4 shrink-0" />
219 {:else}
220 <ChevronRight class="h-4 w-4 shrink-0" />
221 {/if}
222
223 <PencilRuler class="inline h-4 w-4 shrink-0" />
224
225 <span class="flex-1">Tools</span>
226
227 <span class="text-xs text-muted-foreground">
228 {toolsPanel.totalToolCount} tool{toolsPanel.totalToolCount !== 1 ? 's' : ''}
229 </span>
230 </Collapsible.Trigger>
231
232 <Collapsible.Content>
233 <div class="flex flex-col gap-0.5 pl-4">
234 {#each toolsPanel.activeGroups as group (group.label)}
235 {@const checked = toolsPanel.isGroupChecked(group)}
236 {@const enabledCount = toolsPanel.getEnabledToolCount(group)}
237 {@const favicon = toolsPanel.getFavicon(group)}
238
239 <button
240 type="button"
241 class={sheetItemRowClass}
242 onclick={() => toolsPanel.toggleGroupByLabel(group.label)}
243 >
244 {#if favicon}
245 <img
246 src={favicon}
247 alt=""
248 class="h-4 w-4 shrink-0 rounded-sm"
249 onerror={(e) => {
250 (e.currentTarget as HTMLImageElement).style.display = 'none';
251 }}
252 />
253 {/if}
254
255 <span class="min-w-0 flex-1 truncate text-sm font-medium">{group.label}</span>
256
257 <span class="shrink-0 text-xs text-muted-foreground">
258 {enabledCount}/{group.tools.length}
259 </span>
260
261 <Checkbox
262 {checked}
263 class="h-4 w-4 shrink-0"
264 onclick={(e) => e.stopPropagation()}
265 onCheckedChange={() => toolsPanel.toggleGroupByLabel(group.label)}
266 />
267 </button>
268 {/each}
269 </div>
270 </Collapsible.Content>
271 </Collapsible.Root>
272 {/if}
273
274 <button
275 type="button"
276 class={sheetItemClass}
277 onclick={() => attachmentMenu.callbacks[AttachmentAction.SYSTEM_PROMPT_CLICK]()}
278 >
279 <MessageSquare class="h-4 w-4 shrink-0" />
280
281 <span>System Message</span>
282 </button>
283
284 {#if hasMcpPromptsSupport}
285 <button
286 type="button"
287 class={sheetItemClass}
288 onclick={() => attachmentMenu.callbacks[AttachmentAction.MCP_PROMPT_CLICK]()}
289 >
290 <Zap class="h-4 w-4 shrink-0" />
291
292 <span>MCP Prompt</span>
293 </button>
294 {/if}
295
296 {#if hasMcpResourcesSupport}
297 <button
298 type="button"
299 class={sheetItemClass}
300 onclick={() => attachmentMenu.callbacks[AttachmentAction.MCP_RESOURCES_CLICK]()}
301 >
302 <FolderOpen class="h-4 w-4 shrink-0" />
303
304 <span>MCP Resources</span>
305 </button>
306 {/if}
307 </div>
308 </Sheet.Content>
309 </Sheet.Root>
310 </div>