@webmcp-auto-ui/sdk 2.5.27 → 2.5.29
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/canvas-vanilla.ts +1 -1
- package/src/canvas.ts +1 -1
- package/src/hyperskills.ts +5 -1
- package/src/index.ts +5 -3
- package/src/short-url.ts +33 -0
- package/src/stores/canvas.svelte.ts +14 -2
- package/src/stores/canvas.ts +258 -126
package/package.json
CHANGED
package/src/canvas-vanilla.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// Canvas store — Vanilla (framework-agnostic), no Svelte dependency
|
|
2
2
|
export { canvasVanilla } from './stores/canvas.js';
|
|
3
|
-
export type { Widget, WidgetType, Block, BlockType, Mode, LLMId, ChatMsg, McpToolInfo, CanvasSnapshot } from './stores/canvas.js';
|
|
3
|
+
export type { Widget, WidgetType, Block, BlockType, Mode, LLMId, ChatMsg, McpToolInfo, DataServer, CanvasSnapshot } from './stores/canvas.js';
|
package/src/canvas.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// Canvas store — Svelte 5 runes (browser-only, must be imported in Svelte components)
|
|
2
2
|
export { canvas } from './stores/canvas.svelte.js';
|
|
3
|
-
export type { Widget, WidgetType, Block, BlockType, Mode, LLMId, ChatMsg, McpToolInfo } from './stores/canvas.svelte.js';
|
|
3
|
+
export type { Widget, WidgetType, Block, BlockType, Mode, LLMId, ChatMsg, McpToolInfo, DataServer } from './stores/canvas.svelte.js';
|
package/src/hyperskills.ts
CHANGED
|
@@ -32,7 +32,10 @@ async function compressGzip(bytes: Uint8Array): Promise<Uint8Array> {
|
|
|
32
32
|
// @ts-ignore — CompressionStream is part of the DOM lib but may be missing in older TS targets
|
|
33
33
|
const cs = new CompressionStream('gzip');
|
|
34
34
|
const writer = cs.writable.getWriter();
|
|
35
|
-
|
|
35
|
+
// Copy into a fresh Uint8Array to guarantee the underlying buffer is a plain
|
|
36
|
+
// ArrayBuffer (not SharedArrayBuffer/ArrayBufferLike) — required by
|
|
37
|
+
// WritableStreamDefaultWriter<BufferSource> under TS 5.x strict lib.
|
|
38
|
+
writer.write(new Uint8Array(bytes));
|
|
36
39
|
writer.close();
|
|
37
40
|
return new Uint8Array(await new Response(cs.readable).arrayBuffer());
|
|
38
41
|
}
|
|
@@ -87,6 +90,7 @@ export function getHsParam(url?: string): string | null {
|
|
|
87
90
|
return null;
|
|
88
91
|
}
|
|
89
92
|
}
|
|
93
|
+
if (typeof window === 'undefined') return null;
|
|
90
94
|
return hs.getHsParam();
|
|
91
95
|
}
|
|
92
96
|
|
package/src/index.ts
CHANGED
|
@@ -44,9 +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
|
-
//
|
|
48
|
-
|
|
49
|
-
return encode(base, json, compress ? { compress } : {});
|
|
47
|
+
// Skip gzip for small payloads — overhead exceeds savings under ~1KB.
|
|
48
|
+
return encode(base, json, { compress: json.length < 1024 ? 'none' : 'gz' });
|
|
50
49
|
}
|
|
51
50
|
|
|
52
51
|
export async function decodeHyperSkill(urlOrParam: string): Promise<HyperSkill> {
|
|
@@ -94,3 +93,6 @@ export type { McpDemoServer } from './mcp-demo-servers.js';
|
|
|
94
93
|
// Recipe runner — markdown-fence parser + JS/TS/SQL/etc executor over MCP
|
|
95
94
|
export { parseBody, runCode, estimateTokens, safeStringify } from './recipes/index.js';
|
|
96
95
|
export type { ParsedSegment, RunResult, RunLog, RunTab, RecipeData } from './recipes/index.js';
|
|
96
|
+
|
|
97
|
+
// Short URL — domain-dependent compact token
|
|
98
|
+
export { buildShortUrl, getShortToken } from './short-url.js';
|
package/src/short-url.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Domain-dependent short URL — compact token served from the skill's own domain.
|
|
2
|
+
// Not a dedicated subdomain: the skill host resolves `?n=<token>` to the full state.
|
|
3
|
+
|
|
4
|
+
import { hash } from './hyperskills.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Build a short URL from a source URL and the content to share.
|
|
8
|
+
* The short token is a prefix of the content hash, resolved server-side.
|
|
9
|
+
*/
|
|
10
|
+
export async function buildShortUrl(sourceUrl: string, content: string): Promise<string> {
|
|
11
|
+
const h = await hash(sourceUrl, content);
|
|
12
|
+
const token = h.slice(0, 10);
|
|
13
|
+
const u = new URL(sourceUrl);
|
|
14
|
+
u.search = '';
|
|
15
|
+
u.searchParams.set('n', token);
|
|
16
|
+
return u.toString();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Read the short token from a URL or param string. Returns null if absent.
|
|
21
|
+
*/
|
|
22
|
+
export function getShortToken(urlOrParam: string): string | null {
|
|
23
|
+
try {
|
|
24
|
+
if (urlOrParam.startsWith('?') || urlOrParam.includes('=')) {
|
|
25
|
+
const sp = new URLSearchParams(urlOrParam.replace(/^\?/, ''));
|
|
26
|
+
return sp.get('n');
|
|
27
|
+
}
|
|
28
|
+
const u = new URL(urlOrParam);
|
|
29
|
+
return u.searchParams.get('n');
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { canvasVanilla } from './canvas.js';
|
|
10
|
-
import type { Widget, WidgetType, Mode, LLMId, ChatMsg, McpToolInfo } from './canvas.js';
|
|
10
|
+
import type { Widget, WidgetType, Mode, LLMId, ChatMsg, McpToolInfo, DataServer } from './canvas.js';
|
|
11
11
|
|
|
12
12
|
// Re-export types (including deprecated aliases)
|
|
13
|
-
export type { Widget, WidgetType, Mode, LLMId, ChatMsg, McpToolInfo };
|
|
13
|
+
export type { Widget, WidgetType, Mode, LLMId, ChatMsg, McpToolInfo, DataServer };
|
|
14
14
|
export type { Block, BlockType, CanvasSnapshot } from './canvas.js';
|
|
15
15
|
|
|
16
16
|
function createCanvas() {
|
|
@@ -29,6 +29,7 @@ function createCanvas() {
|
|
|
29
29
|
let statusColor = $state(canvasVanilla.statusColor);
|
|
30
30
|
let themeOverrides = $state<Record<string, string>>(canvasVanilla.themeOverrides);
|
|
31
31
|
let enabledServerIds = $state<string[]>(canvasVanilla.enabledServerIds);
|
|
32
|
+
let dataServers = $state<DataServer[]>(canvasVanilla.dataServers);
|
|
32
33
|
|
|
33
34
|
// ── Derived ─────────────────────────────────────────────────────────────
|
|
34
35
|
const blockCount = $derived(blocks.length);
|
|
@@ -51,6 +52,7 @@ function createCanvas() {
|
|
|
51
52
|
statusColor = s.statusColor;
|
|
52
53
|
themeOverrides = s.themeOverrides;
|
|
53
54
|
enabledServerIds = canvasVanilla.enabledServerIds;
|
|
55
|
+
dataServers = canvasVanilla.dataServers;
|
|
54
56
|
});
|
|
55
57
|
|
|
56
58
|
// ── Return public API ───────────────────────────────────────────────────
|
|
@@ -112,6 +114,16 @@ function createCanvas() {
|
|
|
112
114
|
get enabledServerIds() { return enabledServerIds; },
|
|
113
115
|
setEnabledServers: canvasVanilla.setEnabledServers.bind(canvasVanilla),
|
|
114
116
|
|
|
117
|
+
// Data servers (multi-MCP) — additive, coexists with mcp* primary fields
|
|
118
|
+
get dataServers() { return dataServers; },
|
|
119
|
+
set dataServers(v: DataServer[]) { canvasVanilla.dataServers = v; },
|
|
120
|
+
addDataServer: canvasVanilla.addDataServer.bind(canvasVanilla),
|
|
121
|
+
removeDataServer: canvasVanilla.removeDataServer.bind(canvasVanilla),
|
|
122
|
+
getDataServer: canvasVanilla.getDataServer.bind(canvasVanilla),
|
|
123
|
+
setDataServerMeta: canvasVanilla.setDataServerMeta.bind(canvasVanilla),
|
|
124
|
+
setDataServerEnabled: canvasVanilla.setDataServerEnabled.bind(canvasVanilla),
|
|
125
|
+
toggleDataServer: canvasVanilla.toggleDataServer.bind(canvasVanilla),
|
|
126
|
+
|
|
115
127
|
// HyperSkill
|
|
116
128
|
buildSkillJSON: canvasVanilla.buildSkillJSON.bind(canvasVanilla),
|
|
117
129
|
buildHyperskillParam: canvasVanilla.buildHyperskillParam.bind(canvasVanilla),
|
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,6 +60,25 @@ 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
|
+
*/
|
|
69
|
+
export interface DataServer {
|
|
70
|
+
name: string; // user-chosen label
|
|
71
|
+
url: string;
|
|
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
|
|
77
|
+
tools?: McpToolInfo[];
|
|
78
|
+
recipes?: { name: string; description?: string; body?: string }[];
|
|
79
|
+
error?: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
49
82
|
export interface CanvasSnapshot {
|
|
50
83
|
blocks: Widget[];
|
|
51
84
|
mode: Mode;
|
|
@@ -61,22 +94,20 @@ export interface CanvasSnapshot {
|
|
|
61
94
|
statusColor: string;
|
|
62
95
|
themeOverrides: Record<string, string>;
|
|
63
96
|
enabledServerIds: string[];
|
|
97
|
+
dataServers: DataServer[];
|
|
64
98
|
blockCount: number;
|
|
65
99
|
isEmpty: boolean;
|
|
66
100
|
}
|
|
67
101
|
|
|
68
102
|
type Listener = () => void;
|
|
69
103
|
|
|
70
|
-
function uuid() {
|
|
71
|
-
|
|
72
|
-
}
|
|
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); }
|
|
73
106
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
107
|
+
const NAME_ALIAS: Record<string, string> = { 'moulineuse': 'Tricoteuses' };
|
|
108
|
+
function aliasName(n: string): string { return NAME_ALIAS[n] ?? n; }
|
|
77
109
|
|
|
78
110
|
function createCanvasVanilla() {
|
|
79
|
-
// ── Subscribers ────────────────────────────────────────────────────────
|
|
80
111
|
const listeners = new Set<Listener>();
|
|
81
112
|
function notify() { listeners.forEach(fn => fn()); }
|
|
82
113
|
|
|
@@ -84,18 +115,174 @@ function createCanvasVanilla() {
|
|
|
84
115
|
let _blocks: Widget[] = [];
|
|
85
116
|
let _mode: Mode = 'drag';
|
|
86
117
|
let _llm: LLMId = 'haiku';
|
|
87
|
-
let _mcpUrl = '';
|
|
88
|
-
let _mcpConnected = false;
|
|
89
|
-
let _mcpConnecting = false;
|
|
90
|
-
let _mcpName = '';
|
|
91
|
-
const MCP_NAME_MAP: Record<string, string> = { 'moulineuse': 'Tricoteuses' };
|
|
92
|
-
let _mcpTools: McpToolInfo[] = [];
|
|
93
118
|
let _messages: ChatMsg[] = [];
|
|
94
119
|
let _generating = false;
|
|
95
|
-
let _statusText = '● no MCP connected';
|
|
96
|
-
let _statusColor = 'text-zinc-600';
|
|
97
120
|
let _themeOverrides: Record<string, string> = {};
|
|
98
121
|
let _enabledServerIds: string[] = ['autoui'];
|
|
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];
|
|
188
|
+
notify();
|
|
189
|
+
return srv;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function removeDataServer(name: string): boolean {
|
|
193
|
+
const before = _servers.length;
|
|
194
|
+
_servers = _servers.filter((s) => s.name !== name);
|
|
195
|
+
if (_servers.length !== before) { notify(); return true; }
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function getDataServer(name: string): DataServer | undefined {
|
|
200
|
+
return _servers.find((s) => s.name === name);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function setDataServerMeta(name: string, patch: Partial<Omit<DataServer, 'name' | 'url' | 'kind'>>): void {
|
|
204
|
+
const idx = _servers.findIndex((s) => s.name === name);
|
|
205
|
+
if (idx < 0) return;
|
|
206
|
+
_servers = _servers.map((s, i) => i === idx ? { ...s, ...patch } : s);
|
|
207
|
+
notify();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function setDataServerEnabled(name: string, enabled: boolean): boolean {
|
|
211
|
+
const s = _servers.find((x) => x.name === name);
|
|
212
|
+
if (!s) return false;
|
|
213
|
+
if (s.enabled === enabled) return true;
|
|
214
|
+
_servers = _servers.map((x) => x.name === name ? { ...x, enabled } : x);
|
|
215
|
+
notify();
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function toggleDataServer(name: string): boolean {
|
|
220
|
+
const s = _servers.find((x) => x.name === name);
|
|
221
|
+
if (!s) return false;
|
|
222
|
+
return setDataServerEnabled(name, !s.enabled);
|
|
223
|
+
}
|
|
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
|
+
}
|
|
99
286
|
|
|
100
287
|
// ── Widget actions ─────────────────────────────────────────────────────
|
|
101
288
|
function addWidget(type: WidgetType, data: Record<string, unknown> = {}): Widget {
|
|
@@ -104,20 +291,15 @@ function createCanvasVanilla() {
|
|
|
104
291
|
notify();
|
|
105
292
|
return widget;
|
|
106
293
|
}
|
|
107
|
-
|
|
108
|
-
/** @deprecated Use addWidget */
|
|
109
294
|
const addBlock = addWidget;
|
|
110
|
-
|
|
111
295
|
function removeBlock(id: string) {
|
|
112
296
|
_blocks = _blocks.filter((b) => b.id !== id);
|
|
113
297
|
notify();
|
|
114
298
|
}
|
|
115
|
-
|
|
116
299
|
function updateBlock(id: string, data: Partial<Record<string, unknown>>) {
|
|
117
300
|
_blocks = _blocks.map((b) => b.id === id ? { ...b, data: { ...b.data, ...data } } : b);
|
|
118
301
|
notify();
|
|
119
302
|
}
|
|
120
|
-
|
|
121
303
|
function moveBlock(fromId: string, toId: string) {
|
|
122
304
|
const fi = _blocks.findIndex((b) => b.id === fromId);
|
|
123
305
|
const ti = _blocks.findIndex((b) => b.id === toId);
|
|
@@ -128,16 +310,8 @@ function createCanvasVanilla() {
|
|
|
128
310
|
_blocks = next;
|
|
129
311
|
notify();
|
|
130
312
|
}
|
|
131
|
-
|
|
132
|
-
function
|
|
133
|
-
_blocks = [];
|
|
134
|
-
notify();
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function setBlocks(newBlocks: Widget[]) {
|
|
138
|
-
_blocks = newBlocks;
|
|
139
|
-
notify();
|
|
140
|
-
}
|
|
313
|
+
function clearBlocks() { _blocks = []; notify(); }
|
|
314
|
+
function setBlocks(newBlocks: Widget[]) { _blocks = newBlocks; notify(); }
|
|
141
315
|
|
|
142
316
|
// ── Chat ───────────────────────────────────────────────────────────────
|
|
143
317
|
function addMsg(role: ChatMsg['role'], content: string, thinking = false): ChatMsg {
|
|
@@ -146,52 +320,11 @@ function createCanvasVanilla() {
|
|
|
146
320
|
notify();
|
|
147
321
|
return msg;
|
|
148
322
|
}
|
|
149
|
-
|
|
150
323
|
function updateMsg(id: string, content: string, thinking = false) {
|
|
151
324
|
_messages = _messages.map((m) => m.id === id ? { ...m, content, thinking } : m);
|
|
152
325
|
notify();
|
|
153
326
|
}
|
|
154
|
-
|
|
155
|
-
function clearMessages() {
|
|
156
|
-
_messages = [];
|
|
157
|
-
notify();
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// ── MCP ────────────────────────────────────────────────────────────────
|
|
161
|
-
function setMcpConnecting(connecting: boolean) {
|
|
162
|
-
_mcpConnecting = connecting;
|
|
163
|
-
if (connecting) {
|
|
164
|
-
_statusText = '● connexion…';
|
|
165
|
-
_statusColor = 'text-amber-400';
|
|
166
|
-
}
|
|
167
|
-
notify();
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function setMcpConnected(
|
|
171
|
-
connected: boolean,
|
|
172
|
-
name?: string,
|
|
173
|
-
tools?: McpToolInfo[]
|
|
174
|
-
) {
|
|
175
|
-
_mcpConnected = connected;
|
|
176
|
-
if (name) _mcpName = name;
|
|
177
|
-
if (tools) _mcpTools = tools;
|
|
178
|
-
if (connected) {
|
|
179
|
-
_statusText = `● ${name} · ${tools?.length ?? 0} tools`;
|
|
180
|
-
_statusColor = 'text-teal-400';
|
|
181
|
-
} else {
|
|
182
|
-
_statusText = '● no MCP connected';
|
|
183
|
-
_statusColor = 'text-zinc-600';
|
|
184
|
-
}
|
|
185
|
-
notify();
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function setMcpError(err: string) {
|
|
189
|
-
_mcpConnected = false;
|
|
190
|
-
_mcpConnecting = false;
|
|
191
|
-
_statusText = `● erreur: ${err}`;
|
|
192
|
-
_statusColor = 'text-red-400';
|
|
193
|
-
notify();
|
|
194
|
-
}
|
|
327
|
+
function clearMessages() { _messages = []; notify(); }
|
|
195
328
|
|
|
196
329
|
// ── Theme ──────────────────────────────────────────────────────────────
|
|
197
330
|
function setThemeOverrides(overrides: Record<string, string>) {
|
|
@@ -199,7 +332,7 @@ function createCanvasVanilla() {
|
|
|
199
332
|
notify();
|
|
200
333
|
}
|
|
201
334
|
|
|
202
|
-
// ── Enabled servers
|
|
335
|
+
// ── Enabled servers (kept for UI server catalogue) ─────────────────────
|
|
203
336
|
function setEnabledServers(ids: string[]) {
|
|
204
337
|
_enabledServerIds = ids;
|
|
205
338
|
notify();
|
|
@@ -207,11 +340,12 @@ function createCanvasVanilla() {
|
|
|
207
340
|
|
|
208
341
|
// ── HyperSkill ─────────────────────────────────────────────────────────
|
|
209
342
|
function buildSkillJSON() {
|
|
343
|
+
const p = primaryServer();
|
|
210
344
|
const skill: Record<string, unknown> = {
|
|
211
345
|
version: '1.0',
|
|
212
346
|
name: 'skill-' + Date.now(),
|
|
213
347
|
created: new Date().toISOString(),
|
|
214
|
-
mcp:
|
|
348
|
+
mcp: p?.url ?? '',
|
|
215
349
|
llm: _llm,
|
|
216
350
|
blocks: _blocks.map((b) => ({ type: b.type, data: JSON.parse(JSON.stringify(b.data)) })),
|
|
217
351
|
};
|
|
@@ -222,8 +356,8 @@ function createCanvasVanilla() {
|
|
|
222
356
|
|
|
223
357
|
async function buildHyperskillParam(): Promise<string> {
|
|
224
358
|
const json = JSON.stringify(buildSkillJSON());
|
|
225
|
-
|
|
226
|
-
const url = await encode('https://x.local', json, compress
|
|
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' });
|
|
227
361
|
return new URL(url).searchParams.get('hs')!;
|
|
228
362
|
}
|
|
229
363
|
|
|
@@ -234,7 +368,7 @@ function createCanvasVanilla() {
|
|
|
234
368
|
servers?: string[];
|
|
235
369
|
blocks?: { type: WidgetType; data: Record<string, unknown> }[];
|
|
236
370
|
}) {
|
|
237
|
-
if (skill.mcp)
|
|
371
|
+
if (skill.mcp) ensurePrimary(skill.mcp);
|
|
238
372
|
if (skill.llm) _llm = skill.llm;
|
|
239
373
|
if (skill.theme) _themeOverrides = skill.theme;
|
|
240
374
|
if (skill.servers) _enabledServerIds = skill.servers;
|
|
@@ -243,15 +377,11 @@ function createCanvasVanilla() {
|
|
|
243
377
|
}
|
|
244
378
|
notify();
|
|
245
379
|
}
|
|
246
|
-
|
|
247
|
-
// Try hyperskills NPM decode (handles gz., br., and plain base64)
|
|
248
380
|
try {
|
|
249
381
|
const { content: json } = await decode(param);
|
|
250
382
|
applySkill(JSON.parse(json));
|
|
251
383
|
return true;
|
|
252
|
-
} catch { /* fall through
|
|
253
|
-
|
|
254
|
-
// Fallback: legacy format (plain base64 with escape/unescape)
|
|
384
|
+
} catch { /* fall through */ }
|
|
255
385
|
try {
|
|
256
386
|
let b64 = param.replace(/ /g, '+').replace(/-/g, '+').replace(/_/g, '/');
|
|
257
387
|
while (b64.length % 4) b64 += '=';
|
|
@@ -263,14 +393,19 @@ function createCanvasVanilla() {
|
|
|
263
393
|
}
|
|
264
394
|
}
|
|
265
395
|
|
|
266
|
-
// ── loadFromUrl ────────────────────────────────────────────────────────
|
|
267
396
|
async function loadFromUrl(url: string): Promise<boolean> {
|
|
268
397
|
try {
|
|
269
398
|
const { content: raw } = await decode(url);
|
|
270
|
-
const decoded = JSON.parse(raw) as {
|
|
271
|
-
|
|
399
|
+
const decoded = JSON.parse(raw) as {
|
|
400
|
+
meta?: Record<string, unknown>;
|
|
401
|
+
content?: { blocks?: { type: WidgetType; data: Record<string, unknown> }[] };
|
|
402
|
+
servers?: string[];
|
|
403
|
+
};
|
|
404
|
+
if (decoded.meta?.mcp) ensurePrimary(decoded.meta.mcp as string);
|
|
272
405
|
if (decoded.meta?.llm) _llm = decoded.meta.llm as LLMId;
|
|
273
406
|
if (decoded.meta?.theme) _themeOverrides = decoded.meta.theme as Record<string, string>;
|
|
407
|
+
const servers = (decoded.servers ?? (decoded.meta?.servers as string[] | undefined));
|
|
408
|
+
if (Array.isArray(servers)) _enabledServerIds = servers;
|
|
274
409
|
if (decoded.content?.blocks) _blocks = decoded.content.blocks.map((b) => ({ id: uuid(), type: b.type, data: b.data }));
|
|
275
410
|
notify();
|
|
276
411
|
return true;
|
|
@@ -279,89 +414,86 @@ function createCanvasVanilla() {
|
|
|
279
414
|
}
|
|
280
415
|
}
|
|
281
416
|
|
|
282
|
-
// ── Snapshot
|
|
417
|
+
// ── Snapshot (fields kept for API stability) ───────────────────────────
|
|
283
418
|
function getSnapshot(): CanvasSnapshot {
|
|
419
|
+
const p = primaryServer();
|
|
284
420
|
return {
|
|
285
421
|
blocks: _blocks,
|
|
286
422
|
mode: _mode,
|
|
287
423
|
llm: _llm,
|
|
288
|
-
mcpUrl:
|
|
289
|
-
mcpConnected:
|
|
290
|
-
mcpConnecting:
|
|
291
|
-
mcpName:
|
|
292
|
-
mcpTools:
|
|
424
|
+
mcpUrl: p?.url ?? '',
|
|
425
|
+
mcpConnected: p?.connected ?? false,
|
|
426
|
+
mcpConnecting: anyConnecting(),
|
|
427
|
+
mcpName: displayName(),
|
|
428
|
+
mcpTools: unionTools(),
|
|
293
429
|
messages: _messages,
|
|
294
430
|
generating: _generating,
|
|
295
|
-
statusText:
|
|
296
|
-
statusColor:
|
|
431
|
+
statusText: statusText(),
|
|
432
|
+
statusColor: statusColor(),
|
|
297
433
|
themeOverrides: _themeOverrides,
|
|
298
434
|
enabledServerIds: _enabledServerIds,
|
|
435
|
+
dataServers: _servers,
|
|
299
436
|
blockCount: _blocks.length,
|
|
300
437
|
isEmpty: _blocks.length === 0,
|
|
301
438
|
};
|
|
302
439
|
}
|
|
303
440
|
|
|
304
|
-
// ── Subscribe ──────────────────────────────────────────────────────────
|
|
305
441
|
function subscribe(fn: Listener): () => void {
|
|
306
442
|
listeners.add(fn);
|
|
307
443
|
return () => { listeners.delete(fn); };
|
|
308
444
|
}
|
|
309
445
|
|
|
310
|
-
// ── Return public API ──────────────────────────────────────────────────
|
|
311
446
|
return {
|
|
312
|
-
//
|
|
447
|
+
// Reactive getters (read-side)
|
|
313
448
|
get blocks() { return _blocks; },
|
|
314
449
|
get mode() { return _mode; },
|
|
315
450
|
set mode(v: Mode) { _mode = v; notify(); },
|
|
316
451
|
get llm() { return _llm; },
|
|
317
452
|
set llm(v: LLMId) { _llm = v; notify(); },
|
|
318
|
-
get mcpUrl() { return
|
|
319
|
-
set mcpUrl(v: string) {
|
|
320
|
-
get mcpConnected() { return
|
|
321
|
-
get mcpConnecting() { return
|
|
322
|
-
get mcpName() { return
|
|
323
|
-
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(); },
|
|
324
459
|
get messages() { return _messages; },
|
|
325
460
|
get generating() { return _generating; },
|
|
326
461
|
set generating(v: boolean) { _generating = v; notify(); },
|
|
327
|
-
get statusText() { return
|
|
328
|
-
get statusColor() { return
|
|
462
|
+
get statusText() { return statusText(); },
|
|
463
|
+
get statusColor() { return statusColor(); },
|
|
329
464
|
get blockCount() { return _blocks.length; },
|
|
330
465
|
get isEmpty() { return _blocks.length === 0; },
|
|
331
466
|
|
|
332
|
-
// Setters (kept for backward compat)
|
|
333
467
|
setMode(m: Mode) { _mode = m; notify(); },
|
|
334
468
|
setLlm(l: LLMId) { _llm = l; notify(); },
|
|
335
|
-
setMcpUrl
|
|
469
|
+
setMcpUrl,
|
|
336
470
|
setGenerating(g: boolean) { _generating = g; notify(); },
|
|
337
471
|
|
|
338
|
-
|
|
339
|
-
addWidget,
|
|
340
|
-
|
|
341
|
-
// Backward compat alias
|
|
342
|
-
addBlock,
|
|
343
|
-
|
|
344
|
-
// Block actions (kept as-is)
|
|
472
|
+
addWidget, addBlock,
|
|
345
473
|
removeBlock, updateBlock, moveBlock, clearBlocks, setBlocks,
|
|
346
|
-
|
|
347
|
-
// Chat
|
|
348
474
|
addMsg, updateMsg, clearMessages,
|
|
349
475
|
|
|
350
|
-
// MCP
|
|
351
476
|
setMcpConnecting, setMcpConnected, setMcpError,
|
|
352
477
|
|
|
353
|
-
// Theme
|
|
354
478
|
get themeOverrides() { return _themeOverrides; },
|
|
355
479
|
setThemeOverrides,
|
|
356
480
|
|
|
357
|
-
// Enabled servers
|
|
358
481
|
get enabledServerIds() { return _enabledServerIds; },
|
|
359
482
|
setEnabledServers,
|
|
360
483
|
|
|
361
|
-
//
|
|
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(); },
|
|
488
|
+
addDataServer,
|
|
489
|
+
removeDataServer,
|
|
490
|
+
getDataServer,
|
|
491
|
+
setDataServerMeta,
|
|
492
|
+
setDataServerEnabled,
|
|
493
|
+
toggleDataServer,
|
|
494
|
+
|
|
362
495
|
buildSkillJSON, buildHyperskillParam, loadFromParam, loadFromUrl,
|
|
363
496
|
|
|
364
|
-
// Framework-agnostic reactivity
|
|
365
497
|
subscribe,
|
|
366
498
|
getSnapshot,
|
|
367
499
|
};
|