]> git.djapps.eu Git - pkg/ggml/sources/llama.cpp/blob
de1ced1723198b9ec60b2d60db8cc5a027d11be1
[pkg/ggml/sources/llama.cpp] /
1 <script lang="ts">
2 import { ICON_CLASS_DEFAULT } from '$lib/constants/css-classes';
3 import { Settings, Plus } from '@lucide/svelte';
4 import { Switch } from '$lib/components/ui/switch';
5 import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
6 import { McpLogo, DropdownMenuSearchable, McpServerIdentity } from '$lib/components/app';
7 import { conversationsStore } from '$lib/stores/conversations.svelte';
8 import { mcpStore } from '$lib/stores/mcp.svelte';
9 import { HealthCheckStatus } from '$lib/enums';
10 import type { MCPServerSettingsEntry } from '$lib/types';
11 import { goto } from '$app/navigation';
12 import { ROUTES } from '$lib/constants/routes';
13
14 interface Props {
15 onMcpSettingsClick?: () => void;
16 }
17
18 let { onMcpSettingsClick }: Props = $props();
19
20 let mcpSearchQuery = $state('');
21 // Every configured server is listed; `enabled` is an on/off state,
22 // not a visibility filter, so a disabled server stays toggleable.
23 let mcpServers = $derived(mcpStore.getServers());
24 let hasMcpServers = $derived(mcpServers.length > 0);
25 let filteredMcpServers = $derived.by(() => {
26 const query = mcpSearchQuery.toLowerCase().trim();
27 if (!query) return mcpServers;
28 return mcpServers.filter((s) => {
29 const name = getServerLabel(s).toLowerCase();
30 const url = s.url.toLowerCase();
31 return name.includes(query) || url.includes(query);
32 });
33 });
34
35 function getServerLabel(server: MCPServerSettingsEntry): string {
36 return mcpStore.getServerLabel(server);
37 }
38
39 function isServerEnabledForChat(serverId: string): boolean {
40 return conversationsStore.isMcpServerEnabledForChat(serverId);
41 }
42
43 async function toggleServerForChat(serverId: string) {
44 await conversationsStore.toggleMcpServerForChat(serverId);
45 }
46
47 function handleMcpSubMenuOpen(open: boolean) {
48 if (open) {
49 mcpSearchQuery = '';
50 mcpStore.runHealthChecksForServers(mcpServers);
51 }
52 }
53
54 function handleMcpSettingsClick() {
55 onMcpSettingsClick?.();
56
57 goto(`${hasMcpServers ? '' : '?add'}${ROUTES.MCP_SERVERS}`);
58 }
59 </script>
60
61 <DropdownMenu.Root>
62 <DropdownMenu.Sub onOpenChange={handleMcpSubMenuOpen}>
63 <DropdownMenu.SubTrigger class="flex cursor-pointer items-center gap-2">
64 <McpLogo class={ICON_CLASS_DEFAULT} />
65
66 <span>MCP Servers</span>
67 </DropdownMenu.SubTrigger>
68
69 <DropdownMenu.SubContent class="w-72 pt-0">
70 {#if hasMcpServers}
71 <DropdownMenuSearchable
72 placeholder="Search servers..."
73 bind:searchValue={mcpSearchQuery}
74 emptyMessage="No servers found"
75 isEmpty={filteredMcpServers.length === 0}
76 >
77 <div class="max-h-64 overflow-y-auto">
78 {#each filteredMcpServers as server (server.id)}
79 {@const healthState = mcpStore.getHealthCheckState(server.id)}
80 {@const hasError = healthState.status === HealthCheckStatus.ERROR}
81 {@const isEnabledForChat = isServerEnabledForChat(server.id)}
82 {@const displayName = getServerLabel(server)}
83 {@const faviconUrl = mcpStore.getServerFavicon(server.id)}
84
85 <button
86 type="button"
87 class="flex w-full items-center justify-between gap-2 rounded-sm px-2 py-2 text-left transition-colors hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50"
88 onclick={() => !hasError && toggleServerForChat(server.id)}
89 disabled={hasError}
90 >
91 <div class="flex min-w-0 flex-1 items-center gap-2">
92 <div class="min-w-0 flex-1">
93 <McpServerIdentity
94 {displayName}
95 {faviconUrl}
96 iconClass={ICON_CLASS_DEFAULT}
97 iconRounded="rounded-sm"
98 showVersion={false}
99 nameClass="text-sm"
100 />
101 </div>
102
103 {#if hasError}
104 <span
105 class="shrink-0 rounded bg-destructive/15 px-1.5 py-0.5 text-xs text-destructive"
106 >
107 Error
108 </span>
109 {/if}
110 </div>
111
112 <Switch
113 checked={isEnabledForChat}
114 disabled={hasError}
115 onclick={(e) => e.stopPropagation()}
116 onCheckedChange={() => toggleServerForChat(server.id)}
117 />
118 </button>
119 {/each}
120 </div>
121
122 {#snippet footer()}
123 <DropdownMenu.Item
124 class="flex cursor-pointer items-center gap-2"
125 onclick={handleMcpSettingsClick}
126 >
127 <Settings class={ICON_CLASS_DEFAULT} />
128
129 <span>Manage MCP Servers</span>
130 </DropdownMenu.Item>
131 {/snippet}
132 </DropdownMenuSearchable>
133 {:else}
134 <div class="px-2 py-3 text-center text-sm text-muted-foreground">
135 No MCP servers configured
136 </div>
137
138 <DropdownMenu.Separator />
139
140 <DropdownMenu.Item
141 class="flex cursor-pointer items-center gap-2"
142 onclick={handleMcpSettingsClick}
143 >
144 <Plus class={ICON_CLASS_DEFAULT} />
145
146 <span>Add MCP Servers</span>
147 </DropdownMenu.Item>
148 {/if}
149 </DropdownMenu.SubContent>
150 </DropdownMenu.Sub>
151 </DropdownMenu.Root>