* feat: Add shimmer text animation for processing state indicators * feat: Redesign CollapsibleContentBlock component with improved UX * feat: Add conditional setting display support with dependsOn field * feat: Add showAgenticTurnStats setting for per-turn statistics * feat: Update ChatMessageAgenticContent with improved UI and new features * feat: Enhance file read tool UI/UX * feat: Refine styling of collapsible content and code preview blocks * feat: add terminal variant to CollapsibleContentBlock * feat: add built-in tools UI registry * feat: extract ChatMessageReasoningBlock and ChatMessageToolCallBlock * refactor: simplify ChatMessageAgenticContent to use extracted blocks * fix: correct markdown content block margin spacing * fix: reorganize SettingsChatFields layout and reset button positioning * fix: use direct map access in agentic store session methods * refactor: remove reasoning preview/throttle system from CollapsibleContentBlock * feat: add auto-scroll to reasoning block and remove showThoughtInProgress * feat: add ChatMessageToolCallDateTime component and support for new tool types * feat: improve auto-scroll reliability in reasoning block with RAF coalescing and MutationObserver * feat: show MCP server favicon for tools without a built-in icon * feat: add search-results parsing utilities and tests * feat: add ChatMessageToolCallSearchResults component * feat: integrate search results rendering into ChatMessageAgenticContent * feat: display tool call input alongside output in ChatMessageToolCallBlock * style: use muted foreground color in reasoning block content * chore: Format * feat: Refine reasoning block layout and make pending thoughts display configurable * feat: Stream tool call code blocks with auto-scroll and handle partial JSON * feat: add streaming permission gate infrastructure * feat: wire permission gate into the agentic loop * fix: bail out on abort and skip already-approved tool calls * fix: clear partial tool calls on abort and savePartialResponse * test: cover partial tool call cleanup end-to-end * refactor: Remove streaming permission gate logic * fix: Correct autoscroll and streaming gates for tool calls and reasoning blocks * refactor: Chat Message Assistant componentization * fix: Show health metadata for disabled MCP servers and promote connections on enable * fix: Inherit global enabled state for missing MCP per-chat overrides * refactor: Cleanup * refactor: Split ChatMessageToolCallBlock into dedicated components * feat: Add live streaming and auto-scroll for tool execution output * feat: Add line numbers and change markers to file edit diffs * chore: Formatting * feat: Add type definitions and utilities for recommended MCP servers * feat: Add recommended MCP servers configuration and storage key * feat: Add McpServerCardCompact component for recommended servers * feat: Add recommended servers section to Add New Server dialog * feat: Update McpServerForm to support authorization requirements * feat: Add select-none classes for text selection prevention * feat: Add recommended MCP server icon assets * refactor: Store dismissed MCP recommendations as a boolean flag * feat: Render tool results as JSON or Markdown based on detected content type * feat: UI improvement * feat: Render search block early and update heading to show execution state * fix: Prevent non-web-search tools from triggering the search UI block * refactor: Cleanup * refactor: Extract hardcoded icon size classes into shared constants * refactor: Extract hardcoded tool result separator into a shared constant * refactor: Tool Calls UI/logic * refactor: Cleanup * refactor: Cleanup * refactor: Cleanup
163 lines
4.8 KiB
Svelte
163 lines
4.8 KiB
Svelte
<script lang="ts">
|
|
import { X, Plus } from '@lucide/svelte';
|
|
import { mcpStore } from '$lib/stores/mcp.svelte';
|
|
import { conversationsStore } from '$lib/stores/conversations.svelte';
|
|
import { toolsStore } from '$lib/stores/tools.svelte';
|
|
import { Button } from '$lib/components/ui/button';
|
|
import * as Empty from '$lib/components/ui/empty';
|
|
import { ActionIcon, McpServerCard, McpServerCardSkeleton } from '$lib/components/app';
|
|
import { DialogMcpServerAddNew } from '$lib/components/app/dialogs';
|
|
import { HealthCheckStatus } from '$lib/enums';
|
|
import { ROUTES } from '$lib/constants';
|
|
import { fade } from 'svelte/transition';
|
|
import { onMount } from 'svelte';
|
|
import McpLogo from '../mcp/McpLogo.svelte';
|
|
import { browser } from '$app/environment';
|
|
import { page } from '$app/state';
|
|
import { goto, replaceState } from '$app/navigation';
|
|
|
|
interface Props {
|
|
class?: string;
|
|
}
|
|
|
|
let { class: className }: Props = $props();
|
|
|
|
let servers = $derived(mcpStore.getServers());
|
|
|
|
let isAddingServer = $state(false);
|
|
|
|
let previousRouteId = $state<string | null>(null);
|
|
|
|
$effect(() => {
|
|
const currentId = page.route.id;
|
|
return () => {
|
|
previousRouteId = currentId;
|
|
};
|
|
});
|
|
|
|
function handleClose() {
|
|
const prevIsMcpServers = previousRouteId === '/mcp-servers';
|
|
if (browser && window.history.length > 1 && !prevIsMcpServers) {
|
|
history.back();
|
|
} else {
|
|
goto(ROUTES.START);
|
|
}
|
|
}
|
|
|
|
onMount(() => {
|
|
if (page.url.searchParams.has('add')) {
|
|
isAddingServer = true;
|
|
|
|
const newUrl = new URL(page.url);
|
|
newUrl.searchParams.delete('add');
|
|
|
|
replaceState(newUrl, {});
|
|
}
|
|
});
|
|
|
|
// Each card decides for itself whether to render based on its own
|
|
// health-check state, so adding a server only flashes the new card
|
|
// (not every other already-loaded card) until its health check resolves.
|
|
// Disabled servers never receive a startup health check, so IDLE only
|
|
// counts as pending when the server is enabled; otherwise the real card
|
|
// renders and keeps the enable toggle reachable.
|
|
function isServerPending(serverId: string, enabled: boolean): boolean {
|
|
const status = mcpStore.getHealthCheckState(serverId).status;
|
|
return (
|
|
status === HealthCheckStatus.CONNECTING || (status === HealthCheckStatus.IDLE && enabled)
|
|
);
|
|
}
|
|
</script>
|
|
|
|
<div in:fade={{ duration: 150 }} class="flex min-h-[calc(100dvh-4rem)] flex-col">
|
|
<div class="fixed top-4.5 right-4 z-50 md:hidden">
|
|
<ActionIcon icon={X} tooltip="Close" onclick={handleClose} />
|
|
</div>
|
|
|
|
<div
|
|
class="sticky top-0 z-10 mt-4 mb-2 flex items-start gap-4 md:p-4 p-0 px-4 md:justify-between md:px-8"
|
|
>
|
|
<div class="flex items-center gap-2">
|
|
<McpLogo class="h-5 w-5 md:h-6 md:w-6" />
|
|
|
|
<h1 class="text-lg font-semibold md:text-2xl">MCP Servers</h1>
|
|
</div>
|
|
</div>
|
|
|
|
<DialogMcpServerAddNew bind:open={isAddingServer} />
|
|
|
|
{#if servers.length === 0}
|
|
<div class="flex flex-1 items-center justify-center py-16">
|
|
<Empty.Root class="max-w-md">
|
|
<Empty.Header>
|
|
<Empty.Media variant="icon">
|
|
<Plus />
|
|
</Empty.Media>
|
|
|
|
<Empty.Title>Add your first MCP server</Empty.Title>
|
|
|
|
<Empty.Description>Connect a remote MCP server by URL.</Empty.Description>
|
|
</Empty.Header>
|
|
|
|
<Empty.Content>
|
|
<Button size="sm" onclick={() => (isAddingServer = true)}>
|
|
<Plus />
|
|
|
|
Add New Server
|
|
</Button>
|
|
</Empty.Content>
|
|
</Empty.Root>
|
|
</div>
|
|
{:else}
|
|
<div
|
|
class="grid gap-3 {className}"
|
|
style="grid-template-columns: repeat(auto-fill, minmax(min(32rem, calc(100dvw - 2rem)), 1fr));"
|
|
>
|
|
{#each servers as server (server.id)}
|
|
{#if isServerPending(server.id, server.enabled)}
|
|
<McpServerCardSkeleton />
|
|
{:else}
|
|
<McpServerCard
|
|
{server}
|
|
enabled={conversationsStore.isMcpServerEnabledForChat(server.id)}
|
|
onToggle={async () => {
|
|
const wasEnabled = conversationsStore.isMcpServerEnabledForChat(server.id);
|
|
await conversationsStore.toggleMcpServerForChat(server.id);
|
|
if (!wasEnabled) {
|
|
// Promote the connection so tools/prompts/resources become
|
|
// available right away instead of waiting for the next chat-init.
|
|
await mcpStore.runHealthCheck(server, true);
|
|
toolsStore.enableAllToolsForServer(server.id);
|
|
}
|
|
}}
|
|
onUpdate={(updates) => mcpStore.updateServer(server.id, updates)}
|
|
onDelete={() => mcpStore.removeServer(server.id)}
|
|
/>
|
|
{/if}
|
|
{/each}
|
|
|
|
{#if !isAddingServer}
|
|
<Empty.Root class="border">
|
|
<Empty.Header>
|
|
<Empty.Media variant="icon">
|
|
<Plus />
|
|
</Empty.Media>
|
|
|
|
<Empty.Title>Add another MCP server</Empty.Title>
|
|
|
|
<Empty.Description>Connect a remote MCP server by URL.</Empty.Description>
|
|
</Empty.Header>
|
|
|
|
<Empty.Content>
|
|
<Button size="sm" onclick={() => (isAddingServer = true)}>
|
|
<Plus />
|
|
|
|
Add New Server
|
|
</Button>
|
|
</Empty.Content>
|
|
</Empty.Root>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
</div>
|