@webmcp-auto-ui/ui 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.
Files changed (76) hide show
  1. package/package.json +18 -5
  2. package/src/agent/LLMSelector.svelte +11 -3
  3. package/src/agent/ModelCacheManager.svelte +359 -0
  4. package/src/index.ts +42 -30
  5. package/src/theme/scale.ts +128 -0
  6. package/src/widgets/WidgetRenderer.svelte +144 -107
  7. package/src/widgets/export-widget.ts +28 -1
  8. package/src/widgets/helpers/safe-image.ts +78 -0
  9. package/src/widgets/notebook/.gitkeep +0 -0
  10. package/src/widgets/notebook/chart-renderer.ts +63 -0
  11. package/src/widgets/notebook/executors/.gitkeep +1 -0
  12. package/src/widgets/notebook/executors/index.ts +4 -0
  13. package/src/widgets/notebook/executors/js-worker.ts +269 -0
  14. package/src/widgets/notebook/executors/sql.ts +206 -0
  15. package/src/widgets/notebook/import-modals.ts +560 -0
  16. package/src/widgets/notebook/left-pane.ts +256 -0
  17. package/src/widgets/notebook/notebook.ts +930 -0
  18. package/src/widgets/notebook/prose.ts +615 -0
  19. package/src/widgets/notebook/recipe-browser.ts +350 -0
  20. package/src/widgets/notebook/recipes/notebook.md +124 -0
  21. package/src/widgets/notebook/resource-extractor.ts +162 -0
  22. package/src/widgets/notebook/share-handlers.ts +222 -0
  23. package/src/widgets/notebook/shared.ts +1633 -0
  24. package/src/widgets/rich/cards.ts +181 -0
  25. package/src/widgets/rich/carousel.ts +319 -0
  26. package/src/widgets/rich/chart-rich.ts +386 -0
  27. package/src/widgets/rich/d3.ts +503 -0
  28. package/src/widgets/rich/data-table.ts +342 -0
  29. package/src/widgets/rich/gallery.ts +350 -0
  30. package/src/widgets/rich/grid-data.ts +173 -0
  31. package/src/widgets/rich/hemicycle.ts +313 -0
  32. package/src/widgets/rich/js-sandbox.ts +122 -0
  33. package/src/widgets/rich/json-viewer.ts +202 -0
  34. package/src/widgets/rich/log.ts +143 -0
  35. package/src/widgets/rich/map.ts +218 -0
  36. package/src/widgets/rich/profile.ts +256 -0
  37. package/src/widgets/rich/sankey.ts +257 -0
  38. package/src/widgets/rich/stat-card.ts +125 -0
  39. package/src/widgets/rich/timeline.ts +179 -0
  40. package/src/widgets/rich/trombinoscope.ts +246 -0
  41. package/src/widgets/simple/actions.ts +89 -0
  42. package/src/widgets/simple/alert.ts +100 -0
  43. package/src/widgets/simple/chart.ts +189 -0
  44. package/src/widgets/simple/code.ts +79 -0
  45. package/src/widgets/simple/kv.ts +68 -0
  46. package/src/widgets/simple/list.ts +89 -0
  47. package/src/widgets/simple/stat.ts +58 -0
  48. package/src/widgets/simple/tags.ts +125 -0
  49. package/src/widgets/simple/text.ts +198 -0
  50. package/src/widgets/SafeImage.svelte +0 -76
  51. package/src/widgets/rich/Cards.svelte +0 -39
  52. package/src/widgets/rich/Carousel.svelte +0 -88
  53. package/src/widgets/rich/Chart.svelte +0 -142
  54. package/src/widgets/rich/D3Widget.svelte +0 -378
  55. package/src/widgets/rich/DataTable.svelte +0 -62
  56. package/src/widgets/rich/Gallery.svelte +0 -94
  57. package/src/widgets/rich/GridData.svelte +0 -44
  58. package/src/widgets/rich/Hemicycle.svelte +0 -78
  59. package/src/widgets/rich/JsSandbox.svelte +0 -51
  60. package/src/widgets/rich/JsonViewer.svelte +0 -42
  61. package/src/widgets/rich/LogViewer.svelte +0 -24
  62. package/src/widgets/rich/MapView.svelte +0 -140
  63. package/src/widgets/rich/ProfileCard.svelte +0 -59
  64. package/src/widgets/rich/Sankey.svelte +0 -56
  65. package/src/widgets/rich/StatCard.svelte +0 -35
  66. package/src/widgets/rich/Timeline.svelte +0 -43
  67. package/src/widgets/rich/Trombinoscope.svelte +0 -48
  68. package/src/widgets/simple/ActionsBlock.svelte +0 -15
  69. package/src/widgets/simple/AlertBlock.svelte +0 -11
  70. package/src/widgets/simple/ChartBlock.svelte +0 -21
  71. package/src/widgets/simple/CodeBlock.svelte +0 -11
  72. package/src/widgets/simple/KVBlock.svelte +0 -16
  73. package/src/widgets/simple/ListBlock.svelte +0 -17
  74. package/src/widgets/simple/StatBlock.svelte +0 -14
  75. package/src/widgets/simple/TagsBlock.svelte +0 -15
  76. package/src/widgets/simple/TextBlock.svelte +0 -122
@@ -0,0 +1,269 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * JS executor — runs cell content in a Web Worker, with state.scope exposed
4
+ * as top-level variables inside the worker's async function body.
5
+ *
6
+ * Output shape detection:
7
+ * - Array of objects → kind: 'table'
8
+ * - Array of scalars → kind: 'value'
9
+ * - Vega / Vega-Lite spec → kind: 'chart'
10
+ * - undefined / null → kind: 'empty'
11
+ * - Otherwise → kind: 'value'
12
+ */
13
+
14
+ import type { CellExecutor, CellExecContext, CellResult } from '../shared.js';
15
+
16
+ export interface JsExecutorOptions {
17
+ /** Timeout (ms). Default 5000 */
18
+ timeoutMs?: number;
19
+ }
20
+
21
+ function isChartSpec(v: any): boolean {
22
+ if (!v || typeof v !== 'object') return false;
23
+ // Vega
24
+ if (Array.isArray(v.marks)) return true;
25
+ // Vega-Lite
26
+ if (v.mark && (typeof v.mark === 'string' || typeof v.mark === 'object')) return true;
27
+ if (v.layer && Array.isArray(v.layer)) return true;
28
+ if (v.data && (v.encoding || v.mark)) return true;
29
+ return false;
30
+ }
31
+
32
+ /**
33
+ * Convert the worker's raw log entries ({level, args}) into a flat string[]
34
+ * suitable for CellResult.logs. Each entry becomes one line, with
35
+ * `[warn]` / `[error]` prefixes for non-log levels (used for color coding).
36
+ */
37
+ function normalizeLogs(raw: unknown): string[] | undefined {
38
+ if (!Array.isArray(raw) || raw.length === 0) return undefined;
39
+ const out: string[] = [];
40
+ for (const entry of raw) {
41
+ if (!entry || typeof entry !== 'object') continue;
42
+ const level = (entry as any).level as string | undefined;
43
+ const args = (entry as any).args as unknown[] | undefined;
44
+ const body = Array.isArray(args)
45
+ ? args.map((a) => {
46
+ if (a == null) return String(a);
47
+ if (typeof a === 'object') { try { return JSON.stringify(a); } catch { return String(a); } }
48
+ return String(a);
49
+ }).join(' ')
50
+ : '';
51
+ const prefix = level === 'warn' ? '[warn] ' : level === 'error' ? '[error] ' : '';
52
+ out.push(prefix + body);
53
+ }
54
+ return out.length ? out : undefined;
55
+ }
56
+
57
+ function toResult(value: unknown, durationMs: number): CellResult {
58
+ if (value === undefined || value === null) {
59
+ return { ok: true, kind: 'empty', durationMs };
60
+ }
61
+ if (Array.isArray(value)) {
62
+ if (value.length > 0 && typeof value[0] === 'object' && value[0] !== null && !Array.isArray(value[0])) {
63
+ const columns = Object.keys(value[0] as Record<string, unknown>);
64
+ return {
65
+ ok: true,
66
+ kind: 'table',
67
+ rows: value as Record<string, unknown>[],
68
+ columns,
69
+ rowCount: value.length,
70
+ durationMs,
71
+ };
72
+ }
73
+ return { ok: true, kind: 'value', value, durationMs };
74
+ }
75
+ if (typeof value === 'object') {
76
+ if (isChartSpec(value)) {
77
+ return { ok: true, kind: 'chart', spec: value, durationMs };
78
+ }
79
+ return { ok: true, kind: 'value', value, durationMs };
80
+ }
81
+ return { ok: true, kind: 'value', value, durationMs };
82
+ }
83
+
84
+ /**
85
+ * Build the worker source inline so the executor ships as a single module
86
+ * without relying on bundler-specific `new URL(..., import.meta.url)` resolution.
87
+ */
88
+ function buildWorkerBlobUrl(): string {
89
+ const src = `
90
+ const origLog = self.console && self.console.log ? self.console.log.bind(self.console) : null;
91
+ const origWarn = self.console && self.console.warn ? self.console.warn.bind(self.console) : null;
92
+ const origErr = self.console && self.console.error ? self.console.error.bind(self.console) : null;
93
+
94
+ self.addEventListener('message', async (e) => {
95
+ const data = e.data || {};
96
+ const code = data.code || '';
97
+ const scope = data.scope || {};
98
+ const logs = [];
99
+ self.console = {
100
+ log: function () { logs.push({ level: 'log', args: [].slice.call(arguments) }); origLog && origLog.apply(null, arguments); },
101
+ warn: function () { logs.push({ level: 'warn', args: [].slice.call(arguments) }); origWarn && origWarn.apply(null, arguments); },
102
+ error:function () { logs.push({ level: 'error', args: [].slice.call(arguments) }); origErr && origErr.apply(null, arguments); },
103
+ info: function () { logs.push({ level: 'log', args: [].slice.call(arguments) }); origLog && origLog.apply(null, arguments); },
104
+ debug:function () { logs.push({ level: 'log', args: [].slice.call(arguments) }); origLog && origLog.apply(null, arguments); },
105
+ };
106
+ try {
107
+ const keys = Object.keys(scope);
108
+ const values = keys.map(function (k) { return scope[k]; });
109
+ const fn = new Function(...keys, 'return (async () => {\\n' + code + '\\n})();');
110
+ const result = await fn.apply(null, values);
111
+ self.postMessage({ ok: true, result: result, logs: logs });
112
+ } catch (err) {
113
+ const msg = String((err && err.message) || err);
114
+ const isSyntax = (err instanceof SyntaxError) || /SyntaxError/.test(msg);
115
+ self.postMessage({ ok: false, error: msg, errorKind: isSyntax ? 'syntax' : 'runtime', logs: logs });
116
+ }
117
+ });
118
+ `;
119
+ const blob = new Blob([src], { type: 'application/javascript' });
120
+ return URL.createObjectURL(blob);
121
+ }
122
+
123
+ // Cache the blob URL across executor calls — workers themselves are disposable
124
+ // but recreating the blob on each run wastes object URLs.
125
+ // Trade-off: we never auto-revoke since the cached URL stays useful for the
126
+ // lifetime of the page. `disposeJsWorkerCache()` is exposed for explicit
127
+ // cleanup (HMR, teardown) when a caller wants to release the object URL.
128
+ let cachedWorkerUrl: string | null = null;
129
+ function getWorkerUrl(): string {
130
+ if (cachedWorkerUrl) return cachedWorkerUrl;
131
+ cachedWorkerUrl = buildWorkerBlobUrl();
132
+ return cachedWorkerUrl;
133
+ }
134
+
135
+ /** Revoke the cached worker blob URL. Safe to call multiple times.
136
+ * The next executor call will create a fresh blob URL on demand. */
137
+ export function disposeJsWorkerCache(): void {
138
+ if (cachedWorkerUrl) {
139
+ try { URL.revokeObjectURL(cachedWorkerUrl); } catch { /* ignore */ }
140
+ cachedWorkerUrl = null;
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Best-effort structured-clone check: filter out values that can't be posted
146
+ * to a worker (functions, DOM nodes, etc.). Replaces them with undefined so
147
+ * the cell can still reference the scope variable without crashing postMessage.
148
+ */
149
+ function sanitizeScope(scope: Record<string, unknown>): Record<string, unknown> {
150
+ const out: Record<string, unknown> = {};
151
+ for (const [k, v] of Object.entries(scope ?? {})) {
152
+ if (typeof v === 'function') continue;
153
+ if (v && typeof v === 'object') {
154
+ // Quick cheap check: Nodes, Windows, etc.
155
+ const ctor = (v as any).constructor?.name;
156
+ if (ctor === 'Window' || ctor === 'HTMLDocument' || (v as any).nodeType != null) continue;
157
+ }
158
+ out[k] = v;
159
+ }
160
+ return out;
161
+ }
162
+
163
+ export function createJsExecutor(opts?: JsExecutorOptions): CellExecutor {
164
+ const timeoutMs = opts?.timeoutMs ?? 5_000;
165
+
166
+ return async (ctx: CellExecContext) => {
167
+ const startedAt = Date.now();
168
+
169
+ if (typeof Worker === 'undefined' || typeof Blob === 'undefined' || typeof URL === 'undefined') {
170
+ return {
171
+ ok: false,
172
+ error: 'Web Worker not available in this environment',
173
+ errorKind: 'runtime',
174
+ durationMs: Date.now() - startedAt,
175
+ };
176
+ }
177
+
178
+ const code = ctx.cell.content ?? '';
179
+ if (!code.trim()) {
180
+ return { ok: true, kind: 'empty', durationMs: Date.now() - startedAt };
181
+ }
182
+
183
+ const scope = sanitizeScope(ctx.scope ?? {});
184
+ const worker = new Worker(getWorkerUrl());
185
+
186
+ return new Promise<CellResult>((resolve) => {
187
+ let done = false;
188
+ const finish = (r: CellResult) => {
189
+ if (done) return;
190
+ done = true;
191
+ clearTimeout(timer);
192
+ ctx.signal.removeEventListener('abort', onAbort);
193
+ try {
194
+ worker.terminate();
195
+ } catch {
196
+ /* noop */
197
+ }
198
+ resolve(r);
199
+ };
200
+
201
+ const timer = setTimeout(() => {
202
+ finish({
203
+ ok: false,
204
+ error: `JS cell timed out after ${timeoutMs}ms`,
205
+ errorKind: 'timeout',
206
+ durationMs: Date.now() - startedAt,
207
+ });
208
+ }, timeoutMs);
209
+
210
+ const onAbort = () => {
211
+ finish({
212
+ ok: false,
213
+ error: 'aborted',
214
+ errorKind: 'timeout',
215
+ durationMs: Date.now() - startedAt,
216
+ });
217
+ };
218
+ if (ctx.signal.aborted) {
219
+ onAbort();
220
+ return;
221
+ }
222
+ ctx.signal.addEventListener('abort', onAbort, { once: true });
223
+
224
+ worker.addEventListener('message', (e: MessageEvent) => {
225
+ const msg = e.data as
226
+ | { ok: true; result: unknown; logs?: unknown[] }
227
+ | { ok: false; error: string; errorKind?: 'syntax' | 'runtime'; logs?: unknown[] };
228
+ const durationMs = Date.now() - startedAt;
229
+ if (!msg || typeof msg !== 'object') {
230
+ finish({ ok: false, error: 'Invalid worker response', errorKind: 'runtime', durationMs });
231
+ return;
232
+ }
233
+ const logs = normalizeLogs((msg as any).logs);
234
+ if (msg.ok) {
235
+ const base = toResult((msg as any).result, durationMs);
236
+ finish(logs ? { ...base, logs } as CellResult : base);
237
+ } else {
238
+ finish({
239
+ ok: false,
240
+ error: (msg as any).error || 'Unknown error',
241
+ errorKind: (msg as any).errorKind === 'syntax' ? 'syntax' : 'runtime',
242
+ durationMs,
243
+ logs,
244
+ });
245
+ }
246
+ });
247
+
248
+ worker.addEventListener('error', (e: ErrorEvent) => {
249
+ finish({
250
+ ok: false,
251
+ error: e.message || 'Worker error',
252
+ errorKind: 'runtime',
253
+ durationMs: Date.now() - startedAt,
254
+ });
255
+ });
256
+
257
+ try {
258
+ worker.postMessage({ code, scope });
259
+ } catch (err: any) {
260
+ finish({
261
+ ok: false,
262
+ error: `Failed to post scope to worker: ${String(err?.message ?? err)}`,
263
+ errorKind: 'runtime',
264
+ durationMs: Date.now() - startedAt,
265
+ });
266
+ }
267
+ });
268
+ };
269
+ }
@@ -0,0 +1,206 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * SQL executor for notebook cells.
4
+ *
5
+ * Finds a SQL-capable tool on the connected data servers via auto-pattern,
6
+ * calls it via postMessage, parses the result into a `table` CellResult.
7
+ */
8
+
9
+ import { callToolViaPostMessage } from '@webmcp-auto-ui/core';
10
+ import type { CellExecutor, CellExecContext, DataServerDescriptor } from '../shared.js';
11
+
12
+ export interface SqlExecutorOptions {
13
+ /** Timeout per query (ms). Default 30000 */
14
+ timeoutMs?: number;
15
+ /** Max rows to keep in result (truncate beyond). Default 1000 */
16
+ maxRows?: number;
17
+ }
18
+
19
+ const PATTERN_PRIMARY = /^.*query_sql$/i;
20
+ const PATTERN_FALLBACK = /^(query|run|execute)(_sql)?$/i;
21
+
22
+ function findSqlTool(servers: DataServerDescriptor[]): string | null {
23
+ // Priority 1: *_query_sql or query_sql
24
+ for (const srv of servers) {
25
+ for (const t of srv.tools ?? []) {
26
+ if (PATTERN_PRIMARY.test(t.name)) return t.name;
27
+ }
28
+ }
29
+ // Priority 2: query / run / execute (with optional _sql)
30
+ for (const srv of servers) {
31
+ for (const t of srv.tools ?? []) {
32
+ if (PATTERN_FALLBACK.test(t.name)) return t.name;
33
+ }
34
+ }
35
+ return null;
36
+ }
37
+
38
+ /**
39
+ * Extract the first text content from an MCP tool result.
40
+ */
41
+ function extractText(result: any): string | null {
42
+ if (!result) return null;
43
+ const content = result.content ?? result;
44
+ if (!Array.isArray(content)) {
45
+ if (typeof content === 'string') return content;
46
+ return null;
47
+ }
48
+ for (const item of content) {
49
+ if (item && item.type === 'text' && typeof item.text === 'string') {
50
+ return item.text;
51
+ }
52
+ }
53
+ return null;
54
+ }
55
+
56
+ function tryParseJson(text: string): unknown {
57
+ try {
58
+ return JSON.parse(text);
59
+ } catch {
60
+ return text; // not JSON, return raw
61
+ }
62
+ }
63
+
64
+ export function createSqlExecutor(
65
+ getServers: () => DataServerDescriptor[],
66
+ opts?: SqlExecutorOptions
67
+ ): CellExecutor {
68
+ const timeoutMs = opts?.timeoutMs ?? 30_000;
69
+ const maxRows = opts?.maxRows ?? 1000;
70
+
71
+ return async (ctx: CellExecContext) => {
72
+ const startedAt = Date.now();
73
+ const servers = getServers();
74
+
75
+ const toolName = findSqlTool(servers);
76
+ if (!toolName) {
77
+ return {
78
+ ok: false,
79
+ error: 'No SQL tool found on connected servers',
80
+ errorKind: 'schema',
81
+ durationMs: Date.now() - startedAt,
82
+ };
83
+ }
84
+
85
+ const sql = (ctx.cell.content ?? '').trim();
86
+ if (!sql) {
87
+ return { ok: true, kind: 'empty', durationMs: Date.now() - startedAt };
88
+ }
89
+
90
+ // Wrap the tool call with the cell's AbortSignal so an external abort
91
+ // rejects the promise even if callToolViaPostMessage doesn't support signals.
92
+ const callPromise = callToolViaPostMessage(toolName, { sql }, { timeout: timeoutMs });
93
+
94
+ let raceResult: any;
95
+ try {
96
+ raceResult = await new Promise((resolve, reject) => {
97
+ let settled = false;
98
+ const onAbort = () => {
99
+ if (settled) return;
100
+ settled = true;
101
+ reject(new Error('aborted'));
102
+ };
103
+ if (ctx.signal.aborted) {
104
+ onAbort();
105
+ return;
106
+ }
107
+ ctx.signal.addEventListener('abort', onAbort, { once: true });
108
+ callPromise.then(
109
+ (v) => {
110
+ if (settled) return;
111
+ settled = true;
112
+ ctx.signal.removeEventListener('abort', onAbort);
113
+ resolve(v);
114
+ },
115
+ (err) => {
116
+ if (settled) return;
117
+ settled = true;
118
+ ctx.signal.removeEventListener('abort', onAbort);
119
+ reject(err);
120
+ }
121
+ );
122
+ });
123
+ } catch (err: any) {
124
+ const durationMs = Date.now() - startedAt;
125
+ const msg = String(err?.message ?? err);
126
+ const isTimeout = /timed out|aborted/i.test(msg);
127
+ return {
128
+ ok: false,
129
+ error: msg,
130
+ errorKind: isTimeout ? 'timeout' : 'runtime',
131
+ durationMs,
132
+ };
133
+ }
134
+
135
+ const durationMs = Date.now() - startedAt;
136
+
137
+ // Unwrap MCP content array → text → JSON.
138
+ const text = extractText(raceResult);
139
+ const parsed = text != null ? tryParseJson(text) : raceResult;
140
+
141
+ // Error shape returned inside the tool result
142
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed) && 'error' in (parsed as any) && (parsed as any).error) {
143
+ return {
144
+ ok: false,
145
+ error: String((parsed as any).error),
146
+ errorKind: 'runtime',
147
+ durationMs,
148
+ };
149
+ }
150
+
151
+ // {rows, columns} shape
152
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed) && Array.isArray((parsed as any).rows)) {
153
+ let rows: Record<string, unknown>[] = (parsed as any).rows;
154
+ const columns: string[] = Array.isArray((parsed as any).columns)
155
+ ? (parsed as any).columns
156
+ : rows.length > 0 && typeof rows[0] === 'object'
157
+ ? Object.keys(rows[0] as Record<string, unknown>)
158
+ : [];
159
+ let truncated = false;
160
+ if (rows.length > maxRows) {
161
+ rows = rows.slice(0, maxRows);
162
+ truncated = true;
163
+ }
164
+ return {
165
+ ok: true,
166
+ kind: 'table',
167
+ rows,
168
+ columns,
169
+ rowCount: (parsed as any).rows.length,
170
+ truncated: truncated || undefined,
171
+ durationMs,
172
+ };
173
+ }
174
+
175
+ // Array of objects
176
+ if (Array.isArray(parsed)) {
177
+ if (parsed.length === 0) {
178
+ return { ok: true, kind: 'table', rows: [], columns: [], rowCount: 0, durationMs };
179
+ }
180
+ const first = parsed[0];
181
+ if (first && typeof first === 'object' && !Array.isArray(first)) {
182
+ let rows = parsed as Record<string, unknown>[];
183
+ const columns = Object.keys(first as Record<string, unknown>);
184
+ const rowCount = rows.length;
185
+ let truncated = false;
186
+ if (rows.length > maxRows) {
187
+ rows = rows.slice(0, maxRows);
188
+ truncated = true;
189
+ }
190
+ return {
191
+ ok: true,
192
+ kind: 'table',
193
+ rows,
194
+ columns,
195
+ rowCount,
196
+ truncated: truncated || undefined,
197
+ durationMs,
198
+ };
199
+ }
200
+ return { ok: true, kind: 'value', value: parsed, durationMs };
201
+ }
202
+
203
+ // Anything else — a scalar or object that isn't tabular
204
+ return { ok: true, kind: 'value', value: parsed, durationMs };
205
+ };
206
+ }