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