@webmcp-auto-ui/ui 2.5.24 → 2.5.26
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
CHANGED
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
prompt: '#6366f1',
|
|
28
28
|
schema: '#06b6d4',
|
|
29
29
|
warning: '#eab308',
|
|
30
|
+
recipe: '#ec4899',
|
|
30
31
|
};
|
|
31
32
|
|
|
32
33
|
function fmtTime(ts: number): string {
|
|
@@ -87,6 +88,12 @@
|
|
|
87
88
|
<span class="ac-tag" class:ac-tag-recette={prov.tag === 'recette'} class:ac-tag-impro={prov.tag === 'impro'}>{prov.tag}</span>
|
|
88
89
|
{/if}
|
|
89
90
|
<span class="ac-detail">{prov.rest}</span>
|
|
91
|
+
{:else if log.type === 'recipe'}
|
|
92
|
+
{@const sepIdx = log.detail.indexOf(' · ')}
|
|
93
|
+
{@const rid = sepIdx > 0 ? log.detail.slice(0, sepIdx) : log.detail}
|
|
94
|
+
{@const rest = sepIdx > 0 ? log.detail.slice(sepIdx + 3) : ''}
|
|
95
|
+
<span class="ac-tag ac-tag-recipe">📄 {rid}</span>
|
|
96
|
+
<span class="ac-detail">{rest}</span>
|
|
90
97
|
{:else}
|
|
91
98
|
<span class="ac-detail">
|
|
92
99
|
{log.detail}
|
|
@@ -243,6 +250,10 @@
|
|
|
243
250
|
color: #fb923c;
|
|
244
251
|
background: rgba(251, 146, 60, 0.1);
|
|
245
252
|
}
|
|
253
|
+
.ac-tag-recipe {
|
|
254
|
+
color: #ec4899;
|
|
255
|
+
background: rgba(236, 72, 153, 0.1);
|
|
256
|
+
}
|
|
246
257
|
|
|
247
258
|
.ac-clickable {
|
|
248
259
|
cursor: pointer;
|
|
@@ -1,9 +1,39 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { fly } from 'svelte/transition';
|
|
3
|
+
import { renderMarkdown } from '../primitives/markdown-renderer.js';
|
|
3
4
|
|
|
4
5
|
interface EphemeralMsg { id: string; role: 'user' | 'assistant'; html: string; }
|
|
5
6
|
interface Props { ephemeral: EphemeralMsg[]; }
|
|
6
7
|
let { ephemeral }: Props = $props();
|
|
8
|
+
|
|
9
|
+
// Detect if content has any markdown markers worth parsing.
|
|
10
|
+
// If not, we skip marked entirely and fall back to {@html} for the
|
|
11
|
+
// pre-existing HTML snippets (e.g. "<strong>tool_name</strong>").
|
|
12
|
+
const MD_RE = /(^|\n)\s*(#{1,6}\s|[-*+]\s|\d+\.\s|>\s)|\*\*|__|`|```|~~~|!\[|\[[^\]]+\]\(/;
|
|
13
|
+
function looksLikeMarkdown(src: string): boolean {
|
|
14
|
+
return MD_RE.test(src);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Gracefully close dangling code fences during streaming so that
|
|
18
|
+
// a half-received ``` block still renders as code instead of
|
|
19
|
+
// swallowing the rest of the message.
|
|
20
|
+
function closeDanglingFences(src: string): string {
|
|
21
|
+
const fences = (src.match(/```/g) ?? []).length;
|
|
22
|
+
if (fences % 2 === 1) return src + '\n```';
|
|
23
|
+
const tildes = (src.match(/~~~/g) ?? []).length;
|
|
24
|
+
if (tildes % 2 === 1) return src + '\n~~~';
|
|
25
|
+
return src;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function renderContent(src: string): string {
|
|
29
|
+
if (!src) return '';
|
|
30
|
+
if (!looksLikeMarkdown(src)) return src;
|
|
31
|
+
try {
|
|
32
|
+
return renderMarkdown(closeDanglingFences(src));
|
|
33
|
+
} catch {
|
|
34
|
+
return src;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
7
37
|
</script>
|
|
8
38
|
|
|
9
39
|
<div class="flex flex-col gap-2 items-start w-full">
|
|
@@ -13,7 +43,7 @@
|
|
|
13
43
|
out:fly={{ y: -32, duration: 450, opacity: 0 }}
|
|
14
44
|
class="ephemeral-msg {msg.role}"
|
|
15
45
|
>
|
|
16
|
-
{@html msg.html}
|
|
46
|
+
{@html renderContent(msg.html)}
|
|
17
47
|
</div>
|
|
18
48
|
{/each}
|
|
19
49
|
</div>
|
|
@@ -41,4 +71,41 @@
|
|
|
41
71
|
color: var(--color-text1);
|
|
42
72
|
align-self: flex-start;
|
|
43
73
|
}
|
|
74
|
+
/* Markdown tweaks scoped to the ephemeral bubble — keep margins tight. */
|
|
75
|
+
.ephemeral-msg :global(p) { margin: 0.25rem 0; }
|
|
76
|
+
.ephemeral-msg :global(p:first-child) { margin-top: 0; }
|
|
77
|
+
.ephemeral-msg :global(p:last-child) { margin-bottom: 0; }
|
|
78
|
+
.ephemeral-msg :global(h1),
|
|
79
|
+
.ephemeral-msg :global(h2),
|
|
80
|
+
.ephemeral-msg :global(h3),
|
|
81
|
+
.ephemeral-msg :global(h4) {
|
|
82
|
+
font-weight: 600;
|
|
83
|
+
margin: 0.35rem 0 0.25rem;
|
|
84
|
+
font-size: 0.78rem;
|
|
85
|
+
}
|
|
86
|
+
.ephemeral-msg :global(ul),
|
|
87
|
+
.ephemeral-msg :global(ol) {
|
|
88
|
+
margin: 0.3rem 0;
|
|
89
|
+
padding-left: 1.1rem;
|
|
90
|
+
}
|
|
91
|
+
.ephemeral-msg :global(li) { margin: 0.1rem 0; }
|
|
92
|
+
.ephemeral-msg :global(code) {
|
|
93
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
94
|
+
font-size: 0.66rem;
|
|
95
|
+
background: rgba(0, 0, 0, 0.28);
|
|
96
|
+
padding: 0.05rem 0.25rem;
|
|
97
|
+
border-radius: 0.2rem;
|
|
98
|
+
}
|
|
99
|
+
.ephemeral-msg :global(pre) {
|
|
100
|
+
background: rgba(0, 0, 0, 0.35);
|
|
101
|
+
border-radius: 0.3rem;
|
|
102
|
+
padding: 0.5rem;
|
|
103
|
+
margin: 0.35rem 0;
|
|
104
|
+
overflow-x: auto;
|
|
105
|
+
font-size: 0.66rem;
|
|
106
|
+
}
|
|
107
|
+
.ephemeral-msg :global(pre code) { background: transparent; padding: 0; }
|
|
108
|
+
.ephemeral-msg :global(strong) { font-weight: 600; }
|
|
109
|
+
.ephemeral-msg :global(em) { font-style: italic; }
|
|
110
|
+
.ephemeral-msg :global(a) { color: rgb(96, 165, 250); text-decoration: underline; }
|
|
44
111
|
</style>
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
onconnect?: () => void;
|
|
14
14
|
ondisconnect?: () => void;
|
|
15
15
|
class?: string;
|
|
16
|
-
compact?: boolean;
|
|
16
|
+
compact?: boolean; // hide token field entirely
|
|
17
|
+
showToken?: boolean; // toggle controlled from outside
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
let {
|
|
@@ -29,9 +30,9 @@
|
|
|
29
30
|
ondisconnect,
|
|
30
31
|
class: cls = '',
|
|
31
32
|
compact = false,
|
|
33
|
+
showToken = $bindable(false),
|
|
32
34
|
}: Props = $props();
|
|
33
35
|
|
|
34
|
-
let showToken = $state(false);
|
|
35
36
|
let debounceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
36
37
|
|
|
37
38
|
function handleUrlInput(e: Event) {
|
|
@@ -82,25 +83,14 @@
|
|
|
82
83
|
<McpStatus {connecting} {connected} name={serverName || 'not connected'} />
|
|
83
84
|
</div>
|
|
84
85
|
|
|
85
|
-
{#if !compact}
|
|
86
|
-
<
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
</button>
|
|
94
|
-
{#if showToken}
|
|
95
|
-
<input
|
|
96
|
-
type="password"
|
|
97
|
-
value={token}
|
|
98
|
-
oninput={(e) => { token = (e.target as HTMLInputElement).value; onTokenChange?.(token); }}
|
|
99
|
-
placeholder="Bearer token (optional)"
|
|
100
|
-
class="flex-1 font-mono text-xs bg-surface2 border border-border2 rounded px-2 h-7 text-text1 outline-none placeholder:text-text2/40 focus:border-accent/50 transition-colors"
|
|
101
|
-
/>
|
|
102
|
-
{/if}
|
|
103
|
-
</div>
|
|
86
|
+
{#if !compact && showToken}
|
|
87
|
+
<input
|
|
88
|
+
type="password"
|
|
89
|
+
value={token}
|
|
90
|
+
oninput={(e) => { token = (e.target as HTMLInputElement).value; onTokenChange?.(token); }}
|
|
91
|
+
placeholder="Bearer token (optional)"
|
|
92
|
+
class="font-mono text-xs bg-surface2 border border-border2 rounded px-2 h-7 text-text1 outline-none placeholder:text-text2/40 focus:border-accent/50 transition-colors"
|
|
93
|
+
/>
|
|
104
94
|
{/if}
|
|
105
95
|
|
|
106
96
|
{#if error}
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
effectivePrompt?: string;
|
|
7
7
|
maxTokens?: number;
|
|
8
8
|
maxContextTokens?: number;
|
|
9
|
-
maxTools?: number;
|
|
10
9
|
maxResultLength?: number;
|
|
11
10
|
compressHistory?: boolean;
|
|
12
11
|
compressPreview?: number;
|
|
@@ -25,7 +24,6 @@
|
|
|
25
24
|
effectivePrompt = '',
|
|
26
25
|
maxTokens = $bindable(4096),
|
|
27
26
|
maxContextTokens = $bindable(150_000),
|
|
28
|
-
maxTools = $bindable(8),
|
|
29
27
|
maxResultLength = $bindable(10000),
|
|
30
28
|
compressHistory = $bindable(false),
|
|
31
29
|
compressPreview = $bindable(500),
|
|
@@ -104,15 +102,15 @@
|
|
|
104
102
|
<label class="text-[9px] font-mono text-text2 uppercase tracking-wider">System Prompt</label>
|
|
105
103
|
<div class="flex items-center gap-2">
|
|
106
104
|
{#if promptSaved && customMode}
|
|
107
|
-
<span class="text-[9px] font-mono text-teal transition-opacity">✓
|
|
105
|
+
<span class="text-[9px] font-mono text-teal transition-opacity">✓ applied</span>
|
|
108
106
|
{/if}
|
|
109
107
|
{#if hasEffective}
|
|
110
108
|
{#if customMode}
|
|
111
109
|
<button class="text-[9px] font-mono text-accent2 hover:text-accent transition-colors"
|
|
112
|
-
onclick={resetToAuto}>
|
|
110
|
+
onclick={resetToAuto}>reset</button>
|
|
113
111
|
{:else}
|
|
114
112
|
<button class="text-[9px] font-mono text-accent hover:text-text1 transition-colors"
|
|
115
|
-
onclick={enterCustomMode}>
|
|
113
|
+
onclick={enterCustomMode}>customize</button>
|
|
116
114
|
{/if}
|
|
117
115
|
{/if}
|
|
118
116
|
</div>
|
|
@@ -123,15 +121,15 @@
|
|
|
123
121
|
value={displayedPrompt}
|
|
124
122
|
rows={8}
|
|
125
123
|
class="w-full bg-surface2/50 border border-border2/50 rounded-lg px-3 py-2 text-xs font-mono text-text2 outline-none resize-none cursor-default"
|
|
126
|
-
placeholder="
|
|
124
|
+
placeholder="Auto-generated prompt"
|
|
127
125
|
></textarea>
|
|
128
|
-
<div class="text-[8px] font-mono text-text2/50">
|
|
126
|
+
<div class="text-[8px] font-mono text-text2/50">auto-generated prompt — click customize to edit</div>
|
|
129
127
|
{:else}
|
|
130
128
|
<textarea
|
|
131
129
|
bind:value={systemPrompt}
|
|
132
130
|
rows={5}
|
|
133
131
|
class="w-full bg-surface2 border border-border2 rounded-lg px-3 py-2 text-xs font-mono text-text1 outline-none resize-none focus:border-accent/50 transition-colors placeholder:text-text2/40"
|
|
134
|
-
placeholder="
|
|
132
|
+
placeholder="System instructions for the agent…"
|
|
135
133
|
></textarea>
|
|
136
134
|
{/if}
|
|
137
135
|
</div>
|
|
@@ -184,19 +182,6 @@
|
|
|
184
182
|
class="w-full accent-accent" />
|
|
185
183
|
</div>
|
|
186
184
|
|
|
187
|
-
<!-- Max tools (WASM only) -->
|
|
188
|
-
{#if modelType === 'wasm'}
|
|
189
|
-
<div>
|
|
190
|
-
<div class="flex justify-between items-baseline mb-1">
|
|
191
|
-
<span class="text-[9px] font-mono text-text2 uppercase tracking-wider">Max tools (WASM)</span>
|
|
192
|
-
<span class="font-mono text-xs text-text1">{maxTools}</span>
|
|
193
|
-
</div>
|
|
194
|
-
<input type="range" bind:value={maxTools}
|
|
195
|
-
min={4} max={20} step={1}
|
|
196
|
-
class="w-full accent-accent" />
|
|
197
|
-
</div>
|
|
198
|
-
{/if}
|
|
199
|
-
|
|
200
185
|
<!-- Max result length -->
|
|
201
186
|
<div>
|
|
202
187
|
<div class="flex justify-between items-baseline mb-1">
|
|
@@ -212,7 +197,7 @@
|
|
|
212
197
|
<div>
|
|
213
198
|
<label class="flex items-center gap-2 cursor-pointer select-none mb-1">
|
|
214
199
|
<input type="checkbox" bind:checked={compressHistory} class="accent-accent w-3.5 h-3.5" />
|
|
215
|
-
<span class="text-[9px] font-mono text-text2 uppercase tracking-wider">
|
|
200
|
+
<span class="text-[9px] font-mono text-text2 uppercase tracking-wider">Truncate history</span>
|
|
216
201
|
</label>
|
|
217
202
|
{#if compressHistory}
|
|
218
203
|
<div class="flex justify-between items-baseline mb-1">
|
|
@@ -237,7 +222,7 @@
|
|
|
237
222
|
</div>
|
|
238
223
|
<div class="pl-5">
|
|
239
224
|
<div class="flex justify-between items-baseline mb-1">
|
|
240
|
-
<span class="text-[9px] font-mono text-text2 uppercase tracking-wider">
|
|
225
|
+
<span class="text-[9px] font-mono text-text2 uppercase tracking-wider">Inline residue (chars)</span>
|
|
241
226
|
<span class="font-mono text-xs text-text1">{ragResidueSize}</span>
|
|
242
227
|
</div>
|
|
243
228
|
<input type="range" bind:value={ragResidueSize}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Server {
|
|
3
|
+
id: string;
|
|
4
|
+
label: string;
|
|
5
|
+
description: string;
|
|
6
|
+
widgetCount: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
servers: Server[];
|
|
11
|
+
enabledServers?: Set<string>;
|
|
12
|
+
onToggle?: (id: string) => void;
|
|
13
|
+
recipeCountByServer?: Record<string, number>;
|
|
14
|
+
toolCountByServer?: Record<string, number>;
|
|
15
|
+
onrecipeclick?: (id: string) => void;
|
|
16
|
+
ontoolclick?: (id: string) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let {
|
|
20
|
+
servers,
|
|
21
|
+
enabledServers = new Set<string>(),
|
|
22
|
+
onToggle,
|
|
23
|
+
recipeCountByServer,
|
|
24
|
+
toolCountByServer,
|
|
25
|
+
onrecipeclick,
|
|
26
|
+
ontoolclick,
|
|
27
|
+
}: Props = $props();
|
|
28
|
+
|
|
29
|
+
let collapsed = $state(true);
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<div class="flex flex-col gap-2">
|
|
33
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
34
|
+
<div class="flex items-center gap-1 cursor-pointer select-none"
|
|
35
|
+
onclick={() => collapsed = !collapsed}>
|
|
36
|
+
<span class="text-[9px] font-mono text-text2 uppercase tracking-wider">WebMCP servers</span>
|
|
37
|
+
<span class="text-[9px] text-text2/60 font-mono">({enabledServers.size}/{servers.length})</span>
|
|
38
|
+
<span class="text-[10px] text-text2 ml-auto transition-transform {collapsed ? '' : 'rotate-90'}">{@html '▶'}</span>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
{#if !collapsed}
|
|
42
|
+
<div class="flex flex-col gap-1">
|
|
43
|
+
{#each servers as srv (srv.id)}
|
|
44
|
+
{@const enabled = enabledServers.has(srv.id)}
|
|
45
|
+
{@const recipes = recipeCountByServer?.[srv.id] ?? 0}
|
|
46
|
+
{@const tools = toolCountByServer?.[srv.id] ?? 0}
|
|
47
|
+
<div class="group flex items-center gap-2 px-2 py-1.5 rounded border border-border2 bg-surface2 hover:border-accent/30 transition-colors">
|
|
48
|
+
<input
|
|
49
|
+
type="checkbox"
|
|
50
|
+
checked={enabled}
|
|
51
|
+
onchange={() => onToggle?.(srv.id)}
|
|
52
|
+
class="w-3.5 h-3.5 rounded border-border2 accent-accent cursor-pointer flex-shrink-0"
|
|
53
|
+
/>
|
|
54
|
+
<div class="flex-1 min-w-0 flex flex-col">
|
|
55
|
+
<span class="font-mono text-xs font-medium text-text1 truncate">{srv.label}</span>
|
|
56
|
+
{#if srv.description}
|
|
57
|
+
<span class="text-[10px] text-text2 truncate">{srv.description}</span>
|
|
58
|
+
{/if}
|
|
59
|
+
{#if enabled && (recipes > 0 || tools > 0)}
|
|
60
|
+
<span class="flex items-center gap-1.5 mt-0.5">
|
|
61
|
+
{#if recipes > 0}
|
|
62
|
+
<button class="text-[10px] font-mono text-accent hover:underline"
|
|
63
|
+
onclick={(e) => { e.stopPropagation(); onrecipeclick?.(srv.id); }}>
|
|
64
|
+
{recipes} recipes
|
|
65
|
+
</button>
|
|
66
|
+
{/if}
|
|
67
|
+
{#if recipes > 0 && tools > 0}
|
|
68
|
+
<span class="text-[10px] text-text2">·</span>
|
|
69
|
+
{/if}
|
|
70
|
+
{#if tools > 0}
|
|
71
|
+
<button class="text-[10px] font-mono text-accent hover:underline"
|
|
72
|
+
onclick={(e) => { e.stopPropagation(); ontoolclick?.(srv.id); }}>
|
|
73
|
+
{tools} tools
|
|
74
|
+
</button>
|
|
75
|
+
{/if}
|
|
76
|
+
</span>
|
|
77
|
+
{/if}
|
|
78
|
+
</div>
|
|
79
|
+
<span class="text-[9px] font-mono text-text2/50 flex-shrink-0">{srv.widgetCount}w</span>
|
|
80
|
+
</div>
|
|
81
|
+
{/each}
|
|
82
|
+
</div>
|
|
83
|
+
{/if}
|
|
84
|
+
</div>
|
package/src/index.ts
CHANGED
|
@@ -95,6 +95,7 @@ export type { ChatFeedItem, ChatBubble, ChatBlock } from './agent/ChatPanel.svel
|
|
|
95
95
|
export { default as AgentConsole } from './agent/AgentConsole.svelte';
|
|
96
96
|
export { default as SettingsPanel } from './agent/SettingsPanel.svelte';
|
|
97
97
|
export { default as RemoteMCPserversDemo } from './agent/RemoteMCPserversDemo.svelte';
|
|
98
|
+
export { default as WebMCPserversList } from './agent/WebMCPserversList.svelte';
|
|
98
99
|
export { default as EphemeralBubble } from './agent/EphemeralBubble.svelte';
|
|
99
100
|
export { default as TokenBubble } from './agent/TokenBubble.svelte';
|
|
100
101
|
export { default as DiagnosticModal } from './agent/DiagnosticModal.svelte';
|