TdAI / llama.cpp /tools /ui /src /lib /components /app /mcp /McpServerCard /McpServerCard.svelte
tda45's picture
Upload folder using huggingface_hub (part 8)
15c3607 verified
Raw
History Blame Contribute Delete
5.45 kB
<script lang="ts">
import { tick } from 'svelte';
import * as Card from '$lib/components/ui/card';
import { Skeleton } from '$lib/components/ui/skeleton';
import type { MCPServerSettingsEntry, HealthCheckState } from '$lib/types';
import { HealthCheckStatus } from '$lib/enums';
import { mcpStore } from '$lib/stores/mcp.svelte';
import {
McpServerCardActions,
McpServerCardDeleteDialog,
McpServerCardEditForm,
McpServerCardHeader,
McpServerCardToolsList,
McpConnectionLogs,
McpServerInfo
} from '$lib/components/app/mcp';
interface Props {
server: MCPServerSettingsEntry;
enabled?: boolean;
onToggle: (enabled: boolean) => void;
onUpdate: (updates: Partial<MCPServerSettingsEntry>) => void;
onDelete: () => void;
}
let { server, enabled, onToggle, onUpdate, onDelete }: Props = $props();
let healthState = $derived<HealthCheckState>(mcpStore.getHealthCheckState(server.id));
let displayName = $derived(mcpStore.getServerLabel(server));
let faviconUrl = $derived(mcpStore.getServerFavicon(server.id));
let isIdle = $derived(healthState.status === HealthCheckStatus.IDLE);
let isHealthChecking = $derived(healthState.status === HealthCheckStatus.CONNECTING);
let isConnected = $derived(healthState.status === HealthCheckStatus.SUCCESS);
let isError = $derived(healthState.status === HealthCheckStatus.ERROR);
let showSkeleton = $derived(isIdle || isHealthChecking);
let errorMessage = $derived(
healthState.status === HealthCheckStatus.ERROR ? healthState.message : undefined
);
let tools = $derived(healthState.status === HealthCheckStatus.SUCCESS ? healthState.tools : []);
let connectionLogs = $derived(
healthState.status === HealthCheckStatus.CONNECTING ||
healthState.status === HealthCheckStatus.SUCCESS ||
healthState.status === HealthCheckStatus.ERROR
? healthState.logs
: []
);
let successState = $derived(
healthState.status === HealthCheckStatus.SUCCESS ? healthState : null
);
let serverInfo = $derived(successState?.serverInfo);
let capabilities = $derived(successState?.capabilities);
let transportType = $derived(successState?.transportType);
let protocolVersion = $derived(successState?.protocolVersion);
let connectionTimeMs = $derived(successState?.connectionTimeMs);
let instructions = $derived(successState?.instructions);
let isEditing = $derived(!server.url.trim());
let showDeleteDialog = $state(false);
let editFormRef: McpServerCardEditForm | null = $state(null);
function handleHealthCheck() {
mcpStore.runHealthCheck(server);
}
async function startEditing() {
isEditing = true;
await tick();
editFormRef?.setInitialValues(server.url, server.headers || '', server.useProxy || false);
}
function cancelEditing() {
if (server.url.trim()) {
isEditing = false;
} else {
onDelete();
}
}
function saveEditing(url: string, headers: string, useProxy: boolean) {
onUpdate({
url: url,
headers: headers || undefined,
useProxy: useProxy
});
isEditing = false;
if (server.enabled && url) {
setTimeout(() => mcpStore.runHealthCheck({ ...server, url, useProxy }), 100);
}
}
function handleDeleteClick() {
showDeleteDialog = true;
}
</script>
<Card.Root class="!gap-3 bg-muted/30 p-4">
{#if isEditing}
<McpServerCardEditForm
bind:this={editFormRef}
serverId={server.id}
serverUrl={server.url}
serverUseProxy={server.useProxy}
onSave={saveEditing}
onCancel={cancelEditing}
/>
{:else}
<McpServerCardHeader
{displayName}
{faviconUrl}
enabled={enabled ?? server.enabled}
disabled={isError}
{onToggle}
{serverInfo}
{capabilities}
{transportType}
/>
{#if isError && errorMessage}
<p class="text-xs text-destructive">{errorMessage}</p>
{/if}
{#if isConnected && serverInfo?.description}
<p class="line-clamp-2 text-xs text-muted-foreground">
{serverInfo.description}
</p>
{/if}
<div class="grid gap-3">
{#if showSkeleton}
<div class="space-y-2">
<div class="flex items-center gap-2">
<Skeleton class="h-4 w-4 rounded" />
<Skeleton class="h-3 w-24" />
</div>
<div class="flex flex-wrap gap-1.5">
<Skeleton class="h-5 w-16 rounded-full" />
<Skeleton class="h-5 w-20 rounded-full" />
<Skeleton class="h-5 w-14 rounded-full" />
</div>
</div>
<div class="space-y-1.5">
<div class="flex items-center gap-2">
<Skeleton class="h-4 w-4 rounded" />
<Skeleton class="h-3 w-32" />
</div>
</div>
{:else}
{#if isConnected && instructions}
<McpServerInfo {instructions} />
{/if}
{#if tools.length > 0}
<McpServerCardToolsList {tools} />
{/if}
{#if connectionLogs.length > 0}
<McpConnectionLogs logs={connectionLogs} {connectionTimeMs} />
{/if}
{/if}
</div>
<div class="mt-auto flex justify-between gap-4">
{#if showSkeleton}
<Skeleton class="h-3 w-28" />
{:else if protocolVersion}
<div class="flex flex-wrap items-center gap-1">
<span class="text-[10px] text-muted-foreground">
Protocol version: {protocolVersion}
</span>
</div>
{/if}
<McpServerCardActions
{isHealthChecking}
onEdit={startEditing}
onRefresh={handleHealthCheck}
onDelete={handleDeleteClick}
/>
</div>
{/if}
</Card.Root>
<McpServerCardDeleteDialog
bind:open={showDeleteDialog}
{displayName}
onOpenChange={(open) => (showDeleteDialog = open)}
onConfirm={onDelete}
/>