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