@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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webmcp-auto-ui/ui",
3
- "version": "2.5.24",
3
+ "version": "2.5.26",
4
4
  "description": "Svelte 5 UI components — primitives, widgets, window manager",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "type": "module",
@@ -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; // hide token field
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
- <div class="flex items-center gap-2">
87
- <button
88
- onclick={() => showToken = !showToken}
89
- class="text-text2 hover:text-text1 transition-colors flex-shrink-0"
90
- title="Bearer token"
91
- >
92
- <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/></svg>
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">✓ applique</span>
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}>reinitialiser</button>
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}>personnaliser</button>
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="Prompt auto-genere"
124
+ placeholder="Auto-generated prompt"
127
125
  ></textarea>
128
- <div class="text-[8px] font-mono text-text2/50">prompt auto-generecliquer personnaliser pour editer</div>
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="Instructions systeme pour l'agent…"
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">Tronquer l'historique</span>
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">Résidu inline (chars)</span>
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 '&#x25B6;'}</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';