@webmcp-auto-ui/sdk 2.5.28 → 2.5.30
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 +1 -1
- package/src/index.ts +2 -1
- package/src/stores/canvas.ts +210 -157
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -44,7 +44,8 @@ import { encode, decode, hash, diff } from './hyperskills.js';
|
|
|
44
44
|
export async function encodeHyperSkill(skill: HyperSkill, sourceUrl?: string): Promise<string> {
|
|
45
45
|
const base = sourceUrl ?? (typeof window !== 'undefined' ? window.location.href.split('?')[0] : 'https://example.com');
|
|
46
46
|
const json = JSON.stringify(skill);
|
|
47
|
-
|
|
47
|
+
// Skip gzip for small payloads — overhead exceeds savings under ~1KB.
|
|
48
|
+
return encode(base, json, { compress: json.length < 1024 ? 'none' : 'gz' });
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
export async function decodeHyperSkill(urlOrParam: string): Promise<HyperSkill> {
|
package/src/stores/canvas.ts
CHANGED
|
@@ -1,12 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Canvas state store — Vanilla (framework-agnostic)
|
|
3
|
-
* Manages widgets on the canvas, mode, MCP
|
|
4
|
-
*
|
|
5
|
-
* This is the canonical, framework-agnostic version of the canvas store.
|
|
6
|
-
* For Svelte 5 reactivity, use the Svelte wrapper (canvas.svelte.ts)
|
|
7
|
-
* which re-exports this store with $state/$derived reactivity.
|
|
3
|
+
* Manages widgets on the canvas, mode, MCP connections, chat history.
|
|
8
4
|
*
|
|
9
5
|
* Reactivity: subscribe(fn) / getSnapshot() pattern (useSyncExternalStore compatible).
|
|
6
|
+
*
|
|
7
|
+
* ---------------------------------------------------------------------------
|
|
8
|
+
* Unified server model (2026-04-23 debloat)
|
|
9
|
+
* ---------------------------------------------------------------------------
|
|
10
|
+
*
|
|
11
|
+
* Historically this store had TWO parallel surfaces for MCP servers:
|
|
12
|
+
* - `mcpUrl` / `mcpName` / `mcpConnected` / `mcpConnecting` / `mcpTools`
|
|
13
|
+
* (flat, single-server or comma-joined multi)
|
|
14
|
+
* - `dataServers: DataServer[]` (list, managed by MultiMcpBridge)
|
|
15
|
+
*
|
|
16
|
+
* They were actually the same concept. This file now stores a single list
|
|
17
|
+
* (`_servers`) and derives the flat `mcp*` fields from it. All writes (via
|
|
18
|
+
* `setMcpConnected`, `addDataServer`, etc.) mutate the same underlying list,
|
|
19
|
+
* so tools populated by the agent-MCP path are visible to the notebook / data
|
|
20
|
+
* server consumers and vice-versa.
|
|
21
|
+
*
|
|
22
|
+
* The public API shape is preserved (both `mcp*` and `dataServers` / `addDataServer`)
|
|
23
|
+
* so existing apps keep working without modification.
|
|
10
24
|
*/
|
|
11
25
|
|
|
12
26
|
import { encode, decode } from '../hyperskills.js';
|
|
@@ -16,7 +30,7 @@ export type WidgetType =
|
|
|
16
30
|
| 'stat-card' | 'data-table' | 'timeline' | 'profile' | 'trombinoscope' | 'json-viewer'
|
|
17
31
|
| 'hemicycle' | 'chart-rich' | 'cards' | 'grid-data' | 'sankey' | 'map' | 'log'
|
|
18
32
|
| 'gallery' | 'carousel' | 'd3' | 'js-sandbox'
|
|
19
|
-
| (string & {});
|
|
33
|
+
| (string & {});
|
|
20
34
|
|
|
21
35
|
/** @deprecated Use WidgetType */
|
|
22
36
|
export type BlockType = WidgetType;
|
|
@@ -46,15 +60,23 @@ export interface McpToolInfo {
|
|
|
46
60
|
inputSchema?: Record<string, unknown>;
|
|
47
61
|
}
|
|
48
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Single MCP server entry — the one true shape.
|
|
65
|
+
* `primary: true` marks the "agent MCP" (used by the chat/tool-call path) —
|
|
66
|
+
* at most one server may be primary. Additional servers (data-only) are
|
|
67
|
+
* non-primary but otherwise identical.
|
|
68
|
+
*/
|
|
49
69
|
export interface DataServer {
|
|
50
|
-
name: string; //
|
|
70
|
+
name: string; // user-chosen label
|
|
51
71
|
url: string;
|
|
52
|
-
kind: 'data';
|
|
53
|
-
enabled: boolean; // user intent
|
|
54
|
-
connected: boolean; //
|
|
72
|
+
kind: 'data'; // legacy field, kept for schema stability
|
|
73
|
+
enabled: boolean; // user intent
|
|
74
|
+
connected: boolean; // handshake completed
|
|
75
|
+
connecting?: boolean;
|
|
76
|
+
primary?: boolean; // agent MCP when true
|
|
55
77
|
tools?: McpToolInfo[];
|
|
56
78
|
recipes?: { name: string; description?: string; body?: string }[];
|
|
57
|
-
error?: string;
|
|
79
|
+
error?: string;
|
|
58
80
|
}
|
|
59
81
|
|
|
60
82
|
export interface CanvasSnapshot {
|
|
@@ -79,16 +101,13 @@ export interface CanvasSnapshot {
|
|
|
79
101
|
|
|
80
102
|
type Listener = () => void;
|
|
81
103
|
|
|
82
|
-
function uuid() {
|
|
83
|
-
|
|
84
|
-
}
|
|
104
|
+
function uuid() { return 'w_' + Date.now().toString(36) + Math.random().toString(36).slice(2, 5); }
|
|
105
|
+
function msgId() { return 'm_' + Date.now().toString(36) + Math.random().toString(36).slice(2, 5); }
|
|
85
106
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
107
|
+
const NAME_ALIAS: Record<string, string> = { 'moulineuse': 'Tricoteuses' };
|
|
108
|
+
function aliasName(n: string): string { return NAME_ALIAS[n] ?? n; }
|
|
89
109
|
|
|
90
110
|
function createCanvasVanilla() {
|
|
91
|
-
// ── Subscribers ────────────────────────────────────────────────────────
|
|
92
111
|
const listeners = new Set<Listener>();
|
|
93
112
|
function notify() { listeners.forEach(fn => fn()); }
|
|
94
113
|
|
|
@@ -96,63 +115,175 @@ function createCanvasVanilla() {
|
|
|
96
115
|
let _blocks: Widget[] = [];
|
|
97
116
|
let _mode: Mode = 'drag';
|
|
98
117
|
let _llm: LLMId = 'haiku';
|
|
99
|
-
let _mcpUrl = '';
|
|
100
|
-
let _mcpConnected = false;
|
|
101
|
-
let _mcpConnecting = false;
|
|
102
|
-
let _mcpName = '';
|
|
103
|
-
const MCP_NAME_MAP: Record<string, string> = { 'moulineuse': 'Tricoteuses' };
|
|
104
|
-
let _mcpTools: McpToolInfo[] = [];
|
|
105
118
|
let _messages: ChatMsg[] = [];
|
|
106
119
|
let _generating = false;
|
|
107
|
-
let _statusText = '● no MCP connected';
|
|
108
|
-
let _statusColor = 'text-zinc-600';
|
|
109
120
|
let _themeOverrides: Record<string, string> = {};
|
|
110
121
|
let _enabledServerIds: string[] = ['autoui'];
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
122
|
+
// Single source of truth for MCP servers — agent MCP entries and data-only
|
|
123
|
+
// entries all live here. `primary: true` marks the agent MCP.
|
|
124
|
+
let _servers: DataServer[] = [];
|
|
125
|
+
|
|
126
|
+
// ── Helpers over _servers ──────────────────────────────────────────────
|
|
127
|
+
function primaryServer(): DataServer | undefined {
|
|
128
|
+
return _servers.find((s) => s.primary);
|
|
129
|
+
}
|
|
130
|
+
function connectedServers(): DataServer[] {
|
|
131
|
+
return _servers.filter((s) => s.connected);
|
|
132
|
+
}
|
|
133
|
+
function anyConnecting(): boolean {
|
|
134
|
+
return _servers.some((s) => s.connecting);
|
|
135
|
+
}
|
|
136
|
+
function unionTools(): McpToolInfo[] {
|
|
137
|
+
const out: McpToolInfo[] = [];
|
|
138
|
+
for (const s of _servers) if (s.connected && Array.isArray(s.tools)) out.push(...s.tools);
|
|
139
|
+
return out;
|
|
140
|
+
}
|
|
141
|
+
function displayName(): string {
|
|
142
|
+
const connected = connectedServers();
|
|
143
|
+
if (connected.length === 0) return '';
|
|
144
|
+
if (connected.length === 1) return aliasName(connected[0].name);
|
|
145
|
+
return connected.map((s) => aliasName(s.name)).join(', ');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ── Derived status strings (used by the header / toast) ────────────────
|
|
149
|
+
function statusText(): string {
|
|
150
|
+
if (anyConnecting()) return '● connexion…';
|
|
151
|
+
const errored = _servers.find((s) => s.error && !s.connected);
|
|
152
|
+
if (errored) return `● erreur: ${errored.error}`;
|
|
153
|
+
const connected = connectedServers();
|
|
154
|
+
if (connected.length === 0) return '● no MCP connected';
|
|
155
|
+
return `● ${displayName()} · ${unionTools().length} tools`;
|
|
156
|
+
}
|
|
157
|
+
function statusColor(): string {
|
|
158
|
+
if (anyConnecting()) return 'text-amber-400';
|
|
159
|
+
const errored = _servers.find((s) => s.error && !s.connected);
|
|
160
|
+
if (errored) return 'text-red-400';
|
|
161
|
+
return connectedServers().length > 0 ? 'text-teal-400' : 'text-zinc-600';
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ── Server list actions (public, stable) ───────────────────────────────
|
|
165
|
+
function addDataServer(desc: { name: string; url: string; primary?: boolean }): DataServer {
|
|
166
|
+
const existing = _servers.find((s) => s.name === desc.name);
|
|
167
|
+
if (existing) {
|
|
168
|
+
if (desc.primary && !existing.primary) {
|
|
169
|
+
// Promote: there's only one primary. Demote others.
|
|
170
|
+
_servers = _servers.map((s) => ({ ...s, primary: s.name === desc.name }));
|
|
171
|
+
notify();
|
|
172
|
+
}
|
|
173
|
+
return existing;
|
|
174
|
+
}
|
|
175
|
+
const srv: DataServer = {
|
|
176
|
+
name: desc.name,
|
|
177
|
+
url: desc.url,
|
|
178
|
+
kind: 'data',
|
|
179
|
+
enabled: true,
|
|
180
|
+
connected: false,
|
|
181
|
+
primary: !!desc.primary,
|
|
182
|
+
};
|
|
183
|
+
if (srv.primary) {
|
|
184
|
+
// Demote any existing primary before inserting.
|
|
185
|
+
_servers = _servers.map((s) => ({ ...s, primary: false }));
|
|
186
|
+
}
|
|
187
|
+
_servers = [..._servers, srv];
|
|
119
188
|
notify();
|
|
120
189
|
return srv;
|
|
121
190
|
}
|
|
122
191
|
|
|
123
192
|
function removeDataServer(name: string): boolean {
|
|
124
|
-
const before =
|
|
125
|
-
|
|
126
|
-
if (
|
|
193
|
+
const before = _servers.length;
|
|
194
|
+
_servers = _servers.filter((s) => s.name !== name);
|
|
195
|
+
if (_servers.length !== before) { notify(); return true; }
|
|
127
196
|
return false;
|
|
128
197
|
}
|
|
129
198
|
|
|
130
199
|
function getDataServer(name: string): DataServer | undefined {
|
|
131
|
-
return
|
|
200
|
+
return _servers.find((s) => s.name === name);
|
|
132
201
|
}
|
|
133
202
|
|
|
134
203
|
function setDataServerMeta(name: string, patch: Partial<Omit<DataServer, 'name' | 'url' | 'kind'>>): void {
|
|
135
|
-
const idx =
|
|
204
|
+
const idx = _servers.findIndex((s) => s.name === name);
|
|
136
205
|
if (idx < 0) return;
|
|
137
|
-
|
|
206
|
+
_servers = _servers.map((s, i) => i === idx ? { ...s, ...patch } : s);
|
|
138
207
|
notify();
|
|
139
208
|
}
|
|
140
209
|
|
|
141
210
|
function setDataServerEnabled(name: string, enabled: boolean): boolean {
|
|
142
|
-
const s =
|
|
211
|
+
const s = _servers.find((x) => x.name === name);
|
|
143
212
|
if (!s) return false;
|
|
144
213
|
if (s.enabled === enabled) return true;
|
|
145
|
-
|
|
214
|
+
_servers = _servers.map((x) => x.name === name ? { ...x, enabled } : x);
|
|
146
215
|
notify();
|
|
147
216
|
return true;
|
|
148
217
|
}
|
|
149
218
|
|
|
150
219
|
function toggleDataServer(name: string): boolean {
|
|
151
|
-
const s =
|
|
220
|
+
const s = _servers.find((x) => x.name === name);
|
|
152
221
|
if (!s) return false;
|
|
153
222
|
return setDataServerEnabled(name, !s.enabled);
|
|
154
223
|
}
|
|
155
224
|
|
|
225
|
+
// ── Agent-MCP compatibility layer ──────────────────────────────────────
|
|
226
|
+
// All these mutate the SAME _servers list; `mcpUrl` targets the primary
|
|
227
|
+
// entry, creating one if none exists. Apps can equivalently call
|
|
228
|
+
// addDataServer({primary: true}) + setDataServerMeta(name, ...) directly.
|
|
229
|
+
function ensurePrimary(url?: string): DataServer {
|
|
230
|
+
let p = primaryServer();
|
|
231
|
+
if (p) {
|
|
232
|
+
if (url && p.url !== url) {
|
|
233
|
+
_servers = _servers.map((s) => s.name === p!.name ? { ...s, url } : s);
|
|
234
|
+
}
|
|
235
|
+
return _servers.find((s) => s.primary)!;
|
|
236
|
+
}
|
|
237
|
+
// Create a placeholder primary with a stable name derived from the URL.
|
|
238
|
+
const nm = url ? new URL(url, 'http://local').host || url : 'primary';
|
|
239
|
+
_servers = [..._servers, {
|
|
240
|
+
name: nm, url: url ?? '', kind: 'data', enabled: true, connected: false, primary: true,
|
|
241
|
+
}];
|
|
242
|
+
return _servers[_servers.length - 1]!;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function setMcpUrl(u: string): void {
|
|
246
|
+
// Update the primary server's URL (create one if none).
|
|
247
|
+
ensurePrimary(u);
|
|
248
|
+
notify();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function setMcpConnecting(connecting: boolean): void {
|
|
252
|
+
const p = ensurePrimary();
|
|
253
|
+
_servers = _servers.map((s) => s.name === p.name ? { ...s, connecting } : s);
|
|
254
|
+
notify();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function setMcpConnected(connected: boolean, name?: string, tools?: McpToolInfo[]): void {
|
|
258
|
+
if (!connected) {
|
|
259
|
+
// Disconnect all — agent-level disconnect affects the primary and
|
|
260
|
+
// traditionally cleared the flat tools. Mirror that by disconnecting
|
|
261
|
+
// all primary-flagged servers.
|
|
262
|
+
const p = primaryServer();
|
|
263
|
+
if (p) {
|
|
264
|
+
_servers = _servers.map((s) => s.primary
|
|
265
|
+
? { ...s, connected: false, connecting: false, tools: [], error: undefined }
|
|
266
|
+
: s);
|
|
267
|
+
}
|
|
268
|
+
notify();
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
const p = ensurePrimary();
|
|
272
|
+
const newName = name && name.length > 0 ? name : p.name;
|
|
273
|
+
_servers = _servers.map((s) => s.name === p.name
|
|
274
|
+
? { ...s, name: newName, connected: true, connecting: false, tools: tools ?? s.tools ?? [], error: undefined }
|
|
275
|
+
: s);
|
|
276
|
+
notify();
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function setMcpError(err: string): void {
|
|
280
|
+
const p = ensurePrimary();
|
|
281
|
+
_servers = _servers.map((s) => s.name === p.name
|
|
282
|
+
? { ...s, connected: false, connecting: false, error: err }
|
|
283
|
+
: s);
|
|
284
|
+
notify();
|
|
285
|
+
}
|
|
286
|
+
|
|
156
287
|
// ── Widget actions ─────────────────────────────────────────────────────
|
|
157
288
|
function addWidget(type: WidgetType, data: Record<string, unknown> = {}): Widget {
|
|
158
289
|
const widget: Widget = { id: uuid(), type, data };
|
|
@@ -160,20 +291,15 @@ function createCanvasVanilla() {
|
|
|
160
291
|
notify();
|
|
161
292
|
return widget;
|
|
162
293
|
}
|
|
163
|
-
|
|
164
|
-
/** @deprecated Use addWidget */
|
|
165
294
|
const addBlock = addWidget;
|
|
166
|
-
|
|
167
295
|
function removeBlock(id: string) {
|
|
168
296
|
_blocks = _blocks.filter((b) => b.id !== id);
|
|
169
297
|
notify();
|
|
170
298
|
}
|
|
171
|
-
|
|
172
299
|
function updateBlock(id: string, data: Partial<Record<string, unknown>>) {
|
|
173
300
|
_blocks = _blocks.map((b) => b.id === id ? { ...b, data: { ...b.data, ...data } } : b);
|
|
174
301
|
notify();
|
|
175
302
|
}
|
|
176
|
-
|
|
177
303
|
function moveBlock(fromId: string, toId: string) {
|
|
178
304
|
const fi = _blocks.findIndex((b) => b.id === fromId);
|
|
179
305
|
const ti = _blocks.findIndex((b) => b.id === toId);
|
|
@@ -184,16 +310,8 @@ function createCanvasVanilla() {
|
|
|
184
310
|
_blocks = next;
|
|
185
311
|
notify();
|
|
186
312
|
}
|
|
187
|
-
|
|
188
|
-
function
|
|
189
|
-
_blocks = [];
|
|
190
|
-
notify();
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
function setBlocks(newBlocks: Widget[]) {
|
|
194
|
-
_blocks = newBlocks;
|
|
195
|
-
notify();
|
|
196
|
-
}
|
|
313
|
+
function clearBlocks() { _blocks = []; notify(); }
|
|
314
|
+
function setBlocks(newBlocks: Widget[]) { _blocks = newBlocks; notify(); }
|
|
197
315
|
|
|
198
316
|
// ── Chat ───────────────────────────────────────────────────────────────
|
|
199
317
|
function addMsg(role: ChatMsg['role'], content: string, thinking = false): ChatMsg {
|
|
@@ -202,56 +320,11 @@ function createCanvasVanilla() {
|
|
|
202
320
|
notify();
|
|
203
321
|
return msg;
|
|
204
322
|
}
|
|
205
|
-
|
|
206
323
|
function updateMsg(id: string, content: string, thinking = false) {
|
|
207
324
|
_messages = _messages.map((m) => m.id === id ? { ...m, content, thinking } : m);
|
|
208
325
|
notify();
|
|
209
326
|
}
|
|
210
|
-
|
|
211
|
-
function clearMessages() {
|
|
212
|
-
_messages = [];
|
|
213
|
-
notify();
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// ── MCP ────────────────────────────────────────────────────────────────
|
|
217
|
-
function setMcpConnecting(connecting: boolean) {
|
|
218
|
-
_mcpConnecting = connecting;
|
|
219
|
-
if (connecting) {
|
|
220
|
-
_statusText = '● connexion…';
|
|
221
|
-
_statusColor = 'text-amber-400';
|
|
222
|
-
}
|
|
223
|
-
notify();
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
function setMcpConnected(
|
|
227
|
-
connected: boolean,
|
|
228
|
-
name?: string,
|
|
229
|
-
tools?: McpToolInfo[]
|
|
230
|
-
) {
|
|
231
|
-
_mcpConnected = connected;
|
|
232
|
-
if (connected) {
|
|
233
|
-
if (name) _mcpName = name;
|
|
234
|
-
if (tools) _mcpTools = tools;
|
|
235
|
-
_statusText = `● ${name} · ${tools?.length ?? 0} tools`;
|
|
236
|
-
_statusColor = 'text-teal-400';
|
|
237
|
-
} else {
|
|
238
|
-
// Reset stale connection state on disconnect so the UI doesn't keep
|
|
239
|
-
// advertising the previous server name / tool list.
|
|
240
|
-
_mcpName = '';
|
|
241
|
-
_mcpTools = [];
|
|
242
|
-
_statusText = '● no MCP connected';
|
|
243
|
-
_statusColor = 'text-zinc-600';
|
|
244
|
-
}
|
|
245
|
-
notify();
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
function setMcpError(err: string) {
|
|
249
|
-
_mcpConnected = false;
|
|
250
|
-
_mcpConnecting = false;
|
|
251
|
-
_statusText = `● erreur: ${err}`;
|
|
252
|
-
_statusColor = 'text-red-400';
|
|
253
|
-
notify();
|
|
254
|
-
}
|
|
327
|
+
function clearMessages() { _messages = []; notify(); }
|
|
255
328
|
|
|
256
329
|
// ── Theme ──────────────────────────────────────────────────────────────
|
|
257
330
|
function setThemeOverrides(overrides: Record<string, string>) {
|
|
@@ -259,7 +332,7 @@ function createCanvasVanilla() {
|
|
|
259
332
|
notify();
|
|
260
333
|
}
|
|
261
334
|
|
|
262
|
-
// ── Enabled servers
|
|
335
|
+
// ── Enabled servers (kept for UI server catalogue) ─────────────────────
|
|
263
336
|
function setEnabledServers(ids: string[]) {
|
|
264
337
|
_enabledServerIds = ids;
|
|
265
338
|
notify();
|
|
@@ -267,11 +340,12 @@ function createCanvasVanilla() {
|
|
|
267
340
|
|
|
268
341
|
// ── HyperSkill ─────────────────────────────────────────────────────────
|
|
269
342
|
function buildSkillJSON() {
|
|
343
|
+
const p = primaryServer();
|
|
270
344
|
const skill: Record<string, unknown> = {
|
|
271
345
|
version: '1.0',
|
|
272
346
|
name: 'skill-' + Date.now(),
|
|
273
347
|
created: new Date().toISOString(),
|
|
274
|
-
mcp:
|
|
348
|
+
mcp: p?.url ?? '',
|
|
275
349
|
llm: _llm,
|
|
276
350
|
blocks: _blocks.map((b) => ({ type: b.type, data: JSON.parse(JSON.stringify(b.data)) })),
|
|
277
351
|
};
|
|
@@ -282,7 +356,8 @@ function createCanvasVanilla() {
|
|
|
282
356
|
|
|
283
357
|
async function buildHyperskillParam(): Promise<string> {
|
|
284
358
|
const json = JSON.stringify(buildSkillJSON());
|
|
285
|
-
|
|
359
|
+
// Skip gzip for small payloads — overhead exceeds savings under ~1KB.
|
|
360
|
+
const url = await encode('https://x.local', json, { compress: json.length < 1024 ? 'none' : 'gz' });
|
|
286
361
|
return new URL(url).searchParams.get('hs')!;
|
|
287
362
|
}
|
|
288
363
|
|
|
@@ -293,7 +368,7 @@ function createCanvasVanilla() {
|
|
|
293
368
|
servers?: string[];
|
|
294
369
|
blocks?: { type: WidgetType; data: Record<string, unknown> }[];
|
|
295
370
|
}) {
|
|
296
|
-
if (skill.mcp)
|
|
371
|
+
if (skill.mcp) ensurePrimary(skill.mcp);
|
|
297
372
|
if (skill.llm) _llm = skill.llm;
|
|
298
373
|
if (skill.theme) _themeOverrides = skill.theme;
|
|
299
374
|
if (skill.servers) _enabledServerIds = skill.servers;
|
|
@@ -302,15 +377,11 @@ function createCanvasVanilla() {
|
|
|
302
377
|
}
|
|
303
378
|
notify();
|
|
304
379
|
}
|
|
305
|
-
|
|
306
|
-
// Try hyperskills NPM decode (handles gz., br., and plain base64)
|
|
307
380
|
try {
|
|
308
381
|
const { content: json } = await decode(param);
|
|
309
382
|
applySkill(JSON.parse(json));
|
|
310
383
|
return true;
|
|
311
|
-
} catch { /* fall through
|
|
312
|
-
|
|
313
|
-
// Fallback: legacy format (plain base64 with escape/unescape)
|
|
384
|
+
} catch { /* fall through */ }
|
|
314
385
|
try {
|
|
315
386
|
let b64 = param.replace(/ /g, '+').replace(/-/g, '+').replace(/_/g, '/');
|
|
316
387
|
while (b64.length % 4) b64 += '=';
|
|
@@ -322,7 +393,6 @@ function createCanvasVanilla() {
|
|
|
322
393
|
}
|
|
323
394
|
}
|
|
324
395
|
|
|
325
|
-
// ── loadFromUrl ────────────────────────────────────────────────────────
|
|
326
396
|
async function loadFromUrl(url: string): Promise<boolean> {
|
|
327
397
|
try {
|
|
328
398
|
const { content: raw } = await decode(url);
|
|
@@ -331,12 +401,9 @@ function createCanvasVanilla() {
|
|
|
331
401
|
content?: { blocks?: { type: WidgetType; data: Record<string, unknown> }[] };
|
|
332
402
|
servers?: string[];
|
|
333
403
|
};
|
|
334
|
-
if (decoded.meta?.mcp)
|
|
404
|
+
if (decoded.meta?.mcp) ensurePrimary(decoded.meta.mcp as string);
|
|
335
405
|
if (decoded.meta?.llm) _llm = decoded.meta.llm as LLMId;
|
|
336
406
|
if (decoded.meta?.theme) _themeOverrides = decoded.meta.theme as Record<string, string>;
|
|
337
|
-
// Align with loadFromParam: restore enabledServerIds when `servers` is
|
|
338
|
-
// present. buildSkillJSON emits it at the root, but tolerate it under
|
|
339
|
-
// `meta` as well for forward/back compatibility.
|
|
340
407
|
const servers = (decoded.servers ?? (decoded.meta?.servers as string[] | undefined));
|
|
341
408
|
if (Array.isArray(servers)) _enabledServerIds = servers;
|
|
342
409
|
if (decoded.content?.blocks) _blocks = decoded.content.blocks.map((b) => ({ id: uuid(), type: b.type, data: b.data }));
|
|
@@ -347,89 +414,77 @@ function createCanvasVanilla() {
|
|
|
347
414
|
}
|
|
348
415
|
}
|
|
349
416
|
|
|
350
|
-
// ── Snapshot
|
|
417
|
+
// ── Snapshot (fields kept for API stability) ───────────────────────────
|
|
351
418
|
function getSnapshot(): CanvasSnapshot {
|
|
419
|
+
const p = primaryServer();
|
|
352
420
|
return {
|
|
353
421
|
blocks: _blocks,
|
|
354
422
|
mode: _mode,
|
|
355
423
|
llm: _llm,
|
|
356
|
-
mcpUrl:
|
|
357
|
-
mcpConnected:
|
|
358
|
-
mcpConnecting:
|
|
359
|
-
mcpName:
|
|
360
|
-
mcpTools:
|
|
424
|
+
mcpUrl: p?.url ?? '',
|
|
425
|
+
mcpConnected: p?.connected ?? false,
|
|
426
|
+
mcpConnecting: anyConnecting(),
|
|
427
|
+
mcpName: displayName(),
|
|
428
|
+
mcpTools: unionTools(),
|
|
361
429
|
messages: _messages,
|
|
362
430
|
generating: _generating,
|
|
363
|
-
statusText:
|
|
364
|
-
statusColor:
|
|
431
|
+
statusText: statusText(),
|
|
432
|
+
statusColor: statusColor(),
|
|
365
433
|
themeOverrides: _themeOverrides,
|
|
366
434
|
enabledServerIds: _enabledServerIds,
|
|
367
|
-
dataServers:
|
|
435
|
+
dataServers: _servers,
|
|
368
436
|
blockCount: _blocks.length,
|
|
369
437
|
isEmpty: _blocks.length === 0,
|
|
370
438
|
};
|
|
371
439
|
}
|
|
372
440
|
|
|
373
|
-
// ── Subscribe ──────────────────────────────────────────────────────────
|
|
374
441
|
function subscribe(fn: Listener): () => void {
|
|
375
442
|
listeners.add(fn);
|
|
376
443
|
return () => { listeners.delete(fn); };
|
|
377
444
|
}
|
|
378
445
|
|
|
379
|
-
// ── Return public API ──────────────────────────────────────────────────
|
|
380
446
|
return {
|
|
381
|
-
//
|
|
447
|
+
// Reactive getters (read-side)
|
|
382
448
|
get blocks() { return _blocks; },
|
|
383
449
|
get mode() { return _mode; },
|
|
384
450
|
set mode(v: Mode) { _mode = v; notify(); },
|
|
385
451
|
get llm() { return _llm; },
|
|
386
452
|
set llm(v: LLMId) { _llm = v; notify(); },
|
|
387
|
-
get mcpUrl() { return
|
|
388
|
-
set mcpUrl(v: string) {
|
|
389
|
-
get mcpConnected() { return
|
|
390
|
-
get mcpConnecting() { return
|
|
391
|
-
get mcpName() { return
|
|
392
|
-
get mcpTools() { return
|
|
453
|
+
get mcpUrl() { return primaryServer()?.url ?? ''; },
|
|
454
|
+
set mcpUrl(v: string) { setMcpUrl(v); },
|
|
455
|
+
get mcpConnected() { return primaryServer()?.connected ?? false; },
|
|
456
|
+
get mcpConnecting() { return anyConnecting(); },
|
|
457
|
+
get mcpName() { return displayName(); },
|
|
458
|
+
get mcpTools() { return unionTools(); },
|
|
393
459
|
get messages() { return _messages; },
|
|
394
460
|
get generating() { return _generating; },
|
|
395
461
|
set generating(v: boolean) { _generating = v; notify(); },
|
|
396
|
-
get statusText() { return
|
|
397
|
-
get statusColor() { return
|
|
462
|
+
get statusText() { return statusText(); },
|
|
463
|
+
get statusColor() { return statusColor(); },
|
|
398
464
|
get blockCount() { return _blocks.length; },
|
|
399
465
|
get isEmpty() { return _blocks.length === 0; },
|
|
400
466
|
|
|
401
|
-
// Setters (kept for backward compat)
|
|
402
467
|
setMode(m: Mode) { _mode = m; notify(); },
|
|
403
468
|
setLlm(l: LLMId) { _llm = l; notify(); },
|
|
404
|
-
setMcpUrl
|
|
469
|
+
setMcpUrl,
|
|
405
470
|
setGenerating(g: boolean) { _generating = g; notify(); },
|
|
406
471
|
|
|
407
|
-
|
|
408
|
-
addWidget,
|
|
409
|
-
|
|
410
|
-
// Backward compat alias
|
|
411
|
-
addBlock,
|
|
412
|
-
|
|
413
|
-
// Block actions (kept as-is)
|
|
472
|
+
addWidget, addBlock,
|
|
414
473
|
removeBlock, updateBlock, moveBlock, clearBlocks, setBlocks,
|
|
415
|
-
|
|
416
|
-
// Chat
|
|
417
474
|
addMsg, updateMsg, clearMessages,
|
|
418
475
|
|
|
419
|
-
// MCP
|
|
420
476
|
setMcpConnecting, setMcpConnected, setMcpError,
|
|
421
477
|
|
|
422
|
-
// Theme
|
|
423
478
|
get themeOverrides() { return _themeOverrides; },
|
|
424
479
|
setThemeOverrides,
|
|
425
480
|
|
|
426
|
-
// Enabled servers
|
|
427
481
|
get enabledServerIds() { return _enabledServerIds; },
|
|
428
482
|
setEnabledServers,
|
|
429
483
|
|
|
430
|
-
//
|
|
431
|
-
|
|
432
|
-
|
|
484
|
+
// Server list — unified store. `dataServers` kept as accessor name for
|
|
485
|
+
// schema/API stability; it returns ALL servers (primary + data-only).
|
|
486
|
+
get dataServers() { return _servers; },
|
|
487
|
+
set dataServers(v: DataServer[]) { _servers = Array.isArray(v) ? v : []; notify(); },
|
|
433
488
|
addDataServer,
|
|
434
489
|
removeDataServer,
|
|
435
490
|
getDataServer,
|
|
@@ -437,10 +492,8 @@ function createCanvasVanilla() {
|
|
|
437
492
|
setDataServerEnabled,
|
|
438
493
|
toggleDataServer,
|
|
439
494
|
|
|
440
|
-
// HyperSkill
|
|
441
495
|
buildSkillJSON, buildHyperskillParam, loadFromParam, loadFromUrl,
|
|
442
496
|
|
|
443
|
-
// Framework-agnostic reactivity
|
|
444
497
|
subscribe,
|
|
445
498
|
getSnapshot,
|
|
446
499
|
};
|