@webmcp-auto-ui/ui 2.5.32 → 2.5.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +16 -2
- package/src/agent/DiagnosticModal.svelte +126 -50
- package/src/agent/EphemeralBubble.svelte +13 -3
- package/src/agent/MCPserversList.svelte +147 -0
- package/src/agent/McpConnector.svelte +10 -1
- package/src/agent/RecipeBrowser.svelte +384 -0
- package/src/agent/RemoteMCPserversDemo.svelte +5 -121
- package/src/agent/ToolBrowser.svelte +133 -0
- package/src/agent/WebMCPserversList.svelte +2 -0
- package/src/agent/useAgentLoop.svelte.ts +396 -0
- package/src/base/chat-inline.svelte +68 -0
- package/src/base/dialog-content.svelte +3 -1
- package/src/base/dialog-trigger.svelte +3 -2
- package/src/components/HeaderControls.svelte +78 -0
- package/src/index.ts +13 -35
- package/src/stores/canvas.svelte.ts +0 -6
- package/src/widgets/SafeImage.svelte +67 -0
- package/src/widgets/WidgetRenderer.svelte +153 -78
- package/src/widgets/notebook/executors/index.ts +0 -1
- package/src/widgets/notebook/executors/sql.ts +32 -182
- package/src/widgets/notebook/import-modal-api.ts +237 -0
- package/src/widgets/notebook/import-modal.svelte +738 -0
- package/src/widgets/notebook/left-pane.ts +1 -1
- package/src/widgets/notebook/notebook.svelte +75 -0
- package/src/widgets/notebook/notebook.ts +38 -73
- package/src/widgets/notebook/prose.ts +6 -3
- package/src/widgets/notebook/shared.ts +68 -49
- package/src/widgets/rich/cards.svelte +74 -0
- package/src/widgets/rich/carousel.svelte +126 -0
- package/src/widgets/rich/chart-rich.svelte +221 -0
- package/src/widgets/rich/chat-input.svelte +51 -0
- package/src/widgets/rich/data-table.svelte +132 -0
- package/src/widgets/rich/gallery.svelte +115 -0
- package/src/widgets/rich/grid-data.svelte +85 -0
- package/src/widgets/rich/hemicycle.svelte +95 -0
- package/src/widgets/rich/js-sandbox.svelte +67 -0
- package/src/widgets/rich/json-viewer.svelte +82 -0
- package/src/widgets/rich/log.svelte +62 -0
- package/src/widgets/rich/profile.svelte +91 -0
- package/src/widgets/rich/sankey.svelte +73 -0
- package/src/widgets/rich/stat-card.svelte +60 -0
- package/src/widgets/rich/timeline.svelte +95 -0
- package/src/widgets/rich/trombinoscope.svelte +87 -0
- package/src/widgets/simple/actions.svelte +36 -0
- package/src/widgets/simple/alert.svelte +52 -0
- package/src/widgets/simple/chart.svelte +38 -0
- package/src/widgets/simple/code.svelte +30 -0
- package/src/widgets/simple/kv.svelte +31 -0
- package/src/widgets/simple/list.svelte +35 -0
- package/src/widgets/simple/stat.svelte +36 -0
- package/src/widgets/simple/tags.svelte +34 -0
- package/src/widgets/simple/text.svelte +130 -0
- package/src/widgets/helpers/safe-image.ts +0 -78
- package/src/widgets/notebook/import-modals.ts +0 -560
- package/src/widgets/notebook/recipe-browser.ts +0 -350
- package/src/widgets/rich/cards.ts +0 -181
- package/src/widgets/rich/carousel.ts +0 -319
- package/src/widgets/rich/chart-rich.ts +0 -386
- package/src/widgets/rich/d3.ts +0 -503
- package/src/widgets/rich/data-table.ts +0 -342
- package/src/widgets/rich/gallery.ts +0 -350
- package/src/widgets/rich/grid-data.ts +0 -173
- package/src/widgets/rich/hemicycle.ts +0 -313
- package/src/widgets/rich/js-sandbox.ts +0 -122
- package/src/widgets/rich/json-viewer.ts +0 -202
- package/src/widgets/rich/log.ts +0 -143
- package/src/widgets/rich/map.ts +0 -218
- package/src/widgets/rich/profile.ts +0 -256
- package/src/widgets/rich/sankey.ts +0 -257
- package/src/widgets/rich/stat-card.ts +0 -125
- package/src/widgets/rich/timeline.ts +0 -179
- package/src/widgets/rich/trombinoscope.ts +0 -246
- package/src/widgets/simple/actions.ts +0 -89
- package/src/widgets/simple/alert.ts +0 -100
- package/src/widgets/simple/chart.ts +0 -189
- package/src/widgets/simple/code.ts +0 -79
- package/src/widgets/simple/kv.ts +0 -68
- package/src/widgets/simple/list.ts +0 -89
- package/src/widgets/simple/stat.ts +0 -58
- package/src/widgets/simple/tags.ts +0 -125
- package/src/widgets/simple/text.ts +0 -198
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webmcp-auto-ui/ui",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.34",
|
|
4
4
|
"description": "Svelte 5 UI components — primitives, widgets, window manager",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"type": "module",
|
|
@@ -16,11 +16,23 @@
|
|
|
16
16
|
"svelte": "./src/stores/canvas.svelte.ts",
|
|
17
17
|
"import": "./src/stores/canvas.svelte.ts"
|
|
18
18
|
},
|
|
19
|
+
"./agent": {
|
|
20
|
+
"svelte": "./src/agent/useAgentLoop.svelte.ts",
|
|
21
|
+
"import": "./src/agent/useAgentLoop.svelte.ts"
|
|
22
|
+
},
|
|
19
23
|
"./widgets/notebook/*": {
|
|
20
24
|
"svelte": "./src/widgets/notebook/*",
|
|
21
25
|
"import": "./src/widgets/notebook/*"
|
|
22
26
|
},
|
|
23
|
-
"./widgets/notebook/recipes/*": "./src/widgets/notebook/recipes/*"
|
|
27
|
+
"./widgets/notebook/recipes/*": "./src/widgets/notebook/recipes/*",
|
|
28
|
+
"./widgets/simple/*": {
|
|
29
|
+
"svelte": "./src/widgets/simple/*",
|
|
30
|
+
"import": "./src/widgets/simple/*"
|
|
31
|
+
},
|
|
32
|
+
"./widgets/rich/*": {
|
|
33
|
+
"svelte": "./src/widgets/rich/*",
|
|
34
|
+
"import": "./src/widgets/rich/*"
|
|
35
|
+
}
|
|
24
36
|
},
|
|
25
37
|
"scripts": {
|
|
26
38
|
"build": "svelte-package -i src",
|
|
@@ -57,7 +69,9 @@
|
|
|
57
69
|
"auto-ui"
|
|
58
70
|
],
|
|
59
71
|
"dependencies": {
|
|
72
|
+
"@internationalized/date": "^3.8.1",
|
|
60
73
|
"@types/d3": "^7.4.3",
|
|
74
|
+
"@webmcp-auto-ui/agent": "*",
|
|
61
75
|
"@webmcp-auto-ui/core": "*",
|
|
62
76
|
"@webmcp-auto-ui/sdk": "*",
|
|
63
77
|
"bits-ui": "^2.17.2",
|
|
@@ -7,13 +7,32 @@
|
|
|
7
7
|
codeFix?: string;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
interface DebugLayer {
|
|
11
|
+
protocol: string;
|
|
12
|
+
serverName?: string;
|
|
13
|
+
toolCount?: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface DebugInfo {
|
|
17
|
+
estimatedTokens: number;
|
|
18
|
+
layerCount: number;
|
|
19
|
+
toolCount: number;
|
|
20
|
+
mcpToolCount: number;
|
|
21
|
+
recipeCount: number;
|
|
22
|
+
layers: DebugLayer[];
|
|
23
|
+
prompt: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
10
26
|
interface Props {
|
|
11
27
|
open: boolean;
|
|
12
28
|
diagnostics: DiagnosticItem[];
|
|
13
29
|
onclose: () => void;
|
|
30
|
+
debugInfo?: DebugInfo;
|
|
14
31
|
}
|
|
15
32
|
|
|
16
|
-
let { open = $bindable(false), diagnostics, onclose }: Props = $props();
|
|
33
|
+
let { open = $bindable(false), diagnostics, onclose, debugInfo }: Props = $props();
|
|
34
|
+
|
|
35
|
+
let activeTab = $state<'diagnostics' | 'debug'>('diagnostics');
|
|
17
36
|
|
|
18
37
|
let expandedQuickFix = $state<Set<number>>(new Set());
|
|
19
38
|
let expandedCodeFix = $state<Set<number>>(new Set());
|
|
@@ -56,59 +75,116 @@
|
|
|
56
75
|
onclick={close}>x</button>
|
|
57
76
|
</div>
|
|
58
77
|
|
|
59
|
-
<!--
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
<
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
78
|
+
<!-- Tabs (only shown when debugInfo is provided) -->
|
|
79
|
+
{#if debugInfo}
|
|
80
|
+
<div class="flex border-b border-border flex-shrink-0">
|
|
81
|
+
<button
|
|
82
|
+
class="px-5 py-2 font-mono text-xs transition-colors border-b-2
|
|
83
|
+
{activeTab === 'diagnostics' ? 'border-accent text-text1' : 'border-transparent text-text2 hover:text-text1'}"
|
|
84
|
+
onclick={() => activeTab = 'diagnostics'}>
|
|
85
|
+
Diagnostics
|
|
86
|
+
</button>
|
|
87
|
+
<button
|
|
88
|
+
class="px-5 py-2 font-mono text-xs transition-colors border-b-2
|
|
89
|
+
{activeTab === 'debug' ? 'border-accent text-text1' : 'border-transparent text-text2 hover:text-text1'}"
|
|
90
|
+
onclick={() => activeTab = 'debug'}>
|
|
91
|
+
Debug
|
|
92
|
+
</button>
|
|
93
|
+
</div>
|
|
94
|
+
{/if}
|
|
95
|
+
|
|
96
|
+
<!-- Body: Diagnostics tab -->
|
|
97
|
+
{#if !debugInfo || activeTab === 'diagnostics'}
|
|
98
|
+
<div class="flex-1 overflow-y-auto p-4 flex flex-col gap-3">
|
|
99
|
+
{#if diagnostics.length === 0}
|
|
100
|
+
<div class="font-mono text-xs text-text2 text-center py-8">Aucun probleme detecte.</div>
|
|
101
|
+
{:else}
|
|
102
|
+
{#each diagnostics as diag, i}
|
|
103
|
+
<div class="rounded-lg border p-3 flex flex-col gap-1.5
|
|
104
|
+
{diag.severity === 'error' ? 'border-accent2/30 bg-accent2/5' : 'border-amber-500/30 bg-amber-500/5'}">
|
|
105
|
+
<!-- Severity icon + title -->
|
|
106
|
+
<div class="flex items-start gap-2">
|
|
107
|
+
{#if diag.severity === 'error'}
|
|
108
|
+
<span class="flex-shrink-0 w-4 h-4 rounded-full bg-accent2/20 text-accent2 flex items-center justify-center text-[10px] mt-0.5">!</span>
|
|
109
|
+
{:else}
|
|
110
|
+
<span class="flex-shrink-0 w-4 h-4 flex items-center justify-center text-amber-500 text-xs mt-0.5">⚠</span>
|
|
92
111
|
{/if}
|
|
112
|
+
<span class="font-mono text-xs font-bold text-text1">{diag.title}</span>
|
|
93
113
|
</div>
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
<
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
114
|
+
<!-- Detail -->
|
|
115
|
+
<div class="font-mono text-[11px] text-text2 leading-relaxed pl-6">{diag.detail}</div>
|
|
116
|
+
|
|
117
|
+
<!-- Quick fix -->
|
|
118
|
+
{#if diag.quickFix}
|
|
119
|
+
<div class="pl-6">
|
|
120
|
+
<button class="font-mono text-[10px] text-accent hover:underline cursor-pointer"
|
|
121
|
+
onclick={() => toggleQuickFix(i)}>
|
|
122
|
+
{expandedQuickFix.has(i) ? '- Quick fix (prompt)' : '+ Quick fix (prompt)'}
|
|
123
|
+
</button>
|
|
124
|
+
{#if expandedQuickFix.has(i)}
|
|
125
|
+
<div class="mt-1 relative">
|
|
126
|
+
<pre class="bg-surface2 border border-border2 rounded p-2 text-[10px] font-mono text-text2 whitespace-pre-wrap break-words">{diag.quickFix}</pre>
|
|
127
|
+
<button class="absolute top-1 right-1 px-1.5 py-0.5 rounded text-[9px] font-mono bg-surface border border-border2 text-text2 hover:text-text1 transition-colors"
|
|
128
|
+
onclick={() => copyText(diag.quickFix!)}>Copier</button>
|
|
129
|
+
</div>
|
|
130
|
+
{/if}
|
|
131
|
+
</div>
|
|
132
|
+
{/if}
|
|
133
|
+
|
|
134
|
+
<!-- Code fix -->
|
|
135
|
+
{#if diag.codeFix}
|
|
136
|
+
<div class="pl-6">
|
|
137
|
+
<button class="font-mono text-[10px] text-teal hover:underline cursor-pointer"
|
|
138
|
+
onclick={() => toggleCodeFix(i)}>
|
|
139
|
+
{expandedCodeFix.has(i) ? '- Fix (code)' : '+ Fix (code)'}
|
|
140
|
+
</button>
|
|
141
|
+
{#if expandedCodeFix.has(i)}
|
|
142
|
+
<pre class="mt-1 bg-surface2 border border-border2 rounded p-2 text-[10px] font-mono text-text2 whitespace-pre-wrap break-words">{diag.codeFix}</pre>
|
|
143
|
+
{/if}
|
|
144
|
+
</div>
|
|
145
|
+
{/if}
|
|
146
|
+
</div>
|
|
147
|
+
{/each}
|
|
148
|
+
{/if}
|
|
149
|
+
</div>
|
|
150
|
+
{/if}
|
|
151
|
+
|
|
152
|
+
<!-- Body: Debug tab -->
|
|
153
|
+
{#if debugInfo && activeTab === 'debug'}
|
|
154
|
+
<div class="flex-1 overflow-y-auto p-4 flex flex-col gap-3">
|
|
155
|
+
<!-- Stats grid -->
|
|
156
|
+
<div class="grid grid-cols-2 gap-x-4 gap-y-1 text-[10px]">
|
|
157
|
+
<span class="text-text2">Prompt tokens (est.)</span>
|
|
158
|
+
<span class="text-text1 font-mono">{debugInfo.estimatedTokens.toLocaleString()}</span>
|
|
159
|
+
<span class="text-text2">Active layers</span>
|
|
160
|
+
<span class="text-text1 font-mono">{debugInfo.layerCount}</span>
|
|
161
|
+
<span class="text-text2">Tools sent</span>
|
|
162
|
+
<span class="text-text1 font-mono">{debugInfo.toolCount} <span class="text-text2">(MCP: {debugInfo.mcpToolCount}, UI: {debugInfo.toolCount - debugInfo.mcpToolCount})</span></span>
|
|
163
|
+
<span class="text-text2">Recipes</span>
|
|
164
|
+
<span class="text-text1 font-mono">{debugInfo.recipeCount}</span>
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
<!-- Layers -->
|
|
168
|
+
<div>
|
|
169
|
+
<div class="text-[9px] text-text2 uppercase tracking-wider mb-1">Layers</div>
|
|
170
|
+
<div class="flex flex-col gap-1">
|
|
171
|
+
{#each debugInfo.layers as layer, i}
|
|
172
|
+
<div class="text-[10px] font-mono text-text1 px-2 py-1 bg-surface2/50 rounded">
|
|
173
|
+
[{i}] {layer.protocol}
|
|
174
|
+
{#if layer.serverName}— {layer.serverName}{/if}
|
|
175
|
+
{#if layer.toolCount !== undefined}({layer.toolCount} tools){/if}
|
|
106
176
|
</div>
|
|
107
|
-
{/
|
|
177
|
+
{/each}
|
|
108
178
|
</div>
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
179
|
+
</div>
|
|
180
|
+
|
|
181
|
+
<!-- System prompt -->
|
|
182
|
+
<div>
|
|
183
|
+
<div class="text-[9px] text-text2 uppercase tracking-wider mb-1">System Prompt</div>
|
|
184
|
+
<pre class="text-[9px] text-text1 bg-surface2/50 rounded p-2 max-h-[300px] overflow-auto whitespace-pre-wrap break-all">{debugInfo.prompt}</pre>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
{/if}
|
|
112
188
|
</div>
|
|
113
189
|
</div>
|
|
114
190
|
{/if}
|
|
@@ -3,8 +3,16 @@
|
|
|
3
3
|
import { renderMarkdown } from '../primitives/markdown-renderer.js';
|
|
4
4
|
|
|
5
5
|
interface EphemeralMsg { id: string; role: 'user' | 'assistant'; html: string; }
|
|
6
|
-
interface Props { ephemeral: EphemeralMsg[]; }
|
|
7
|
-
let { ephemeral }: Props = $props();
|
|
6
|
+
interface Props { ephemeral: EphemeralMsg[]; ondismiss?: () => void; }
|
|
7
|
+
let { ephemeral, ondismiss }: Props = $props();
|
|
8
|
+
|
|
9
|
+
let wrapper: HTMLDivElement | undefined = $state();
|
|
10
|
+
|
|
11
|
+
function onWindowPointerDown(e: PointerEvent) {
|
|
12
|
+
if (!ondismiss || ephemeral.length === 0) return;
|
|
13
|
+
const target = e.target as Node | null;
|
|
14
|
+
if (wrapper && target && !wrapper.contains(target)) ondismiss();
|
|
15
|
+
}
|
|
8
16
|
|
|
9
17
|
// Detect if content has any markdown markers worth parsing.
|
|
10
18
|
// If not, we skip marked entirely and fall back to {@html} for the
|
|
@@ -36,7 +44,9 @@
|
|
|
36
44
|
}
|
|
37
45
|
</script>
|
|
38
46
|
|
|
39
|
-
<
|
|
47
|
+
<svelte:window onpointerdown={onWindowPointerDown} />
|
|
48
|
+
|
|
49
|
+
<div bind:this={wrapper} class="flex flex-col gap-2 items-start w-full">
|
|
40
50
|
{#each ephemeral as msg (msg.id)}
|
|
41
51
|
<div
|
|
42
52
|
in:fly={{ y: 16, duration: 280, opacity: 0 }}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
<svelte:options customElement={{ tag: 'auto-mcp-servers-list', shadow: 'none' }} />
|
|
2
|
+
|
|
3
|
+
<script lang="ts">
|
|
4
|
+
interface Server {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
description: string;
|
|
8
|
+
url: string;
|
|
9
|
+
tags?: string[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
servers: Server[];
|
|
14
|
+
connectedUrls?: string[];
|
|
15
|
+
loading?: string[];
|
|
16
|
+
onconnect?: (url: string) => void;
|
|
17
|
+
onconnectall?: () => void;
|
|
18
|
+
ondisconnect?: (url: string) => void;
|
|
19
|
+
recipeCountByServer?: Record<string, number>;
|
|
20
|
+
onrecipeclick?: (url: string) => void;
|
|
21
|
+
toolCountByServer?: Record<string, number>;
|
|
22
|
+
ontoolclick?: (url: string) => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let {
|
|
26
|
+
servers,
|
|
27
|
+
connectedUrls = [],
|
|
28
|
+
loading = [],
|
|
29
|
+
onconnect,
|
|
30
|
+
onconnectall,
|
|
31
|
+
ondisconnect,
|
|
32
|
+
recipeCountByServer,
|
|
33
|
+
onrecipeclick,
|
|
34
|
+
toolCountByServer,
|
|
35
|
+
ontoolclick,
|
|
36
|
+
}: Props = $props();
|
|
37
|
+
|
|
38
|
+
const allConnected = $derived(
|
|
39
|
+
servers.length > 0 && servers.every(s => connectedUrls.includes(s.url))
|
|
40
|
+
);
|
|
41
|
+
const anyConnected = $derived(
|
|
42
|
+
servers.some(s => connectedUrls.includes(s.url))
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
function isConnected(url: string) {
|
|
46
|
+
return connectedUrls.includes(url);
|
|
47
|
+
}
|
|
48
|
+
function isLoading(url: string) {
|
|
49
|
+
return loading.includes(url);
|
|
50
|
+
}
|
|
51
|
+
</script>
|
|
52
|
+
|
|
53
|
+
<div class="flex flex-col gap-2">
|
|
54
|
+
<span class="text-[9px] font-mono uppercase tracking-wider text-text2">
|
|
55
|
+
Available MCP servers
|
|
56
|
+
</span>
|
|
57
|
+
|
|
58
|
+
<div class="flex flex-col gap-1">
|
|
59
|
+
{#each servers as server (server.id)}
|
|
60
|
+
{@const connected = isConnected(server.url)}
|
|
61
|
+
{@const busy = isLoading(server.url)}
|
|
62
|
+
<div
|
|
63
|
+
class="group flex items-center gap-2 px-2 py-1.5 rounded border border-border2 bg-surface2 hover:border-accent/30 transition-colors"
|
|
64
|
+
>
|
|
65
|
+
<!-- status dot -->
|
|
66
|
+
<div
|
|
67
|
+
class="w-1.5 h-1.5 rounded-full flex-shrink-0 {busy
|
|
68
|
+
? 'bg-amber animate-pulse'
|
|
69
|
+
: connected
|
|
70
|
+
? 'bg-teal'
|
|
71
|
+
: 'bg-text2/30'}"
|
|
72
|
+
></div>
|
|
73
|
+
|
|
74
|
+
<!-- info -->
|
|
75
|
+
<div class="flex-1 min-w-0 flex flex-col">
|
|
76
|
+
<span class="font-mono text-xs font-medium text-text1">{server.name}</span>
|
|
77
|
+
<span class="text-[10px] text-text2 truncate">{server.description}</span>
|
|
78
|
+
{#if connected && (recipeCountByServer?.[server.url] || toolCountByServer?.[server.url])}
|
|
79
|
+
<span class="flex items-center gap-1.5 mt-0.5">
|
|
80
|
+
{#if recipeCountByServer?.[server.url]}
|
|
81
|
+
<button class="text-[10px] font-mono text-accent hover:underline"
|
|
82
|
+
onclick={(e) => { e.stopPropagation(); onrecipeclick?.(server.url); }}>
|
|
83
|
+
{recipeCountByServer[server.url]} recipes
|
|
84
|
+
</button>
|
|
85
|
+
{/if}
|
|
86
|
+
{#if recipeCountByServer?.[server.url] && toolCountByServer?.[server.url]}
|
|
87
|
+
<span class="text-[10px] text-text2">·</span>
|
|
88
|
+
{/if}
|
|
89
|
+
{#if toolCountByServer?.[server.url]}
|
|
90
|
+
<button class="text-[10px] font-mono text-accent hover:underline"
|
|
91
|
+
onclick={(e) => { e.stopPropagation(); ontoolclick?.(server.url); }}>
|
|
92
|
+
{toolCountByServer[server.url]} tools
|
|
93
|
+
</button>
|
|
94
|
+
{/if}
|
|
95
|
+
</span>
|
|
96
|
+
{/if}
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
<!-- action -->
|
|
100
|
+
<div class="flex-shrink-0">
|
|
101
|
+
{#if busy}
|
|
102
|
+
<div class="w-4 h-4 border border-accent/50 border-t-accent rounded-full animate-spin"></div>
|
|
103
|
+
{:else if connected}
|
|
104
|
+
<button
|
|
105
|
+
onclick={() => ondisconnect?.(server.url)}
|
|
106
|
+
class="text-xs font-mono px-1.5 h-6 rounded text-teal group-hover:text-accent2 transition-colors"
|
|
107
|
+
title="Disconnect"
|
|
108
|
+
>
|
|
109
|
+
<span class="group-hover:hidden">✓</span>
|
|
110
|
+
<span class="hidden group-hover:inline text-accent2">×</span>
|
|
111
|
+
</button>
|
|
112
|
+
{:else}
|
|
113
|
+
<button
|
|
114
|
+
onclick={() => onconnect?.(server.url)}
|
|
115
|
+
class="text-[10px] font-mono px-1.5 h-6 rounded border border-border2 bg-surface2 hover:border-accent/50 hover:text-accent text-text2 transition-colors"
|
|
116
|
+
>
|
|
117
|
+
connect
|
|
118
|
+
</button>
|
|
119
|
+
{/if}
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
{/each}
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
<!-- bottom actions -->
|
|
126
|
+
<div class="flex items-center gap-2 mt-1">
|
|
127
|
+
<button
|
|
128
|
+
onclick={onconnectall}
|
|
129
|
+
disabled={allConnected}
|
|
130
|
+
class="text-xs font-mono px-2 h-7 rounded border border-accent/40 bg-accent/10 text-accent hover:bg-accent/20 transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
|
|
131
|
+
>
|
|
132
|
+
Load all
|
|
133
|
+
</button>
|
|
134
|
+
{#if anyConnected}
|
|
135
|
+
<button
|
|
136
|
+
onclick={() => {
|
|
137
|
+
for (const s of servers) {
|
|
138
|
+
if (isConnected(s.url)) ondisconnect?.(s.url);
|
|
139
|
+
}
|
|
140
|
+
}}
|
|
141
|
+
class="text-xs font-mono px-2 h-7 rounded border border-border2 bg-surface2 hover:border-accent2/50 hover:text-accent2 text-text2 transition-colors"
|
|
142
|
+
>
|
|
143
|
+
Disconnect all
|
|
144
|
+
</button>
|
|
145
|
+
{/if}
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
@@ -34,10 +34,15 @@
|
|
|
34
34
|
}: Props = $props();
|
|
35
35
|
|
|
36
36
|
let debounceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
37
|
+
let urlChangeTimer: ReturnType<typeof setTimeout> | null = null;
|
|
37
38
|
|
|
38
39
|
function handleUrlInput(e: Event) {
|
|
39
40
|
const v = (e.target as HTMLInputElement).value;
|
|
40
|
-
onurlchange
|
|
41
|
+
// Debounce onurlchange so we don't pollute the canvas store with every keystroke.
|
|
42
|
+
if (urlChangeTimer) clearTimeout(urlChangeTimer);
|
|
43
|
+
urlChangeTimer = setTimeout(() => {
|
|
44
|
+
onurlchange?.(v);
|
|
45
|
+
}, 400);
|
|
41
46
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
42
47
|
debounceTimer = setTimeout(() => {
|
|
43
48
|
if (v.startsWith('http') && !connected && !connecting) {
|
|
@@ -48,7 +53,11 @@
|
|
|
48
53
|
|
|
49
54
|
function handleKeydown(e: KeyboardEvent) {
|
|
50
55
|
if (e.key === 'Enter') {
|
|
56
|
+
if (urlChangeTimer) { clearTimeout(urlChangeTimer); urlChangeTimer = null; }
|
|
51
57
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
58
|
+
// Flush current URL immediately before connecting.
|
|
59
|
+
const v = (e.target as HTMLInputElement).value;
|
|
60
|
+
onurlchange?.(v);
|
|
52
61
|
onconnect?.();
|
|
53
62
|
}
|
|
54
63
|
}
|