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