From 10e5ad13962a1c47620a80f0e22cf082092d6a77 Mon Sep 17 00:00:00 2001 From: Aleksander Grygier Date: Wed, 7 Jan 2026 13:53:25 +0100 Subject: [PATCH] feat: UI improvements --- .../webui/src/lib/components/app/index.ts | 2 +- .../lib/components/app/mcp/McpSelector.svelte | 207 ++++++++--------- .../ChatSettings => mcp}/McpServerCard.svelte | 214 +++++++++--------- .../ChatSettings => mcp}/McpServerForm.svelte | 0 .../McpSettingsSection.svelte | 37 +-- 5 files changed, 234 insertions(+), 226 deletions(-) rename tools/server/webui/src/lib/components/app/{chat/ChatSettings => mcp}/McpServerCard.svelte (63%) rename tools/server/webui/src/lib/components/app/{chat/ChatSettings => mcp}/McpServerForm.svelte (100%) rename tools/server/webui/src/lib/components/app/{chat/ChatSettings => mcp}/McpSettingsSection.svelte (86%) diff --git a/tools/server/webui/src/lib/components/app/index.ts b/tools/server/webui/src/lib/components/app/index.ts index 436b388c05..9d53544295 100644 --- a/tools/server/webui/src/lib/components/app/index.ts +++ b/tools/server/webui/src/lib/components/app/index.ts @@ -35,7 +35,7 @@ export { default as ChatSettingsFooter } from './chat/ChatSettings/ChatSettingsF export { default as ChatSettingsFields } from './chat/ChatSettings/ChatSettingsFields.svelte'; export { default as ChatSettingsImportExportTab } from './chat/ChatSettings/ChatSettingsImportExportTab.svelte'; export { default as ChatSettingsParameterSourceIndicator } from './chat/ChatSettings/ChatSettingsParameterSourceIndicator.svelte'; -export { default as McpSettingsSection } from './chat/ChatSettings/McpSettingsSection.svelte'; +export { default as McpSettingsSection } from './mcp/McpSettingsSection.svelte'; export { default as ChatSidebar } from './chat/ChatSidebar/ChatSidebar.svelte'; export { default as ChatSidebarConversationItem } from './chat/ChatSidebar/ChatSidebarConversationItem.svelte'; diff --git a/tools/server/webui/src/lib/components/app/mcp/McpSelector.svelte b/tools/server/webui/src/lib/components/app/mcp/McpSelector.svelte index 341f7693cb..ba366bd066 100644 --- a/tools/server/webui/src/lib/components/app/mcp/McpSelector.svelte +++ b/tools/server/webui/src/lib/components/app/mcp/McpSelector.svelte @@ -6,7 +6,7 @@ import { cn } from '$lib/components/ui/utils'; import { SearchableDropdownMenu } from '$lib/components/app'; import McpLogo from '$lib/components/app/misc/McpLogo.svelte'; - import { config } from '$lib/stores/settings.svelte'; + import { settingsStore } from '$lib/stores/settings.svelte'; import { conversationsStore } from '$lib/stores/conversations.svelte'; import { parseMcpServerSettings, parseMcpServerUsageStats } from '$lib/config/mcp'; import type { MCPServerSettingsEntry } from '$lib/types/mcp'; @@ -24,38 +24,32 @@ let { class: className = '', disabled = false, onSettingsClick }: Props = $props(); - let currentConfig = $derived(config()); let searchQuery = $state(''); - // MCP servers state - let mcpServers = $derived( - parseMcpServerSettings(currentConfig.mcpServers) - ); + let mcpServers = $derived.by(() => { + return parseMcpServerSettings(settingsStore.config.mcpServers); + }); - // Usage stats for sorting by popularity - let mcpUsageStats = $derived(parseMcpServerUsageStats(currentConfig.mcpServerUsageStats)); + let hasMcpServers = $derived(mcpServers.length > 0); + + let mcpUsageStats = $derived(parseMcpServerUsageStats(settingsStore.config.mcpServerUsageStats)); - // Get usage count for a server function getServerUsageCount(serverId: string): number { return mcpUsageStats[serverId] || 0; } - // Helper to check if server is enabled for current chat (per-chat override or global) function isServerEnabledForChat(server: MCPServerSettingsEntry): boolean { return conversationsStore.isMcpServerEnabledForChat(server.id, server.enabled); } - // Helper to check if server has per-chat override function hasPerChatOverride(serverId: string): boolean { return conversationsStore.getMcpServerOverride(serverId) !== undefined; } - // Servers enabled for current chat (considering per-chat overrides) let enabledMcpServersForChat = $derived( mcpServers.filter((s) => isServerEnabledForChat(s) && s.url.trim()) ); - // Filter out servers with health check errors let healthyEnabledMcpServers = $derived( enabledMcpServersForChat.filter((s) => { const healthState = mcpGetHealthCheckState(s.id); @@ -65,21 +59,21 @@ let hasEnabledMcpServers = $derived(enabledMcpServersForChat.length > 0); - // Sort servers: globally enabled first (by popularity), then rest (by popularity) let sortedMcpServers = $derived( [...mcpServers].sort((a, b) => { // First: globally enabled servers come first if (a.enabled !== b.enabled) return a.enabled ? -1 : 1; + // Then sort by usage count (descending) const usageA = getServerUsageCount(a.id); const usageB = getServerUsageCount(b.id); if (usageB !== usageA) return usageB - usageA; + // Then alphabetically by name return getServerDisplayName(a).localeCompare(getServerDisplayName(b)); }) ); - // Filtered servers for display let filteredMcpServers = $derived(() => { const query = searchQuery.toLowerCase().trim(); if (query) { @@ -89,19 +83,16 @@ return name.includes(query) || url.includes(query); }); } - // When not searching, show max 4 items + return sortedMcpServers.slice(0, 4); }); - // Count of extra servers beyond the 3 shown as favicons (excluding error servers) let extraServersCount = $derived(Math.max(0, healthyEnabledMcpServers.length - 3)); - // Toggle server enabled state for current chat (per-chat override only) async function toggleServerForChat(serverId: string, globalEnabled: boolean) { await conversationsStore.toggleMcpServerForChat(serverId, globalEnabled); } - // Get display name for server function getServerDisplayName(server: MCPServerSettingsEntry): string { if (server.name) return server.name; try { @@ -114,7 +105,6 @@ } } - // Get favicon URL for server function getFaviconUrl(server: MCPServerSettingsEntry): string | null { try { const url = new URL(server.url); @@ -134,10 +124,8 @@ .filter((f) => f.url !== null) ); - // All servers with valid URLs (for health checks) let serversWithUrls = $derived(mcpServers.filter((s) => s.url.trim())); - // Run health checks on mount for ALL servers with URLs onMount(() => { for (const server of serversWithUrls) { if (!mcpHasHealthCheck(server.id)) { @@ -147,93 +135,110 @@ }); - - {#snippet trigger()} - + {/snippet} + + {#each filteredMcpServers() as server (server.id)} + {@const healthState = mcpGetHealthCheckState(server.id)} + {@const hasError = healthState.status === 'error'} + {@const isEnabledForChat = isServerEnabledForChat(server)} + {@const hasOverride = hasPerChatOverride(server.id)} + +
+
+ {#if getFaviconUrl(server)} { (e.currentTarget as HTMLImageElement).style.display = 'none'; }} /> - {/each} + {/if} + {getServerDisplayName(server)} + {#if hasError} + Error + {:else if server.enabled} + Global + {/if}
- - {#if extraServersCount > 0} - +{extraServersCount} - {/if} - {/if} - - - - {/snippet} - - {#each filteredMcpServers() as server (server.id)} - {@const healthState = mcpGetHealthCheckState(server.id)} - {@const hasError = healthState.status === 'error'} - {@const isEnabledForChat = isServerEnabledForChat(server)} - {@const hasOverride = hasPerChatOverride(server.id)} - -
-
- {#if getFaviconUrl(server)} - { - (e.currentTarget as HTMLImageElement).style.display = 'none'; - }} - /> - {/if} - {getServerDisplayName(server)} - {#if hasError} - Error - {:else if server.enabled} - Global - {/if} + toggleServerForChat(server.id, server.enabled)} + disabled={hasError} + class={hasOverride ? 'ring-2 ring-primary/50 ring-offset-1' : ''} + />
- toggleServerForChat(server.id, server.enabled)} - disabled={hasError} - class={hasOverride ? 'ring-2 ring-primary/50 ring-offset-1' : ''} - /> -
- {/each} + {/each} - {#snippet footer()} - - - Manage MCP Servers - - {/snippet} - + {#snippet footer()} + + + Manage MCP Servers + + {/snippet} + +{:else} + +{/if} diff --git a/tools/server/webui/src/lib/components/app/chat/ChatSettings/McpServerCard.svelte b/tools/server/webui/src/lib/components/app/mcp/McpServerCard.svelte similarity index 63% rename from tools/server/webui/src/lib/components/app/chat/ChatSettings/McpServerCard.svelte rename to tools/server/webui/src/lib/components/app/mcp/McpServerCard.svelte index c8f1e410e6..01514965d2 100644 --- a/tools/server/webui/src/lib/components/app/chat/ChatSettings/McpServerCard.svelte +++ b/tools/server/webui/src/lib/components/app/mcp/McpServerCard.svelte @@ -7,7 +7,6 @@ ChevronDown, ChevronRight, Pencil, - Check, X, ExternalLink } from '@lucide/svelte'; @@ -15,7 +14,8 @@ import { Switch } from '$lib/components/ui/switch'; import * as Card from '$lib/components/ui/card'; import * as Collapsible from '$lib/components/ui/collapsible'; - import McpServerForm from './McpServerForm.svelte'; + import * as AlertDialog from '$lib/components/ui/alert-dialog'; + import McpServerForm from '$lib/components/app/mcp/McpServerForm.svelte'; import type { MCPServerSettingsEntry } from '$lib/types/mcp'; import { mcpGetHealthCheckState, @@ -36,7 +36,6 @@ let { server, displayName, faviconUrl, onToggle, onUpdate, onDelete }: Props = $props(); - // Get health state from store let healthState = $derived(mcpGetHealthCheckState(server.id)); let isHealthChecking = $derived(healthState.status === 'loading'); let isConnected = $derived(healthState.status === 'success'); @@ -45,15 +44,14 @@ let tools = $derived(healthState.status === 'success' ? healthState.tools : []); let toolsCount = $derived(tools.length); - // Expandable details state let isExpanded = $state(false); - // Edit mode state - default to edit mode if no URL + let showDeleteDialog = $state(false); + let isEditing = $state(!server.url.trim()); let editUrl = $state(server.url); let editHeaders = $state(server.headers || ''); - // Validation let urlError = $derived.by(() => { if (!editUrl.trim()) return 'URL is required'; try { @@ -66,7 +64,6 @@ let canSave = $derived(!urlError); - // Run health check on first mount if not already checked and server is enabled with URL onMount(() => { if (!mcpHasHealthCheck(server.id) && server.enabled && server.url.trim()) { mcpRunHealthCheck(server); @@ -84,11 +81,12 @@ } function cancelEditing() { - // Only allow cancel if server has valid URL if (server.url.trim()) { editUrl = server.url; editHeaders = server.headers || ''; isEditing = false; + } else { + onDelete(); } } @@ -99,51 +97,27 @@ headers: editHeaders.trim() || undefined }); isEditing = false; - // Run health check after saving + if (server.enabled && editUrl.trim()) { setTimeout(() => mcpRunHealthCheck({ ...server, url: editUrl.trim() }), 100); } } - + {#if isEditing} -

Configure Server

-
- {#if server.url.trim()} - - {/if} - - -
+
+ +
+ +
{:else} - -
-
{#if faviconUrl} -
- {#if isError && errorMessage}

{errorMessage}

{/if} - {#if tools.length === 0 && server.url.trim()}
{/if} - {/if} - - {#if tools.length > 0} - -
- - {#if isExpanded} - - {:else} - - {/if} - {toolsCount} tools available · Show details - -
- - - + {#if isExpanded} + + {:else} + + {/if} + {toolsCount} tools available · Show details + +
+ + + +
-
- -
- {#each tools as tool (tool.name)} -
- {tool.name} - {#if tool.description} -

{tool.description}

- {/if} -
- {/each} -
-
-
+ +
+ {#each tools as tool (tool.name)} +
+ {tool.name} + {#if tool.description} +

{tool.description}

+ {/if} +
+ {/each} +
+
+ + {/if} {/if} + + + + + Delete Server + + Are you sure you want to delete {displayName}? This action cannot be + undone. + + + + Cancel + + Delete + + + + diff --git a/tools/server/webui/src/lib/components/app/chat/ChatSettings/McpServerForm.svelte b/tools/server/webui/src/lib/components/app/mcp/McpServerForm.svelte similarity index 100% rename from tools/server/webui/src/lib/components/app/chat/ChatSettings/McpServerForm.svelte rename to tools/server/webui/src/lib/components/app/mcp/McpServerForm.svelte diff --git a/tools/server/webui/src/lib/components/app/chat/ChatSettings/McpSettingsSection.svelte b/tools/server/webui/src/lib/components/app/mcp/McpSettingsSection.svelte similarity index 86% rename from tools/server/webui/src/lib/components/app/chat/ChatSettings/McpSettingsSection.svelte rename to tools/server/webui/src/lib/components/app/mcp/McpSettingsSection.svelte index 3d5a720057..93474ffad8 100644 --- a/tools/server/webui/src/lib/components/app/chat/ChatSettings/McpSettingsSection.svelte +++ b/tools/server/webui/src/lib/components/app/mcp/McpSettingsSection.svelte @@ -1,12 +1,12 @@