mirror of
https://github.com/ggml-org/llama.cpp.git
synced 2026-09-03 19:42:05 +02:00
94875285e4
* feat: ui: Add predefined recommended MCP servers to settings * feat: ui: Add MCP server recommendation dialog with custom server support * feat: Auto-focus input fields on mount and dynamic addition * feat: Add header validation to MCP server add and edit forms * feat: Persist recommended MCP server opt-in selections * test: Cover MCP configuration with tests * chore: Format & cleanup * feat: Centralize MCP server overrides to settings config and improve recommendation UI * fix: Capture index before mutation to prevent focus drift * refactor: Extract MCP_CARD_VISIBLE_TOOL_LIMIT to shared constants * refactor: Support arbitrary authorization header schemes * refactor: Consolidate MCP recommendations dismissal into existing storage key * fix: Use case-insensitive comparison for MCP server ID prefix check * refactor: Centralize MCP server visibility logic and extract recommendations hook * refactor: Cleanup
96 lines
2.4 KiB
Svelte
96 lines
2.4 KiB
Svelte
<script lang="ts">
|
|
import { Button } from '$lib/components/ui/button';
|
|
import * as Dialog from '$lib/components/ui/dialog';
|
|
import { McpServerForm } from '$lib/components/app/mcp';
|
|
import { mcpStore } from '$lib/stores/mcp.svelte';
|
|
import { conversationsStore } from '$lib/stores/conversations.svelte';
|
|
import { parseHeadersToArray, uuid } from '$lib/utils';
|
|
import { MCP_SERVER_ID_PREFIX } from '$lib/constants';
|
|
|
|
interface Props {
|
|
open: boolean;
|
|
onOpenChange?: (open: boolean) => void;
|
|
}
|
|
|
|
let { open = $bindable(), onOpenChange }: Props = $props();
|
|
|
|
let newServerUrl = $state('');
|
|
let newServerHeaders = $state('');
|
|
let newServerUrlError = $derived.by(() => {
|
|
if (!newServerUrl.trim()) return 'URL is required';
|
|
try {
|
|
new URL(newServerUrl);
|
|
|
|
return null;
|
|
} catch {
|
|
return 'Invalid URL format';
|
|
}
|
|
});
|
|
let newServerHeaderPairsValid = $derived(
|
|
parseHeadersToArray(newServerHeaders).every((p) => p.key.trim() && p.value.trim())
|
|
);
|
|
let canSave = $derived(!newServerUrlError && newServerHeaderPairsValid);
|
|
|
|
function handleOpenChange(value: boolean) {
|
|
if (!value) {
|
|
newServerUrl = '';
|
|
newServerHeaders = '';
|
|
}
|
|
open = value;
|
|
onOpenChange?.(value);
|
|
}
|
|
|
|
function saveNewServer() {
|
|
if (!canSave) return;
|
|
|
|
const newServerId = uuid() ?? `${MCP_SERVER_ID_PREFIX}-${Date.now()}`;
|
|
|
|
mcpStore.addServer({
|
|
id: newServerId,
|
|
enabled: true,
|
|
url: newServerUrl.trim(),
|
|
headers: newServerHeaders.trim() || undefined
|
|
});
|
|
|
|
conversationsStore.setMcpServerOverride(newServerId, true);
|
|
|
|
handleOpenChange(false);
|
|
}
|
|
|
|
function handleSubmit(event: SubmitEvent) {
|
|
event.preventDefault();
|
|
saveNewServer();
|
|
}
|
|
</script>
|
|
|
|
<Dialog.Root {open} onOpenChange={handleOpenChange}>
|
|
<Dialog.Content class="sm:max-w-md">
|
|
<Dialog.Header>
|
|
<Dialog.Title>Add New Server</Dialog.Title>
|
|
</Dialog.Header>
|
|
|
|
<form onsubmit={handleSubmit} class="contents">
|
|
<div class="space-y-4 py-4">
|
|
<McpServerForm
|
|
url={newServerUrl}
|
|
headers={newServerHeaders}
|
|
onUrlChange={(v) => (newServerUrl = v)}
|
|
onHeadersChange={(v) => (newServerHeaders = v)}
|
|
urlError={newServerUrl ? newServerUrlError : null}
|
|
id="new-server"
|
|
/>
|
|
</div>
|
|
|
|
<Dialog.Footer>
|
|
<Button variant="secondary" size="sm" onclick={() => handleOpenChange(false)}>
|
|
Cancel
|
|
</Button>
|
|
|
|
<Button variant="default" size="sm" type="submit" disabled={!canSave} aria-label="Save">
|
|
Add
|
|
</Button>
|
|
</Dialog.Footer>
|
|
</form>
|
|
</Dialog.Content>
|
|
</Dialog.Root>
|