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