@webmcp-auto-ui/ui 2.5.26 → 2.5.27
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/README.md +2 -2
- package/package.json +1 -1
- package/src/agent/AgentConsole.svelte +1 -21
- package/src/agent/LLMSelector.svelte +18 -8
- package/src/agent/{GemmaLoader.svelte → ModelLoader.svelte} +1 -1
- package/src/agent/SettingsPanel.svelte +16 -2
- package/src/index.ts +3 -1
- package/src/widgets/WidgetRenderer.svelte +4 -11
- package/src/widgets/rich/D3Widget.svelte +7 -2
- package/src/widgets/rich/Sankey.svelte +22 -4
- package/src/wm/FloatingLayout.svelte +2 -0
- package/src/wm/LinkIndicators.svelte +8 -15
package/README.md
CHANGED
|
@@ -25,9 +25,9 @@ Higher-level components with more complex data shapes and interactivity.
|
|
|
25
25
|
Layout containers for multi-pane interfaces. `TilingLayout` uses a Fibonacci spiral. `FlexLayout` provides an auto-grid layout with a size slider for adjusting block dimensions. `FloatingLayout` supports collapse/expand (double-click) and a fit-to-content button.
|
|
26
26
|
|
|
27
27
|
### Agent UI widgets
|
|
28
|
-
`
|
|
28
|
+
`ModelLoader` · `TokenBubble` · `EphemeralBubble` · `RemoteMCPserversDemo` · `SettingsPanel`
|
|
29
29
|
|
|
30
|
-
`
|
|
30
|
+
`ModelLoader` — floating overlay with progress stream, auto-collapses to a pill once model is loaded. `TokenBubble` — real-time metrics display (req/min, input tokens/min, output tokens/min, cached tokens). `EphemeralBubble` — transient notification bubble (moved from app to package). `RemoteMCPserversDemo` — MCP server discovery component listing available demo servers. `SettingsPanel` — sliders with dynamic ranges for temperature, topK, and maxTokens controls.
|
|
31
31
|
|
|
32
32
|
### WidgetRenderer
|
|
33
33
|
|
package/package.json
CHANGED
|
@@ -48,12 +48,6 @@
|
|
|
48
48
|
setTimeout(() => { copyLabel = 'copy'; }, 2000);
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
/** Extract provenance tag from tool log detail */
|
|
52
|
-
function parseProvenance(detail: string): { tag: 'recette' | 'impro' | null; rest: string } {
|
|
53
|
-
if (detail.startsWith('[recette] ')) return { tag: 'recette', rest: detail.slice(10) };
|
|
54
|
-
if (detail.startsWith('[impro] ')) return { tag: 'impro', rest: detail.slice(8) };
|
|
55
|
-
return { tag: null, rest: detail };
|
|
56
|
-
}
|
|
57
51
|
</script>
|
|
58
52
|
|
|
59
53
|
<div class="agent-console {cls}">
|
|
@@ -82,13 +76,7 @@
|
|
|
82
76
|
tabindex={log.detail.length > 80 ? 0 : undefined}>
|
|
83
77
|
<span class="ac-ts">{fmtTime(log.ts)}</span>
|
|
84
78
|
<span class="ac-type" style="color:{typeColor[log.type] ?? 'var(--color-text2, #888)'}">{log.type}</span>
|
|
85
|
-
{#if log.type === '
|
|
86
|
-
{@const prov = parseProvenance(log.detail)}
|
|
87
|
-
{#if prov.tag}
|
|
88
|
-
<span class="ac-tag" class:ac-tag-recette={prov.tag === 'recette'} class:ac-tag-impro={prov.tag === 'impro'}>{prov.tag}</span>
|
|
89
|
-
{/if}
|
|
90
|
-
<span class="ac-detail">{prov.rest}</span>
|
|
91
|
-
{:else if log.type === 'recipe'}
|
|
79
|
+
{#if log.type === 'recipe'}
|
|
92
80
|
{@const sepIdx = log.detail.indexOf(' · ')}
|
|
93
81
|
{@const rid = sepIdx > 0 ? log.detail.slice(0, sepIdx) : log.detail}
|
|
94
82
|
{@const rest = sepIdx > 0 ? log.detail.slice(sepIdx + 3) : ''}
|
|
@@ -242,14 +230,6 @@
|
|
|
242
230
|
border-radius: 2px;
|
|
243
231
|
line-height: 1.6;
|
|
244
232
|
}
|
|
245
|
-
.ac-tag-recette {
|
|
246
|
-
color: #4ade80;
|
|
247
|
-
background: rgba(74, 222, 128, 0.1);
|
|
248
|
-
}
|
|
249
|
-
.ac-tag-impro {
|
|
250
|
-
color: #fb923c;
|
|
251
|
-
background: rgba(251, 146, 60, 0.1);
|
|
252
|
-
}
|
|
253
233
|
.ac-tag-recipe {
|
|
254
234
|
color: #ec4899;
|
|
255
235
|
background: rgba(236, 72, 153, 0.1);
|
|
@@ -1,20 +1,30 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { listTransformersModels } from '@webmcp-auto-ui/agent';
|
|
3
|
+
|
|
2
4
|
export interface ModelOption {
|
|
3
5
|
value: string;
|
|
4
6
|
label: string;
|
|
5
|
-
group: 'remote' | 'wasm' | 'local';
|
|
7
|
+
group: 'remote' | 'wasm' | 'transformers' | 'local';
|
|
6
8
|
}
|
|
7
9
|
|
|
10
|
+
const transformersOptions: ModelOption[] = listTransformersModels().map(({ id, entry }) => ({
|
|
11
|
+
value: id,
|
|
12
|
+
label: entry.label,
|
|
13
|
+
group: 'transformers' as const,
|
|
14
|
+
}));
|
|
15
|
+
|
|
8
16
|
const DEFAULT_MODELS: ModelOption[] = [
|
|
9
|
-
{ value: 'haiku',
|
|
10
|
-
{ value: 'gemma-e2b',
|
|
11
|
-
{ value: 'gemma-e4b',
|
|
17
|
+
{ value: 'haiku', label: 'claude-haiku-4-5', group: 'remote' },
|
|
18
|
+
{ value: 'gemma-e2b', label: 'Gemma E2B (MediaPipe)', group: 'wasm' },
|
|
19
|
+
{ value: 'gemma-e4b', label: 'Gemma E4B (MediaPipe)', group: 'wasm' },
|
|
20
|
+
...transformersOptions,
|
|
12
21
|
];
|
|
13
22
|
|
|
14
23
|
const GROUP_LABELS: Record<string, string> = {
|
|
15
|
-
remote:
|
|
16
|
-
wasm:
|
|
17
|
-
|
|
24
|
+
remote: 'Remote',
|
|
25
|
+
wasm: 'In-Browser (MediaPipe)',
|
|
26
|
+
transformers: 'In-Browser (Transformers.js)',
|
|
27
|
+
local: 'Local',
|
|
18
28
|
};
|
|
19
29
|
|
|
20
30
|
interface Props {
|
|
@@ -26,7 +36,7 @@
|
|
|
26
36
|
let { value, onchange, models = DEFAULT_MODELS, class: cls = '' }: Props = $props();
|
|
27
37
|
|
|
28
38
|
const groups = $derived(
|
|
29
|
-
['remote', 'wasm', 'local'].filter(g => models.some(m => m.group === g))
|
|
39
|
+
['remote', 'wasm', 'transformers', 'local'].filter(g => models.some(m => m.group === g))
|
|
30
40
|
);
|
|
31
41
|
</script>
|
|
32
42
|
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
compressPreview?: number;
|
|
12
12
|
contextRAGEnabled?: boolean;
|
|
13
13
|
ragResidueSize?: number;
|
|
14
|
+
visualTrace?: boolean;
|
|
14
15
|
cacheEnabled?: boolean;
|
|
15
16
|
temperature?: number;
|
|
16
17
|
topK?: number;
|
|
@@ -29,6 +30,7 @@
|
|
|
29
30
|
compressPreview = $bindable(500),
|
|
30
31
|
contextRAGEnabled = $bindable(false),
|
|
31
32
|
ragResidueSize = $bindable(200),
|
|
33
|
+
visualTrace = $bindable(false),
|
|
32
34
|
cacheEnabled = $bindable(true),
|
|
33
35
|
temperature = $bindable(0.7),
|
|
34
36
|
topK = $bindable(10),
|
|
@@ -186,10 +188,10 @@
|
|
|
186
188
|
<div>
|
|
187
189
|
<div class="flex justify-between items-baseline mb-1">
|
|
188
190
|
<span class="text-[9px] font-mono text-text2 uppercase tracking-wider">Max result (chars)</span>
|
|
189
|
-
<span class="font-mono text-xs text-text1">{formatNumber(maxResultLength)}</span>
|
|
191
|
+
<span class="font-mono text-xs text-text1">{maxResultLength >= 50000 ? '∞' : formatNumber(maxResultLength)}</span>
|
|
190
192
|
</div>
|
|
191
193
|
<input type="range" bind:value={maxResultLength}
|
|
192
|
-
min={500} max={
|
|
194
|
+
min={500} max={50000} step={500}
|
|
193
195
|
class="w-full accent-accent" />
|
|
194
196
|
</div>
|
|
195
197
|
|
|
@@ -230,6 +232,18 @@
|
|
|
230
232
|
class="w-full accent-accent" />
|
|
231
233
|
</div>
|
|
232
234
|
{/if}
|
|
235
|
+
|
|
236
|
+
<!-- Visual trace -->
|
|
237
|
+
<label class="flex items-center gap-2.5 cursor-pointer select-none">
|
|
238
|
+
<input type="checkbox" bind:checked={visualTrace} class="accent-accent w-3.5 h-3.5" />
|
|
239
|
+
<span class="text-xs font-mono text-text1">Visual trace</span>
|
|
240
|
+
<span class="text-[8px] font-mono text-text2/40 ml-auto">experimental</span>
|
|
241
|
+
</label>
|
|
242
|
+
{#if visualTrace}
|
|
243
|
+
<div class="text-[9px] font-mono text-text2/60 pl-5 mb-2">
|
|
244
|
+
Live DAG of agent execution
|
|
245
|
+
</div>
|
|
246
|
+
{/if}
|
|
233
247
|
</section>
|
|
234
248
|
|
|
235
249
|
<!-- Cache (disabled for WASM/Gemma — prompt caching is provider-dependent) -->
|
package/src/index.ts
CHANGED
|
@@ -86,7 +86,9 @@ export type { BusMessage } from './messaging/bus.svelte.js';
|
|
|
86
86
|
|
|
87
87
|
// Agent UI components
|
|
88
88
|
export { default as LLMSelector } from './agent/LLMSelector.svelte';
|
|
89
|
-
export { default as
|
|
89
|
+
export { default as ModelLoader } from './agent/ModelLoader.svelte';
|
|
90
|
+
/** @deprecated Use ModelLoader instead. Alias maintained for backward compatibility. */
|
|
91
|
+
export { default as GemmaLoader } from './agent/ModelLoader.svelte';
|
|
90
92
|
export { default as McpStatus } from './agent/McpStatus.svelte';
|
|
91
93
|
export { default as AgentProgress } from './agent/AgentProgress.svelte';
|
|
92
94
|
export { default as McpConnector } from './agent/McpConnector.svelte';
|
|
@@ -249,22 +249,15 @@
|
|
|
249
249
|
{/if}
|
|
250
250
|
|
|
251
251
|
<style>
|
|
252
|
-
.vanilla-container :global(svg)
|
|
252
|
+
.vanilla-container > :global(svg),
|
|
253
|
+
.vanilla-container > :global(canvas),
|
|
254
|
+
.vanilla-container > :global(img) {
|
|
253
255
|
width: 100%;
|
|
254
256
|
height: auto;
|
|
255
257
|
max-height: 100%;
|
|
256
258
|
display: block;
|
|
257
259
|
}
|
|
258
|
-
.vanilla-container :global(
|
|
259
|
-
width: 100%;
|
|
260
|
-
height: auto;
|
|
261
|
-
max-height: 100%;
|
|
262
|
-
display: block;
|
|
263
|
-
}
|
|
264
|
-
.vanilla-container :global(img) {
|
|
265
|
-
width: 100%;
|
|
266
|
-
height: auto;
|
|
267
|
-
max-height: 100%;
|
|
260
|
+
.vanilla-container > :global(img) {
|
|
268
261
|
object-fit: contain;
|
|
269
262
|
}
|
|
270
263
|
</style>
|
|
@@ -227,6 +227,11 @@
|
|
|
227
227
|
const w = width || 400;
|
|
228
228
|
const h = Math.max(250, Math.round(w * 0.65));
|
|
229
229
|
|
|
230
|
+
// Truncate labels so they stay inside the viewBox. Hover <title> shows the full label.
|
|
231
|
+
const maxChars = Math.max(10, Math.floor(w / 120));
|
|
232
|
+
const truncate = (text: string): string =>
|
|
233
|
+
text.length > maxChars ? text.slice(0, Math.max(1, maxChars - 1)) + '…' : text;
|
|
234
|
+
|
|
230
235
|
const accent = cssVar('--color-accent', '#6c5ce7');
|
|
231
236
|
const accent2 = cssVar('--color-accent2', '#e17055');
|
|
232
237
|
const groups = Array.from(new Set(nodes.map((n) => n.group ?? 0)));
|
|
@@ -295,9 +300,9 @@
|
|
|
295
300
|
.attr('y', 4)
|
|
296
301
|
.attr('font-size', '10px')
|
|
297
302
|
.attr('fill', 'var(--color-text1, #111)')
|
|
298
|
-
.text((n) => n.label ?? n.id);
|
|
303
|
+
.text((n) => truncate(String(n.label ?? n.id)));
|
|
299
304
|
|
|
300
|
-
node.append('title').text((n) => n.label ?? n.id);
|
|
305
|
+
node.append('title').text((n) => String(n.label ?? n.id));
|
|
301
306
|
|
|
302
307
|
sim.on('tick', () => {
|
|
303
308
|
link
|
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
export interface SankeyNode { id: string; label: string; color?: string; }
|
|
2
|
+
export interface SankeyNode { id: string; label: string; color?: string; summary?: string; }
|
|
3
3
|
export interface SankeyLink { source: string; target: string; value: number; label?: string; }
|
|
4
4
|
export interface SankeySpec { title?: string; nodes?: SankeyNode[]; links?: SankeyLink[]; }
|
|
5
5
|
interface Props { spec: Partial<SankeySpec>; onnodeclick?: (n: SankeyNode) => void; onlinkclick?: (l: SankeyLink) => void; }
|
|
6
6
|
let { spec, onnodeclick, onlinkclick }: Props = $props();
|
|
7
|
+
let host: HTMLElement | undefined = $state();
|
|
7
8
|
const nodes=$derived<SankeyNode[]>(Array.isArray(spec.nodes)?spec.nodes:[]);
|
|
8
9
|
const links=$derived<SankeyLink[]>(Array.isArray(spec.links)?spec.links:[]);
|
|
9
10
|
const nodeMap=$derived(new Map(nodes.map(n=>[n.id,n])));
|
|
10
11
|
const maxVal=$derived(Math.max(...links.map(l=>l.value),1));
|
|
11
12
|
const sorted=$derived([...links].sort((a,b)=>b.value-a.value));
|
|
13
|
+
function dispatchNodeDblclick(node: SankeyNode | undefined) {
|
|
14
|
+
if (!node || !host) return;
|
|
15
|
+
host.dispatchEvent(new CustomEvent('widget:node-dblclick', {
|
|
16
|
+
detail: { nodeId: node.id, nodeData: node },
|
|
17
|
+
bubbles: true,
|
|
18
|
+
}));
|
|
19
|
+
}
|
|
12
20
|
</script>
|
|
13
|
-
<div class="bg-surface border border-border rounded-lg p-3 md:p-4 font-sans">
|
|
21
|
+
<div bind:this={host} class="bg-surface border border-border rounded-lg p-3 md:p-4 font-sans">
|
|
14
22
|
{#if spec.title}<h3 class="text-sm font-semibold text-text1 mb-3">{spec.title}</h3>{/if}
|
|
15
23
|
{#if !nodes.length||!links.length}<p class="text-text2 text-sm">No flow data</p>
|
|
16
24
|
{:else}
|
|
@@ -25,11 +33,21 @@
|
|
|
25
33
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
26
34
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
27
35
|
<div class="flex items-center gap-2 text-xs cursor-pointer hover:opacity-80 transition-opacity" role="button" tabindex="0" onclick={()=>onlinkclick?.(link)} onkeydown={(e)=>{if(e.key==='Enter')onlinkclick?.(link)}}>
|
|
28
|
-
<span
|
|
36
|
+
<span
|
|
37
|
+
class="text-text2 min-w-[80px] truncate font-mono"
|
|
38
|
+
style="color:{sc};"
|
|
39
|
+
title={src?.summary ?? src?.label ?? link.source}
|
|
40
|
+
ondblclick={(e)=>{e.stopPropagation(); dispatchNodeDblclick(src); onnodeclick?.(src!);}}
|
|
41
|
+
>{src?.label??link.source}</span>
|
|
29
42
|
<div class="flex-1 bg-surface2 rounded-full overflow-hidden" style="height:{barH}px;">
|
|
30
43
|
<div class="rounded-full h-full" style="width:{pct}%;background:linear-gradient(to right,{sc},{tc});"></div>
|
|
31
44
|
</div>
|
|
32
|
-
<span
|
|
45
|
+
<span
|
|
46
|
+
class="text-text2 min-w-[80px] truncate text-right font-mono"
|
|
47
|
+
style="color:{tc};"
|
|
48
|
+
title={tgt?.summary ?? tgt?.label ?? link.target}
|
|
49
|
+
ondblclick={(e)=>{e.stopPropagation(); dispatchNodeDblclick(tgt); onnodeclick?.(tgt!);}}
|
|
50
|
+
>{tgt?.label??link.target}</span>
|
|
33
51
|
<span class="text-text2 min-w-[40px] text-right font-mono">{link.value}</span>
|
|
34
52
|
</div>
|
|
35
53
|
{/each}
|
|
@@ -125,6 +125,7 @@
|
|
|
125
125
|
|
|
126
126
|
function resizeStart(id: string, e: MouseEvent) {
|
|
127
127
|
e.stopPropagation();
|
|
128
|
+
e.preventDefault();
|
|
128
129
|
rdrag = id; rsx = e.clientX; rsy = e.clientY;
|
|
129
130
|
const lw = lmap.get(id); rww = lw?.width ?? defaultWidth; rwh = lw?.height ?? defaultHeight;
|
|
130
131
|
focus(id);
|
|
@@ -181,6 +182,7 @@
|
|
|
181
182
|
<!-- ── Desktop: scrollable floating canvas ─────────────────────────────── -->
|
|
182
183
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
183
184
|
<div class="absolute inset-0 overflow-auto"
|
|
185
|
+
class:select-none={drag !== null || rdrag !== null}
|
|
184
186
|
onmousemove={mm} onmouseup={mu} onmouseleave={mu}>
|
|
185
187
|
<div class="relative" style="width:{canvasW}px;height:{canvasH}px;">
|
|
186
188
|
{#each windows as win (win.id)}
|
|
@@ -8,8 +8,10 @@
|
|
|
8
8
|
* Usage: place inside a title bar div. The component renders inline
|
|
9
9
|
* (flex items) and is invisible when the widget has no links.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
11
|
+
* Consumes the FONC bus link API: `bus.hasLinks(busId)` and
|
|
12
|
+
* `bus.getLinks(busId)` (see packages/ui/src/messaging/bus.svelte.ts).
|
|
13
|
+
* `getLinks` returns `string[]` of group IDs; the first one drives the
|
|
14
|
+
* indicator color.
|
|
13
15
|
*/
|
|
14
16
|
import { bus } from '../messaging/bus.svelte.js';
|
|
15
17
|
import { groupColor } from './link-utils.js';
|
|
@@ -20,22 +22,13 @@
|
|
|
20
22
|
}
|
|
21
23
|
let { busId }: Props = $props();
|
|
22
24
|
|
|
23
|
-
// ── Reactive link state
|
|
24
|
-
const linked = $derived(
|
|
25
|
-
typeof (bus as any).hasLinks === 'function' ? (bus as any).hasLinks(busId) : false
|
|
26
|
-
);
|
|
25
|
+
// ── Reactive link state ────────────────────────────────────────────
|
|
26
|
+
const linked = $derived(bus.hasLinks(busId));
|
|
27
27
|
|
|
28
|
-
const links = $derived(
|
|
29
|
-
typeof (bus as any).getLinks === 'function' ? (bus as any).getLinks(busId) : []
|
|
30
|
-
);
|
|
28
|
+
const links = $derived(bus.getLinks(busId));
|
|
31
29
|
|
|
32
30
|
/** First group ID (a widget may belong to multiple groups) */
|
|
33
|
-
const groupId = $derived.
|
|
34
|
-
if (!Array.isArray(links) || links.length === 0) return null;
|
|
35
|
-
// Each link has a groupId property
|
|
36
|
-
const first = links[0];
|
|
37
|
-
return typeof first === 'object' && first?.groupId ? String(first.groupId) : null;
|
|
38
|
-
});
|
|
31
|
+
const groupId = $derived(links.length > 0 ? links[0] : null);
|
|
39
32
|
|
|
40
33
|
const color = $derived(groupId ? groupColor(groupId) : 'transparent');
|
|
41
34
|
|