@bian-womp/spark-workbench 0.1.0

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 (89) hide show
  1. package/lib/cjs/index.cjs +1748 -0
  2. package/lib/cjs/index.cjs.map +1 -0
  3. package/lib/cjs/src/adapters/cli/index.d.ts +22 -0
  4. package/lib/cjs/src/adapters/cli/index.d.ts.map +1 -0
  5. package/lib/cjs/src/adapters/react-flow/index.d.ts +31 -0
  6. package/lib/cjs/src/adapters/react-flow/index.d.ts.map +1 -0
  7. package/lib/cjs/src/core/AbstractWorkbench.d.ts +35 -0
  8. package/lib/cjs/src/core/AbstractWorkbench.d.ts.map +1 -0
  9. package/lib/cjs/src/core/InMemoryWorkbench.d.ts +54 -0
  10. package/lib/cjs/src/core/InMemoryWorkbench.d.ts.map +1 -0
  11. package/lib/cjs/src/core/contracts.d.ts +107 -0
  12. package/lib/cjs/src/core/contracts.d.ts.map +1 -0
  13. package/lib/cjs/src/core/ui-extensions.d.ts +59 -0
  14. package/lib/cjs/src/core/ui-extensions.d.ts.map +1 -0
  15. package/lib/cjs/src/examples/cli.d.ts +2 -0
  16. package/lib/cjs/src/examples/cli.d.ts.map +1 -0
  17. package/lib/cjs/src/examples/reactflow/App.d.ts +2 -0
  18. package/lib/cjs/src/examples/reactflow/App.d.ts.map +1 -0
  19. package/lib/cjs/src/examples/reactflow/WorkbenchStudio.d.ts +21 -0
  20. package/lib/cjs/src/examples/reactflow/WorkbenchStudio.d.ts.map +1 -0
  21. package/lib/cjs/src/index.d.ts +9 -0
  22. package/lib/cjs/src/index.d.ts.map +1 -0
  23. package/lib/cjs/src/misc/DebugEvents.d.ts +7 -0
  24. package/lib/cjs/src/misc/DebugEvents.d.ts.map +1 -0
  25. package/lib/cjs/src/misc/DefaultContextMenu.d.ts +13 -0
  26. package/lib/cjs/src/misc/DefaultContextMenu.d.ts.map +1 -0
  27. package/lib/cjs/src/misc/DefaultNode.d.ts +4 -0
  28. package/lib/cjs/src/misc/DefaultNode.d.ts.map +1 -0
  29. package/lib/cjs/src/misc/Inspector.d.ts +10 -0
  30. package/lib/cjs/src/misc/Inspector.d.ts.map +1 -0
  31. package/lib/cjs/src/misc/IssueBadge.d.ts +7 -0
  32. package/lib/cjs/src/misc/IssueBadge.d.ts.map +1 -0
  33. package/lib/cjs/src/misc/WorkbenchCanvas.d.ts +6 -0
  34. package/lib/cjs/src/misc/WorkbenchCanvas.d.ts.map +1 -0
  35. package/lib/cjs/src/misc/context/WorkbenchContext.d.ts +45 -0
  36. package/lib/cjs/src/misc/context/WorkbenchContext.d.ts.map +1 -0
  37. package/lib/cjs/src/misc/context/WorkbenchContext.provider.d.ts +12 -0
  38. package/lib/cjs/src/misc/context/WorkbenchContext.provider.d.ts.map +1 -0
  39. package/lib/cjs/src/misc/hooks.d.ts +17 -0
  40. package/lib/cjs/src/misc/hooks.d.ts.map +1 -0
  41. package/lib/cjs/src/misc/mapping.d.ts +47 -0
  42. package/lib/cjs/src/misc/mapping.d.ts.map +1 -0
  43. package/lib/cjs/src/runtime/GraphRunner.d.ts +61 -0
  44. package/lib/cjs/src/runtime/GraphRunner.d.ts.map +1 -0
  45. package/lib/esm/index.js +1740 -0
  46. package/lib/esm/index.js.map +1 -0
  47. package/lib/esm/src/adapters/cli/index.d.ts +22 -0
  48. package/lib/esm/src/adapters/cli/index.d.ts.map +1 -0
  49. package/lib/esm/src/adapters/react-flow/index.d.ts +31 -0
  50. package/lib/esm/src/adapters/react-flow/index.d.ts.map +1 -0
  51. package/lib/esm/src/core/AbstractWorkbench.d.ts +35 -0
  52. package/lib/esm/src/core/AbstractWorkbench.d.ts.map +1 -0
  53. package/lib/esm/src/core/InMemoryWorkbench.d.ts +54 -0
  54. package/lib/esm/src/core/InMemoryWorkbench.d.ts.map +1 -0
  55. package/lib/esm/src/core/contracts.d.ts +107 -0
  56. package/lib/esm/src/core/contracts.d.ts.map +1 -0
  57. package/lib/esm/src/core/ui-extensions.d.ts +59 -0
  58. package/lib/esm/src/core/ui-extensions.d.ts.map +1 -0
  59. package/lib/esm/src/examples/cli.d.ts +2 -0
  60. package/lib/esm/src/examples/cli.d.ts.map +1 -0
  61. package/lib/esm/src/examples/reactflow/App.d.ts +2 -0
  62. package/lib/esm/src/examples/reactflow/App.d.ts.map +1 -0
  63. package/lib/esm/src/examples/reactflow/WorkbenchStudio.d.ts +21 -0
  64. package/lib/esm/src/examples/reactflow/WorkbenchStudio.d.ts.map +1 -0
  65. package/lib/esm/src/index.d.ts +9 -0
  66. package/lib/esm/src/index.d.ts.map +1 -0
  67. package/lib/esm/src/misc/DebugEvents.d.ts +7 -0
  68. package/lib/esm/src/misc/DebugEvents.d.ts.map +1 -0
  69. package/lib/esm/src/misc/DefaultContextMenu.d.ts +13 -0
  70. package/lib/esm/src/misc/DefaultContextMenu.d.ts.map +1 -0
  71. package/lib/esm/src/misc/DefaultNode.d.ts +4 -0
  72. package/lib/esm/src/misc/DefaultNode.d.ts.map +1 -0
  73. package/lib/esm/src/misc/Inspector.d.ts +10 -0
  74. package/lib/esm/src/misc/Inspector.d.ts.map +1 -0
  75. package/lib/esm/src/misc/IssueBadge.d.ts +7 -0
  76. package/lib/esm/src/misc/IssueBadge.d.ts.map +1 -0
  77. package/lib/esm/src/misc/WorkbenchCanvas.d.ts +6 -0
  78. package/lib/esm/src/misc/WorkbenchCanvas.d.ts.map +1 -0
  79. package/lib/esm/src/misc/context/WorkbenchContext.d.ts +45 -0
  80. package/lib/esm/src/misc/context/WorkbenchContext.d.ts.map +1 -0
  81. package/lib/esm/src/misc/context/WorkbenchContext.provider.d.ts +12 -0
  82. package/lib/esm/src/misc/context/WorkbenchContext.provider.d.ts.map +1 -0
  83. package/lib/esm/src/misc/hooks.d.ts +17 -0
  84. package/lib/esm/src/misc/hooks.d.ts.map +1 -0
  85. package/lib/esm/src/misc/mapping.d.ts +47 -0
  86. package/lib/esm/src/misc/mapping.d.ts.map +1 -0
  87. package/lib/esm/src/runtime/GraphRunner.d.ts +61 -0
  88. package/lib/esm/src/runtime/GraphRunner.d.ts.map +1 -0
  89. package/package.json +65 -0
@@ -0,0 +1,1748 @@
1
+ 'use strict';
2
+
3
+ var sparkGraph = require('@bian-womp/spark-graph');
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+ var React = require('react');
6
+ var ReactFlow = require('reactflow');
7
+ require('reactflow/dist/style.css');
8
+ var cx = require('classnames');
9
+ var react = require('@phosphor-icons/react');
10
+ var sparkRemote = require('@bian-womp/spark-remote');
11
+
12
+ class DefaultUIExtensionRegistry {
13
+ constructor() {
14
+ this.nodeRenderers = new Map();
15
+ this.portRenderers = new Map();
16
+ this.edgeRenderers = new Map();
17
+ }
18
+ registerNodeRenderer(nodeTypeId, renderer) {
19
+ this.nodeRenderers.set(nodeTypeId, renderer);
20
+ return this;
21
+ }
22
+ getNodeRenderer(nodeTypeId) {
23
+ return this.nodeRenderers.get(nodeTypeId);
24
+ }
25
+ registerPortRenderer(dataTypeId, renderer) {
26
+ this.portRenderers.set(dataTypeId, renderer);
27
+ return this;
28
+ }
29
+ getPortRenderer(dataTypeId) {
30
+ return this.portRenderers.get(dataTypeId);
31
+ }
32
+ registerEdgeRenderer(typeId, renderer) {
33
+ this.edgeRenderers.set(typeId, renderer);
34
+ return this;
35
+ }
36
+ getEdgeRenderer(typeId) {
37
+ return this.edgeRenderers.get(typeId);
38
+ }
39
+ setInspector(renderer) {
40
+ this.inspector = renderer;
41
+ return this;
42
+ }
43
+ getInspector() {
44
+ return this.inspector;
45
+ }
46
+ setPalette(renderer) {
47
+ this.palette = renderer;
48
+ return this;
49
+ }
50
+ getPalette() {
51
+ return this.palette;
52
+ }
53
+ setToolbar(renderer) {
54
+ this.toolbar = renderer;
55
+ return this;
56
+ }
57
+ getToolbar() {
58
+ return this.toolbar;
59
+ }
60
+ setContextMenu(renderer) {
61
+ this.contextMenu = renderer;
62
+ return this;
63
+ }
64
+ getContextMenu() {
65
+ return this.contextMenu;
66
+ }
67
+ setMiniMap(renderer) {
68
+ this.miniMap = renderer;
69
+ return this;
70
+ }
71
+ getMiniMap() {
72
+ return this.miniMap;
73
+ }
74
+ setIconProvider(provider) {
75
+ this.iconProvider = provider;
76
+ return this;
77
+ }
78
+ getIconProvider() {
79
+ return this.iconProvider;
80
+ }
81
+ }
82
+
83
+ class AbstractWorkbench {
84
+ constructor(args) {
85
+ this.ui = args.ui;
86
+ this.layout = args.layout;
87
+ this.storage = args.storage;
88
+ this.serializer = args.serializer;
89
+ }
90
+ // Expose UI registry to adapters (React Flow, CLI) to allow overrides
91
+ getUI() {
92
+ return this.ui;
93
+ }
94
+ }
95
+
96
+ class InMemoryWorkbench extends AbstractWorkbench {
97
+ constructor() {
98
+ super(...arguments);
99
+ this.def = { nodes: [], edges: [] };
100
+ this.positions = {};
101
+ this.listeners = new Map();
102
+ this.selection = {
103
+ nodes: [],
104
+ edges: [],
105
+ };
106
+ }
107
+ setRegistry(registry) {
108
+ this.registry = registry;
109
+ }
110
+ async load(def) {
111
+ this.def = { nodes: [...def.nodes], edges: [...def.edges] };
112
+ if (this.layout) {
113
+ const { positions } = await this.layout.layout(this.def);
114
+ this.positions = positions;
115
+ }
116
+ this.emit("graphChanged", { def: this.def });
117
+ this.refreshValidation();
118
+ }
119
+ export() {
120
+ return this.def;
121
+ }
122
+ refreshValidation() {
123
+ this.emit("validationChanged", this.validate());
124
+ }
125
+ validate() {
126
+ if (this.registry) {
127
+ const builder = new sparkGraph.GraphBuilder(this.registry);
128
+ const report = builder.validate(this.def);
129
+ return report;
130
+ }
131
+ const issues = [];
132
+ const nodeIds = new Set();
133
+ for (const n of this.def.nodes) {
134
+ if (nodeIds.has(n.nodeId)) {
135
+ issues.push({
136
+ level: "error",
137
+ code: "NODE_ID_DUP",
138
+ message: `Duplicate nodeId ${n.nodeId}`,
139
+ });
140
+ }
141
+ else
142
+ nodeIds.add(n.nodeId);
143
+ }
144
+ const edgeIds = new Set();
145
+ for (const e of this.def.edges) {
146
+ if (edgeIds.has(e.id)) {
147
+ issues.push({
148
+ level: "error",
149
+ code: "EDGE_ID_DUP",
150
+ message: `Duplicate edge id ${e.id}`,
151
+ });
152
+ }
153
+ else
154
+ edgeIds.add(e.id);
155
+ }
156
+ return { ok: issues.every((i) => i.level !== "error"), issues };
157
+ }
158
+ addNode(node) {
159
+ const id = node.nodeId ?? this.generateId("n");
160
+ this.def.nodes.push({
161
+ nodeId: id,
162
+ typeId: node.typeId,
163
+ params: node.params,
164
+ });
165
+ if (node.position)
166
+ this.positions[id] = node.position;
167
+ this.emit("graphChanged", {
168
+ def: this.def,
169
+ change: { type: "addNode", nodeId: id },
170
+ });
171
+ this.refreshValidation();
172
+ }
173
+ removeNode(nodeId) {
174
+ this.def.nodes = this.def.nodes.filter((n) => n.nodeId !== nodeId);
175
+ this.def.edges = this.def.edges.filter((e) => e.source.nodeId !== nodeId && e.target.nodeId !== nodeId);
176
+ delete this.positions[nodeId];
177
+ this.emit("graphChanged", {
178
+ def: this.def,
179
+ change: { type: "removeNode", nodeId },
180
+ });
181
+ this.refreshValidation();
182
+ }
183
+ connect(edge) {
184
+ const id = edge.id ?? this.generateId("e");
185
+ this.def.edges.push({
186
+ id,
187
+ source: { ...edge.source },
188
+ target: { ...edge.target },
189
+ typeId: edge.typeId,
190
+ });
191
+ this.emit("graphChanged", {
192
+ def: this.def,
193
+ change: { type: "connect", edgeId: id },
194
+ });
195
+ this.refreshValidation();
196
+ }
197
+ disconnect(edgeId) {
198
+ this.def.edges = this.def.edges.filter((e) => e.id !== edgeId);
199
+ this.emit("graphChanged", {
200
+ def: this.def,
201
+ change: { type: "disconnect", edgeId },
202
+ });
203
+ this.emit("validationChanged", this.validate());
204
+ }
205
+ updateParams(nodeId, params) {
206
+ const n = this.def.nodes.find((n) => n.nodeId === nodeId);
207
+ if (!n)
208
+ return;
209
+ n.params = { ...(n.params ?? {}), ...params };
210
+ this.emit("graphChanged", {
211
+ def: this.def,
212
+ change: { type: "updateParams", nodeId },
213
+ });
214
+ }
215
+ // Position and selection APIs for React Flow bridge
216
+ setPosition(nodeId, pos) {
217
+ this.positions[nodeId] = pos;
218
+ this.emit("graphUiChanged", {
219
+ def: this.def,
220
+ change: { type: "moveNode", nodeId, pos },
221
+ });
222
+ }
223
+ setPositions(map) {
224
+ this.positions = { ...map };
225
+ this.emit("graphUiChanged", {
226
+ def: this.def,
227
+ change: { type: "moveNodes" },
228
+ });
229
+ }
230
+ getPositions() {
231
+ return { ...this.positions };
232
+ }
233
+ setSelection(sel) {
234
+ this.selection = { nodes: [...sel.nodes], edges: [...sel.edges] };
235
+ this.emit("selectionChanged", this.selection);
236
+ }
237
+ getSelection() {
238
+ return {
239
+ nodes: [...this.selection.nodes],
240
+ edges: [...this.selection.edges],
241
+ };
242
+ }
243
+ toggleNodeSelection(nodeId) {
244
+ this.selection.nodes = this.selection.nodes.includes(nodeId)
245
+ ? this.selection.nodes.filter((id) => id !== nodeId)
246
+ : [...this.selection.nodes, nodeId];
247
+ this.emit("selectionChanged", this.selection);
248
+ }
249
+ toggleEdgeSelection(edgeId) {
250
+ this.selection.edges = this.selection.edges.includes(edgeId)
251
+ ? this.selection.edges.filter((id) => id !== edgeId)
252
+ : [...this.selection.edges, edgeId];
253
+ this.emit("selectionChanged", this.selection);
254
+ }
255
+ on(event, handler) {
256
+ if (!this.listeners.has(event))
257
+ this.listeners.set(event, new Set());
258
+ const set = this.listeners.get(event);
259
+ set.add(handler);
260
+ return () => set.delete(handler);
261
+ }
262
+ emit(event, payload) {
263
+ const set = this.listeners.get(event);
264
+ if (set)
265
+ for (const h of Array.from(set))
266
+ h(payload);
267
+ }
268
+ generateId(prefix) {
269
+ return `${prefix}${Math.random().toString(36).slice(2, 8)}`;
270
+ }
271
+ }
272
+
273
+ class CLIWorkbench {
274
+ constructor(wb, deps = {}) {
275
+ this.wb = wb;
276
+ this.deps = deps;
277
+ }
278
+ async load(def) {
279
+ await this.wb.load(def);
280
+ }
281
+ print(def, options) {
282
+ const d = def ?? this.wb.export();
283
+ const detail = !!options?.detail;
284
+ const lines = [];
285
+ lines.push(`Nodes (${d.nodes.length})`);
286
+ for (const n of d.nodes) {
287
+ if (!detail) {
288
+ lines.push(` - ${n.nodeId}: ${n.typeId}`);
289
+ }
290
+ else {
291
+ const reg = this.wb["registry"];
292
+ const desc = reg?.nodes?.get(n.typeId);
293
+ const inputs = Object.entries(desc?.inputs ?? {})
294
+ .map(([h, t]) => `${h}:${t}`)
295
+ .join(", ");
296
+ const outputs = Object.entries(desc?.outputs ?? {})
297
+ .map(([h, t]) => `${h}:${t}`)
298
+ .join(", ");
299
+ const params = n.params ? JSON.stringify(n.params) : "{}";
300
+ lines.push(` - ${n.nodeId}: ${n.typeId}`);
301
+ lines.push(` inputs: ${inputs || "-"}`);
302
+ lines.push(` outputs: ${outputs || "-"}`);
303
+ lines.push(` params: ${params}`);
304
+ const inVals = options?.values?.inputs?.[n.nodeId];
305
+ const outVals = options?.values?.outputs?.[n.nodeId];
306
+ if (inVals && Object.keys(inVals).length > 0)
307
+ lines.push(` inputValues: ${JSON.stringify(inVals)}`);
308
+ if (outVals && Object.keys(outVals).length > 0)
309
+ lines.push(` outputValues: ${JSON.stringify(outVals)}`);
310
+ }
311
+ }
312
+ lines.push(`Edges (${d.edges.length})`);
313
+ for (const e of d.edges) {
314
+ lines.push(` - ${e.id}: ${e.source.nodeId}.${e.source.handle} -> ${e.target.nodeId}.${e.target.handle} (${e.typeId})`);
315
+ }
316
+ return lines.join("\n");
317
+ }
318
+ get actions() {
319
+ return this.wb;
320
+ }
321
+ }
322
+
323
+ function toReactFlow$1(def, positions = {}) {
324
+ const nodes = def.nodes.map((n) => ({
325
+ id: n.nodeId,
326
+ data: { typeId: n.typeId, params: n.params },
327
+ position: positions[n.nodeId] ?? { x: 0, y: 0 },
328
+ }));
329
+ const edges = def.edges.map((e) => ({
330
+ id: e.id,
331
+ source: e.source.nodeId,
332
+ target: e.target.nodeId,
333
+ sourceHandle: e.source.handle,
334
+ targetHandle: e.target.handle,
335
+ }));
336
+ return { nodes, edges };
337
+ }
338
+ class ReactFlowWorkbench {
339
+ constructor(wb) {
340
+ this.wb = wb;
341
+ }
342
+ get actions() {
343
+ return this.wb;
344
+ }
345
+ async load(def) {
346
+ await this.wb.load(def);
347
+ }
348
+ export(def) {
349
+ const d = def ?? this.wb.export();
350
+ return toReactFlow$1(d);
351
+ }
352
+ }
353
+
354
+ const WorkbenchContext = React.createContext(null);
355
+ function useWorkbenchContext() {
356
+ const ctx = React.useContext(WorkbenchContext);
357
+ if (!ctx)
358
+ throw new Error("useWorkbenchContext must be used within WorkbenchProvider");
359
+ return ctx;
360
+ }
361
+
362
+ function useWorkbenchBridge(wb) {
363
+ const onConnect = React.useCallback((params) => {
364
+ if (!params.source || !params.target)
365
+ return;
366
+ if (!params.sourceHandle || !params.targetHandle)
367
+ return;
368
+ wb.connect({
369
+ source: { nodeId: params.source, handle: params.sourceHandle },
370
+ target: { nodeId: params.target, handle: params.targetHandle },
371
+ });
372
+ }, [wb]);
373
+ const onNodesChange = React.useCallback((changes) => {
374
+ changes.forEach((c) => {
375
+ if (c.type === "position" && c.position)
376
+ wb.setPosition(c.id, c.position);
377
+ if (c.type === "remove")
378
+ wb.removeNode(c.id);
379
+ if (c.type === "select")
380
+ wb.toggleNodeSelection(c.id);
381
+ });
382
+ }, [wb]);
383
+ const onEdgesDelete = React.useCallback((edges) => edges.forEach((e) => wb.disconnect(e.id)), [wb]);
384
+ const onEdgesChange = React.useCallback((changes) => {
385
+ changes.forEach((c) => {
386
+ if (c.type === "remove")
387
+ wb.disconnect(c.id);
388
+ else if (c.type === "select")
389
+ wb.toggleEdgeSelection(c.id);
390
+ });
391
+ }, [wb]);
392
+ const onNodesDelete = React.useCallback((nodes) => {
393
+ for (const n of nodes)
394
+ wb.removeNode(n.id);
395
+ }, [wb]);
396
+ const onSelectionChange = React.useCallback((sel) => {
397
+ const next = {
398
+ nodes: sel.nodes.map((n) => n.id),
399
+ edges: sel.edges.map((e) => e.id),
400
+ };
401
+ const cur = wb.getSelection();
402
+ const sameLen = cur.nodes.length === next.nodes.length &&
403
+ cur.edges.length === next.edges.length;
404
+ const same = sameLen &&
405
+ cur.nodes.every((id, i) => id === next.nodes[i]) &&
406
+ cur.edges.every((id, i) => id === next.edges[i]);
407
+ if (!same)
408
+ wb.setSelection(next);
409
+ }, [wb]);
410
+ return {
411
+ onConnect,
412
+ onNodesChange,
413
+ onEdgesChange,
414
+ onEdgesDelete,
415
+ onNodesDelete,
416
+ onSelectionChange,
417
+ };
418
+ }
419
+ function useWorkbenchGraphTick(wb) {
420
+ const [tick, setTick] = React.useState(0);
421
+ React.useEffect(() => {
422
+ const bump = () => setTick((t) => t + 1);
423
+ const off = wb.on("graphChanged", bump);
424
+ return () => off();
425
+ }, [wb]);
426
+ return tick;
427
+ }
428
+ function useWorkbenchGraphUiTick(wb) {
429
+ const [tick, setTick] = React.useState(0);
430
+ React.useEffect(() => {
431
+ const bump = () => setTick((t) => t + 1);
432
+ const off = wb.on("graphUiChanged", bump);
433
+ return () => off();
434
+ }, [wb]);
435
+ return tick;
436
+ }
437
+ function useWorkbenchVersionTick(runner) {
438
+ const [version, setVersion] = React.useState(0);
439
+ React.useEffect(() => {
440
+ const bump = () => setVersion((v) => v + 1);
441
+ const u1 = runner.on("value", bump);
442
+ const u2 = runner.on("error", bump);
443
+ const u3 = runner.on("invalidate", bump);
444
+ const u4 = runner.on("status", bump);
445
+ const u5 = runner.on("stats", bump);
446
+ return () => {
447
+ u1();
448
+ u2();
449
+ u3();
450
+ u4();
451
+ u5();
452
+ };
453
+ }, [runner]);
454
+ return version;
455
+ }
456
+ // Query param helpers
457
+ function setSearchParam(key, val) {
458
+ if (typeof window === "undefined")
459
+ return;
460
+ const url = new URL(window.location.href);
461
+ if (val === undefined || val === "")
462
+ url.searchParams.delete(key);
463
+ else
464
+ url.searchParams.set(key, val);
465
+ window.history.replaceState({}, "", url.toString());
466
+ }
467
+ function useQueryParamBoolean(key, defaultValue) {
468
+ const initial = React.useMemo(() => {
469
+ if (typeof window === "undefined")
470
+ return defaultValue;
471
+ const sp = new URLSearchParams(window.location.search);
472
+ const v = sp.get(key);
473
+ if (v === null)
474
+ return defaultValue;
475
+ return v === "1" || v === "true";
476
+ }, [key, defaultValue]);
477
+ const [val, setVal] = React.useState(initial);
478
+ const set = React.useCallback((v) => {
479
+ setVal(v);
480
+ setSearchParam(key, v ? "1" : undefined);
481
+ }, [key]);
482
+ React.useEffect(() => {
483
+ const onPop = () => {
484
+ const sp = new URLSearchParams(window.location.search);
485
+ const v = sp.get(key);
486
+ setVal(v === "1" || v === "true");
487
+ };
488
+ window.addEventListener("popstate", onPop);
489
+ return () => window.removeEventListener("popstate", onPop);
490
+ }, [key]);
491
+ return [val, set];
492
+ }
493
+ function useQueryParamString(key, defaultValue) {
494
+ const initial = React.useMemo(() => {
495
+ if (typeof window === "undefined")
496
+ return defaultValue;
497
+ const sp = new URLSearchParams(window.location.search);
498
+ const v = sp.get(key);
499
+ return v ?? defaultValue;
500
+ }, [key, defaultValue]);
501
+ const [val, setVal] = React.useState(initial);
502
+ const set = React.useCallback((v) => {
503
+ setVal(v);
504
+ setSearchParam(key, v);
505
+ }, [key]);
506
+ React.useEffect(() => {
507
+ const onPop = () => {
508
+ const sp = new URLSearchParams(window.location.search);
509
+ const v = sp.get(key) ?? undefined;
510
+ setVal(v);
511
+ };
512
+ window.addEventListener("popstate", onPop);
513
+ return () => window.removeEventListener("popstate", onPop);
514
+ }, [key]);
515
+ return [val, set];
516
+ }
517
+
518
+ function WorkbenchProvider({ wb, runner, registry, setRegistry, children, }) {
519
+ const [nodeStatus, setNodeStatus] = React.useState({});
520
+ const [edgeStatus, setEdgeStatus] = React.useState({});
521
+ const [events, setEvents] = React.useState([]);
522
+ const clearEvents = React.useCallback(() => setEvents([]), []);
523
+ // Validation
524
+ const [validation, setValidation] = React.useState(undefined);
525
+ // Selection (mirror workbench selectionChanged)
526
+ const [selectedNodeId, setSelectedNodeId] = React.useState();
527
+ const [selectedEdgeId, setSelectedEdgeId] = React.useState();
528
+ const setSelection = React.useCallback((sel) => wb.setSelection(sel), [wb]);
529
+ // Ticks
530
+ const graphTick = useWorkbenchGraphTick(wb);
531
+ const graphUiTick = useWorkbenchGraphUiTick(wb);
532
+ const versionTick = useWorkbenchVersionTick(runner);
533
+ const valuesTick = versionTick + graphTick + graphUiTick;
534
+ // Def and IO values
535
+ const def = wb.export();
536
+ const inputsMap = React.useMemo(() => runner.getInputs(def), [runner, def, valuesTick]);
537
+ const outputsMap = React.useMemo(() => runner.getOutputs(def), [runner, def, valuesTick]);
538
+ // Auto layout (simple layered layout)
539
+ const runAutoLayout = React.useCallback(() => {
540
+ const cur = wb.export();
541
+ const indegree = {};
542
+ const adj = {};
543
+ for (const n of cur.nodes) {
544
+ indegree[n.nodeId] = 0;
545
+ adj[n.nodeId] = [];
546
+ }
547
+ for (const e of cur.edges) {
548
+ indegree[e.target.nodeId] = (indegree[e.target.nodeId] ?? 0) + 1;
549
+ adj[e.source.nodeId].push(e.target.nodeId);
550
+ }
551
+ const q = Object.keys(indegree).filter((k) => indegree[k] === 0);
552
+ const layers = [];
553
+ while (q.length) {
554
+ const layer = [];
555
+ const next = [];
556
+ for (const id of q) {
557
+ layer.push(id);
558
+ for (const nb of adj[id]) {
559
+ indegree[nb] -= 1;
560
+ if (indegree[nb] === 0)
561
+ next.push(nb);
562
+ }
563
+ }
564
+ layers.push(layer);
565
+ q.splice(0, q.length, ...next);
566
+ }
567
+ const X = 360;
568
+ const Y = 180;
569
+ const pos = {};
570
+ layers.forEach((layer, layerIndex) => {
571
+ layer.forEach((id, itemIndex) => {
572
+ pos[id] = { x: layerIndex * X, y: itemIndex * Y };
573
+ });
574
+ });
575
+ wb.setPositions(pos);
576
+ }, [wb]);
577
+ // Subscribe to runner/workbench events
578
+ React.useEffect(() => {
579
+ const add = (source, type) => (payload) => setEvents((prev) => {
580
+ if (source === "workbench" &&
581
+ (type === "graphChanged" || type === "graphUiChanged")) {
582
+ const changeType = payload?.change?.type;
583
+ if (changeType === "moveNode" || changeType === "moveNodes")
584
+ return prev;
585
+ }
586
+ const next = [
587
+ { at: Date.now(), source, type, payload: structuredClone(payload) },
588
+ ...prev,
589
+ ];
590
+ return next.length > 200 ? next.slice(0, 200) : next;
591
+ });
592
+ const off1 = runner.on("value", (e) => {
593
+ if (e?.io === "input") {
594
+ const nodeId = e?.nodeId;
595
+ setNodeStatus((s) => ({
596
+ ...s,
597
+ [nodeId]: { ...(s[nodeId] ?? {}), invalidated: true },
598
+ }));
599
+ }
600
+ return add("runner", "value")(e);
601
+ });
602
+ const off2 = runner.on("error", (e) => {
603
+ const edgeError = e;
604
+ const nodeError = e;
605
+ if (edgeError?.kind === "edge-convert") {
606
+ const edgeId = edgeError.edgeId;
607
+ setEdgeStatus((s) => ({
608
+ ...s,
609
+ [edgeId]: { ...(s[edgeId] ?? {}), lastError: edgeError.err },
610
+ }));
611
+ }
612
+ else if (nodeError?.nodeId) {
613
+ const nodeId = nodeError?.nodeId;
614
+ setNodeStatus((s) => ({
615
+ ...s,
616
+ [nodeId]: {
617
+ ...(s[nodeId] ?? {}),
618
+ lastError: nodeError?.err,
619
+ },
620
+ }));
621
+ }
622
+ return add("runner", "error")(e);
623
+ });
624
+ const off3 = runner.on("invalidate", (e) => {
625
+ if (e?.reason === "graph-updated") {
626
+ setNodeStatus((s) => {
627
+ const next = {};
628
+ for (const n of wb.export().nodes) {
629
+ next[n.nodeId] = { ...(s[n.nodeId] ?? {}), invalidated: true };
630
+ }
631
+ return next;
632
+ });
633
+ }
634
+ return add("runner", "invalidate")(e);
635
+ });
636
+ const off3b = runner.on("stats", (s) => {
637
+ if (!s)
638
+ return;
639
+ if (s.kind === "node-start") {
640
+ const id = s.nodeId;
641
+ setNodeStatus((prev) => ({
642
+ ...prev,
643
+ [id]: {
644
+ ...(prev[id] ?? {}),
645
+ running: true,
646
+ progress: 0,
647
+ invalidated: false,
648
+ },
649
+ }));
650
+ }
651
+ else if (s.kind === "node-progress") {
652
+ const id = s.nodeId;
653
+ setNodeStatus((prev) => ({
654
+ ...prev,
655
+ [id]: {
656
+ ...(prev[id] ?? {}),
657
+ running: true,
658
+ progress: Number(s.progress) || 0,
659
+ },
660
+ }));
661
+ }
662
+ else if (s.kind === "node-done") {
663
+ const id = s.nodeId;
664
+ setNodeStatus((prev) => ({
665
+ ...prev,
666
+ [id]: { ...(prev[id] ?? {}), running: false },
667
+ }));
668
+ }
669
+ else if (s.kind === "edge-start") {
670
+ const id = s.edgeId;
671
+ setEdgeStatus((prev) => ({
672
+ ...prev,
673
+ [id]: { ...(prev[id] ?? {}), running: true },
674
+ }));
675
+ }
676
+ else if (s.kind === "edge-done") {
677
+ const id = s.edgeId;
678
+ setEdgeStatus((prev) => ({
679
+ ...prev,
680
+ [id]: { ...(prev[id] ?? {}), running: false },
681
+ }));
682
+ }
683
+ return add("runner", "stats")(s);
684
+ });
685
+ const off4 = wb.on("graphChanged", add("workbench", "graphChanged"));
686
+ const off4b = wb.on("graphUiChanged", add("workbench", "graphUiChanged"));
687
+ const off5 = wb.on("validationChanged", add("workbench", "validationChanged"));
688
+ const off5b = wb.on("validationChanged", (r) => setValidation(r));
689
+ const off6 = wb.on("selectionChanged", (sel) => {
690
+ setSelectedNodeId(sel.nodes?.[0]);
691
+ setSelectedEdgeId(sel.edges?.[0]);
692
+ });
693
+ const off7 = wb.on("error", add("workbench", "error"));
694
+ wb.refreshValidation();
695
+ return () => {
696
+ off1();
697
+ off2();
698
+ off3();
699
+ off3b();
700
+ off4();
701
+ off4b();
702
+ off5();
703
+ off5b();
704
+ off6();
705
+ off7();
706
+ };
707
+ }, [runner, wb]);
708
+ // Push incremental updates into running engine without full reload
709
+ React.useEffect(() => {
710
+ if (runner.isRunning()) {
711
+ try {
712
+ runner.update(def);
713
+ }
714
+ catch { }
715
+ }
716
+ }, [runner, def, graphTick]);
717
+ const validationByNode = React.useMemo(() => {
718
+ const inputs = {};
719
+ const outputs = {};
720
+ const issues = {};
721
+ if (!validation)
722
+ return { inputs, outputs, issues };
723
+ for (const is of validation.issues ?? []) {
724
+ const d = is?.data;
725
+ const level = is?.level;
726
+ const code = String(is?.code ?? "");
727
+ const message = String(is?.message ?? code);
728
+ if (!d)
729
+ continue;
730
+ if (d.nodeId) {
731
+ if (d.input) {
732
+ const arr = inputs[d.nodeId] ?? (inputs[d.nodeId] = []);
733
+ arr.push({ handle: String(d.input), level, message, code });
734
+ const nodeArr = issues[d.nodeId] ?? (issues[d.nodeId] = []);
735
+ nodeArr.push({ level, message, code });
736
+ }
737
+ if (d.output) {
738
+ const arr = outputs[d.nodeId] ?? (outputs[d.nodeId] = []);
739
+ arr.push({ handle: String(d.output), level, message, code });
740
+ const nodeArr = issues[d.nodeId] ?? (issues[d.nodeId] = []);
741
+ nodeArr.push({ level, message, code });
742
+ }
743
+ if (!d.input && !d.output) {
744
+ const arr = issues[d.nodeId] ?? (issues[d.nodeId] = []);
745
+ arr.push({ level, message, code });
746
+ }
747
+ }
748
+ }
749
+ return { inputs, outputs, issues };
750
+ }, [validation]);
751
+ const validationGlobal = React.useMemo(() => {
752
+ const list = [];
753
+ if (!validation)
754
+ return list;
755
+ for (const is of validation.issues ?? []) {
756
+ const d = is?.data;
757
+ const level = is?.level;
758
+ const code = String(is?.code ?? "");
759
+ const message = String(is?.message ?? code);
760
+ if (!d || (!d.nodeId && !d.edgeId)) {
761
+ list.push({ level, code, message });
762
+ }
763
+ }
764
+ return list;
765
+ }, [validation]);
766
+ const validationByEdge = React.useMemo(() => {
767
+ const errors = {};
768
+ const issues = {};
769
+ if (!validation)
770
+ return { errors, issues };
771
+ for (const is of validation.issues ?? []) {
772
+ const d = is?.data;
773
+ const level = is?.level;
774
+ const code = String(is?.code ?? "");
775
+ const message = String(is?.message ?? code);
776
+ if (d?.edgeId) {
777
+ if (level === "error")
778
+ errors[d.edgeId] = true;
779
+ const arr = issues[d.edgeId] ?? (issues[d.edgeId] = []);
780
+ arr.push({ level, message, code });
781
+ }
782
+ }
783
+ return { errors, issues };
784
+ }, [validation]);
785
+ const isRunning = React.useCallback(() => runner.isRunning(), [runner]);
786
+ const engineKind = React.useCallback(() => runner.getRunningEngine(), [runner]);
787
+ const start = React.useCallback((engine) => {
788
+ try {
789
+ runner.launch(wb.export(), { engine });
790
+ }
791
+ catch { }
792
+ }, [runner, wb]);
793
+ const stop = React.useCallback(() => runner.dispose(), [runner]);
794
+ const step = React.useCallback(() => runner.step(), [runner]);
795
+ const flush = React.useCallback(() => runner.flush(), [runner]);
796
+ const value = React.useMemo(() => ({
797
+ wb,
798
+ runner,
799
+ registry,
800
+ setRegistry,
801
+ def,
802
+ selectedNodeId,
803
+ selectedEdgeId,
804
+ setSelection,
805
+ nodeStatus,
806
+ edgeStatus,
807
+ valuesTick,
808
+ inputsMap,
809
+ outputsMap,
810
+ validationByNode,
811
+ validationByEdge,
812
+ validationGlobal,
813
+ events,
814
+ clearEvents,
815
+ isRunning,
816
+ engineKind,
817
+ start,
818
+ stop,
819
+ step,
820
+ flush,
821
+ runAutoLayout,
822
+ }), [
823
+ wb,
824
+ runner,
825
+ registry,
826
+ setRegistry,
827
+ def,
828
+ selectedNodeId,
829
+ selectedEdgeId,
830
+ setSelection,
831
+ nodeStatus,
832
+ edgeStatus,
833
+ valuesTick,
834
+ inputsMap,
835
+ outputsMap,
836
+ validationByNode,
837
+ validationByEdge,
838
+ validationGlobal,
839
+ events,
840
+ clearEvents,
841
+ isRunning,
842
+ engineKind,
843
+ start,
844
+ stop,
845
+ step,
846
+ flush,
847
+ runAutoLayout,
848
+ ]);
849
+ return (jsxRuntime.jsx(WorkbenchContext.Provider, { value: value, children: children }));
850
+ }
851
+
852
+ function toReactFlow(def, positions, registry, selectedNodeIds, selectedEdgeIds, opts) {
853
+ const nodeHandleMap = {};
854
+ const nodes = def.nodes.map((n) => {
855
+ const desc = registry.nodes.get(n.typeId);
856
+ const inputHandles = Object.entries(desc?.inputs ?? {}).map(([id, typeId]) => ({ id, typeId }));
857
+ const outputHandles = Object.entries(desc?.outputs ?? {}).map(([id, typeId]) => ({ id, typeId }));
858
+ nodeHandleMap[n.nodeId] = {
859
+ inputs: new Set(inputHandles.map((h) => h.id)),
860
+ outputs: new Set(outputHandles.map((h) => h.id)),
861
+ };
862
+ return {
863
+ id: n.nodeId,
864
+ data: {
865
+ typeId: n.typeId,
866
+ params: n.params,
867
+ inputHandles,
868
+ outputHandles,
869
+ showValues: opts?.showValues,
870
+ inputValues: opts?.inputs?.[n.nodeId],
871
+ outputValues: opts?.outputs?.[n.nodeId],
872
+ status: opts?.nodeStatus?.[n.nodeId],
873
+ validation: {
874
+ inputs: opts?.nodeValidation?.inputs?.[n.nodeId] ?? [],
875
+ outputs: opts?.nodeValidation?.outputs?.[n.nodeId] ?? [],
876
+ issues: opts?.nodeValidation?.issues?.[n.nodeId] ?? [],
877
+ },
878
+ toDisplay: opts?.toDisplay,
879
+ },
880
+ position: positions[n.nodeId] ?? { x: 0, y: 0 },
881
+ type: opts?.resolveNodeType?.(n.typeId) ?? "@bian-womp/spark:default",
882
+ selected: selectedNodeIds ? selectedNodeIds.has(n.nodeId) : undefined,
883
+ };
884
+ });
885
+ const edges = def.edges
886
+ .filter((e) => {
887
+ const src = nodeHandleMap[e.source.nodeId];
888
+ const dst = nodeHandleMap[e.target.nodeId];
889
+ if (!src || !dst)
890
+ return false;
891
+ return src.outputs.has(e.source.handle) && dst.inputs.has(e.target.handle);
892
+ })
893
+ .map((e) => {
894
+ const st = opts?.edgeStatus?.[e.id];
895
+ const isRunning = !!st?.running;
896
+ const hasError = !!st?.lastError;
897
+ const isInvalidEdge = !!opts?.edgeValidation?.[e.id];
898
+ const style = hasError || isInvalidEdge
899
+ ? { stroke: "#ef4444", strokeWidth: 2 }
900
+ : isRunning
901
+ ? { stroke: "#3b82f6" }
902
+ : undefined;
903
+ return {
904
+ id: e.id,
905
+ source: e.source.nodeId,
906
+ target: e.target.nodeId,
907
+ sourceHandle: e.source.handle,
908
+ targetHandle: e.target.handle,
909
+ selected: selectedEdgeIds ? selectedEdgeIds.has(e.id) : undefined,
910
+ animated: isRunning,
911
+ style,
912
+ };
913
+ });
914
+ return { nodes, edges };
915
+ }
916
+
917
+ function IssueBadge({ level, title, size = 12, className, }) {
918
+ const colorClass = level === "error" ? "text-red-600" : "text-amber-600";
919
+ return (jsxRuntime.jsx("button", { type: "button", className: `inline-flex items-center justify-center shrink-0 ${colorClass} ${className ?? ""}`, title: title, style: { width: size, height: size }, children: level === "error" ? (jsxRuntime.jsx(react.XCircleIcon, { size: size, weight: "fill" })) : (jsxRuntime.jsx(react.WarningCircleIcon, { size: size, weight: "fill" })) }));
920
+ }
921
+
922
+ const DefaultNode = React.memo(function DefaultNode({ id, data, selected, isConnectable, }) {
923
+ const typeId = data.typeId;
924
+ const inputEntries = data.inputHandles ?? [];
925
+ const outputEntries = data.outputHandles ?? [];
926
+ const status = data.status ?? {};
927
+ const validation = data.validation ?? {
928
+ inputs: [],
929
+ outputs: [],
930
+ issues: [],
931
+ };
932
+ const HEADER_SIZE = 24;
933
+ const ROW_SIZE = 22;
934
+ const maxRows = Math.max(inputEntries.length, outputEntries.length);
935
+ const minHeight = HEADER_SIZE + maxRows * ROW_SIZE;
936
+ const minWidth = data.showValues ? 320 : 160;
937
+ const topFor = (i) => HEADER_SIZE + i * ROW_SIZE + ROW_SIZE / 2;
938
+ const hasError = !!status.lastError;
939
+ const isRunning = !!status.running;
940
+ const isInvalid = !!status.invalidated && !isRunning && !hasError;
941
+ const borderClasses = selected
942
+ ? "border-2 border-gray-900 dark:border-gray-100"
943
+ : hasError
944
+ ? "border-2 border-red-500"
945
+ : isRunning
946
+ ? "border-2 border-blue-500 ring-2 ring-blue-200 dark:ring-blue-900"
947
+ : isInvalid
948
+ ? "border-2 border-amber-500 border-dashed"
949
+ : "border border-gray-500 dark:border-gray-400";
950
+ const pct = Math.round(Math.max(0, Math.min(1, Number(status.progress) || 0)) * 100);
951
+ return (jsxRuntime.jsxs("div", { className: cx("rounded-lg bg-white/70 !dark:bg-stone-900 border-solid", borderClasses), style: { position: "relative", minHeight: minHeight, minWidth }, children: [jsxRuntime.jsxs("div", { className: "flex h-6 items-center justify-center px-2 border-b border-solid border-gray-500 dark:border-gray-400 text-gray-600 dark:text-gray-300", children: [jsxRuntime.jsx("strong", { className: "flex-1 h-full leading-6 text-xs", children: typeId }), jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [hasError && (jsxRuntime.jsx("span", { title: String(status.lastError?.message ?? status.lastError), children: jsxRuntime.jsx(react.XCircleIcon, { size: 12, weight: "fill", className: "text-red-500" }) })), validation.issues && validation.issues.length > 0 && (jsxRuntime.jsx(IssueBadge, { level: validation.issues.some((i) => i.level === "error")
952
+ ? "error"
953
+ : "warning", size: 12, className: "w-3 h-3", title: validation.issues
954
+ .map((v) => `${v.code}: ${v.message}`)
955
+ .join("; ") })), jsxRuntime.jsxs("span", { className: "text-[10px] opacity-70", children: ["(", id, ")"] })] })] }), (isRunning || pct > 0) && (jsxRuntime.jsx("div", { className: "h-1 bg-blue-200 dark:bg-blue-900", children: jsxRuntime.jsx("div", { className: "h-1 bg-blue-500 transition-all", style: { width: `${pct}%` } }) })), inputEntries.map((entry, i) => {
956
+ const vIssues = validation.inputs.filter((v) => v.handle === entry.id);
957
+ const hasAny = vIssues.length > 0;
958
+ const hasErr = vIssues.some((v) => v.level === "error");
959
+ const title = vIssues
960
+ .map((v) => `${v.code}: ${v.message}`)
961
+ .join("; ");
962
+ return (jsxRuntime.jsxs(React.Fragment, { children: [jsxRuntime.jsx(ReactFlow.Handle, { id: entry.id, type: "target", position: ReactFlow.Position.Left, isConnectable: isConnectable, className: cx("!w-3 !h-3 !bg-white !dark:bg-stone-900 !border-gray-500 dark:!border-gray-400", hasAny && (hasErr ? "!border-red-500" : "!border-amber-500")), style: { left: -5, top: topFor(i) } }), jsxRuntime.jsxs("div", { className: "absolute left-2 text-[11px] text-gray-700 dark:text-gray-300 pointer-events-none", style: { top: topFor(i) - 8 }, title: `${entry.id}: ${entry.typeId}`, children: [entry.id, hasAny && (jsxRuntime.jsx(IssueBadge, { level: hasErr ? "error" : "warning", size: 12, className: "ml-1", title: title })), data.showValues && (jsxRuntime.jsx("span", { className: "ml-1 opacity-60", children: data.toDisplay
963
+ ? data.toDisplay(entry.typeId, data.inputValues?.[entry.id])
964
+ : String(data.inputValues?.[entry.id]) }))] })] }, `in-${entry.id}`));
965
+ }), outputEntries.map((entry, i) => {
966
+ const vIssues = validation.outputs.filter((v) => v.handle === entry.id);
967
+ const hasAny = vIssues.length > 0;
968
+ const hasErr = vIssues.some((v) => v.level === "error");
969
+ const title = vIssues
970
+ .map((v) => `${v.code}: ${v.message}`)
971
+ .join("; ");
972
+ return (jsxRuntime.jsxs(React.Fragment, { children: [jsxRuntime.jsx(ReactFlow.Handle, { id: entry.id, type: "source", position: ReactFlow.Position.Right, isConnectable: isConnectable, className: cx("!w-3 !h-3 !bg-white !dark:bg-stone-900 !border-gray-500 dark:!border-gray-400 !rounded-none", hasAny && (hasErr ? "!border-red-500" : "!border-amber-500")), style: { right: -5, top: topFor(i) } }), jsxRuntime.jsxs("div", { className: "absolute right-2 text-[11px] text-gray-700 dark:text-gray-300 pointer-events-none", style: { top: topFor(i) - 8, textAlign: "right" }, title: `${entry.id}: ${entry.typeId}`, children: [entry.id, hasAny && (jsxRuntime.jsx(IssueBadge, { level: hasErr ? "error" : "warning", size: 12, className: "ml-1", title: title })), data.showValues && (jsxRuntime.jsx("span", { className: "ml-1 opacity-60", children: data.toDisplay
973
+ ? data.toDisplay(entry.typeId, data.outputValues?.[entry.id])
974
+ : String(data.outputValues?.[entry.id]) }))] })] }, `out-${entry.id}`));
975
+ })] }));
976
+ });
977
+ DefaultNode.displayName = "DefaultNode";
978
+
979
+ function DefaultContextMenu({ open, clientPos, onAdd, onClose, }) {
980
+ const { registry } = useWorkbenchContext();
981
+ const rf = ReactFlow.useReactFlow();
982
+ if (!open || !clientPos)
983
+ return null;
984
+ const items = Array.from(registry.nodes.keys());
985
+ const handleClick = (typeId) => {
986
+ const p = rf.project({ x: clientPos.x, y: clientPos.y });
987
+ onAdd(typeId, p);
988
+ onClose();
989
+ };
990
+ return (jsxRuntime.jsxs("div", { className: "fixed z-[1000] bg-white border border-gray-300 rounded-lg shadow-lg p-1 min-w-[180px] text-sm text-gray-700", style: { left: clientPos.x, top: clientPos.y }, onMouseLeave: onClose, children: [jsxRuntime.jsx("div", { className: "px-2 py-1 font-semibold text-gray-700", children: "Add Node" }), jsxRuntime.jsx("div", { className: "max-h-60 overflow-auto", children: items.map((id) => (jsxRuntime.jsx("button", { onClick: () => handleClick(id), className: "block w-full text-left px-2 py-1 hover:bg-gray-100 cursor-pointer", children: id }, id))) })] }));
991
+ }
992
+
993
+ function WorkbenchCanvas({ showValues, toDisplay, }) {
994
+ const { wb, registry, inputsMap, outputsMap, valuesTick, nodeStatus, edgeStatus, validationByNode, validationByEdge, } = useWorkbenchContext();
995
+ const ioValues = { inputs: inputsMap, outputs: outputsMap };
996
+ const nodeValidation = validationByNode;
997
+ const edgeValidation = validationByEdge.errors;
998
+ const { onConnect, onNodesChange, onEdgesChange, onEdgesDelete, onNodesDelete, onSelectionChange, } = useWorkbenchBridge(wb);
999
+ const { nodeTypes, resolveNodeType } = React.useMemo(() => {
1000
+ // Build nodeTypes map using UI extension registry
1001
+ const ui = wb.getUI();
1002
+ const custom = new Map();
1003
+ for (const typeId of Array.from(registry.nodes.keys())) {
1004
+ const renderer = ui.getNodeRenderer(typeId);
1005
+ if (renderer)
1006
+ custom.set(typeId, renderer);
1007
+ }
1008
+ const types = { "@bian-womp/spark:default": DefaultNode };
1009
+ for (const [typeId, comp] of custom.entries()) {
1010
+ types[`spark:${typeId}`] = comp;
1011
+ }
1012
+ const resolver = (nodeTypeId) => custom.has(nodeTypeId) ? `spark:${nodeTypeId}` : "@bian-womp/spark:default";
1013
+ return { nodeTypes: types, resolveNodeType: resolver };
1014
+ // registry is stable; ui renderers expected to be set up before mount
1015
+ }, [wb, registry]);
1016
+ const { nodes, edges } = React.useMemo(() => {
1017
+ const def = wb.export();
1018
+ const sel = wb.getSelection();
1019
+ return toReactFlow(def, wb.getPositions(), registry, new Set(sel.nodes), new Set(sel.edges), {
1020
+ showValues,
1021
+ inputs: ioValues.inputs,
1022
+ outputs: ioValues.outputs,
1023
+ resolveNodeType,
1024
+ toDisplay,
1025
+ nodeStatus,
1026
+ edgeStatus,
1027
+ nodeValidation,
1028
+ edgeValidation,
1029
+ });
1030
+ }, [
1031
+ showValues,
1032
+ ioValues,
1033
+ valuesTick,
1034
+ toDisplay,
1035
+ nodeStatus,
1036
+ edgeStatus,
1037
+ nodeValidation,
1038
+ edgeValidation,
1039
+ ]);
1040
+ const [menuOpen, setMenuOpen] = React.useState(false);
1041
+ const [menuPos, setMenuPos] = React.useState(null);
1042
+ const onContextMenu = (e) => {
1043
+ e.preventDefault();
1044
+ setMenuPos({ x: e.clientX, y: e.clientY });
1045
+ setMenuOpen(true);
1046
+ };
1047
+ const addNodeAt = (typeId, pos) => {
1048
+ wb.addNode({ typeId, position: pos });
1049
+ };
1050
+ return (jsxRuntime.jsx("div", { className: "w-full h-full", onContextMenu: onContextMenu, children: jsxRuntime.jsxs(ReactFlow, { nodes: nodes, edges: edges, nodeTypes: nodeTypes, selectionOnDrag: true, onConnect: onConnect, onEdgesChange: onEdgesChange, onEdgesDelete: onEdgesDelete, onNodesDelete: onNodesDelete, onNodesChange: onNodesChange, onSelectionChange: onSelectionChange, deleteKeyCode: ["Backspace", "Delete"], fitView: true, children: [jsxRuntime.jsx(ReactFlow.Background, {}), jsxRuntime.jsx(ReactFlow.MiniMap, {}), jsxRuntime.jsx(ReactFlow.Controls, {}), jsxRuntime.jsx(DefaultContextMenu, { open: menuOpen, clientPos: menuPos, onAdd: addNodeAt, onClose: () => setMenuOpen(false) })] }) }));
1051
+ }
1052
+
1053
+ function DebugEvents({ autoScroll, onAutoScrollChange, hideWorkbench, onHideWorkbenchChange, }) {
1054
+ const { events, clearEvents } = useWorkbenchContext();
1055
+ const scrollRef = React.useRef(null);
1056
+ const rows = React.useMemo(() => {
1057
+ const filtered = hideWorkbench
1058
+ ? events.filter((e) => e.source !== "workbench")
1059
+ : events;
1060
+ return filtered.slice().reverse();
1061
+ }, [events, hideWorkbench]);
1062
+ React.useEffect(() => {
1063
+ if (!autoScroll)
1064
+ return;
1065
+ const el = scrollRef.current;
1066
+ if (!el)
1067
+ return;
1068
+ el.scrollTop = el.scrollHeight;
1069
+ }, [rows, autoScroll]);
1070
+ const renderPayload = (v) => {
1071
+ try {
1072
+ return JSON.stringify(v, null, 0);
1073
+ }
1074
+ catch {
1075
+ return String(v);
1076
+ }
1077
+ };
1078
+ return (jsxRuntime.jsxs("div", { className: "flex flex-col h-full min-h-0", children: [jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-1", children: [jsxRuntime.jsx("div", { className: "font-semibold", children: "Events" }), jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [jsxRuntime.jsxs("label", { className: "flex items-center gap-1 text-xs text-gray-700", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: hideWorkbench, onChange: (e) => onHideWorkbenchChange?.(e.target.checked) }), jsxRuntime.jsx("span", { children: "Hide workbench" })] }), jsxRuntime.jsxs("label", { className: "flex items-center gap-1 text-xs text-gray-700", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: autoScroll, onChange: (e) => onAutoScrollChange?.(e.target.checked) }), jsxRuntime.jsx("span", { children: "Auto scroll" })] }), jsxRuntime.jsx("button", { onClick: clearEvents, className: "text-xs px-2 py-0.5 border border-gray-300 rounded", children: "Clear" })] })] }), jsxRuntime.jsx("div", { ref: scrollRef, className: "flex-1 overflow-auto text-[11px] leading-4 divide-y divide-gray-200", children: rows.map((ev, idx) => (jsxRuntime.jsxs("div", { className: "opacity-85 odd:bg-gray-50 px-2 py-1", children: [jsxRuntime.jsxs("div", { className: "flex items-baseline gap-2", children: [jsxRuntime.jsx("span", { className: "w-8 shrink-0 text-right text-gray-500 select-none", children: idx + 1 }), jsxRuntime.jsxs("span", { className: "text-gray-500", children: [new Date(ev.at).toLocaleTimeString(), " \u00B7 ", ev.source, ":", ev.type] })] }), jsxRuntime.jsx("pre", { className: "m-0 whitespace-pre-wrap ml-10", children: renderPayload(ev.payload) })] }, `${ev.at}:${idx}`))) })] }));
1079
+ }
1080
+
1081
+ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHideWorkbenchChange, toDisplay, setInput, }) {
1082
+ const { registry, def, selectedNodeId, selectedEdgeId, inputsMap, outputsMap, nodeStatus, validationByNode, validationByEdge, validationGlobal, valuesTick, } = useWorkbenchContext();
1083
+ const nodeValidationIssues = validationByNode.issues;
1084
+ const edgeValidationIssues = validationByEdge.issues;
1085
+ const nodeValidationHandles = validationByNode;
1086
+ const globalValidationIssues = validationGlobal;
1087
+ const selectedNode = def.nodes.find((n) => n.nodeId === selectedNodeId);
1088
+ const selectedEdge = def.edges.find((e) => e.id === selectedEdgeId);
1089
+ const selectedDesc = selectedNode
1090
+ ? registry.nodes.get(selectedNode.typeId)
1091
+ : undefined;
1092
+ const inputHandles = Object.keys(selectedDesc?.inputs ?? {});
1093
+ const outputHandles = Object.keys(selectedDesc?.outputs ?? {});
1094
+ const nodeInputs = selectedNodeId ? inputsMap[selectedNodeId] ?? {} : {};
1095
+ const nodeOutputs = selectedNodeId ? outputsMap[selectedNodeId] ?? {} : {};
1096
+ const selectedNodeStatus = selectedNodeId
1097
+ ? nodeStatus?.[selectedNodeId]
1098
+ : undefined;
1099
+ const selectedNodeValidation = selectedNodeId
1100
+ ? nodeValidationIssues?.[selectedNodeId] ?? []
1101
+ : [];
1102
+ const selectedEdgeValidation = selectedEdge
1103
+ ? edgeValidationIssues?.[selectedEdge.id] ?? []
1104
+ : [];
1105
+ const selectedNodeHandleValidation = selectedNodeId
1106
+ ? {
1107
+ inputs: nodeValidationHandles?.inputs?.[selectedNodeId] ?? [],
1108
+ outputs: nodeValidationHandles?.outputs?.[selectedNodeId] ?? [],
1109
+ }
1110
+ : { inputs: [], outputs: [] };
1111
+ // Local drafts and originals for commit-on-blur/enter behavior
1112
+ const [drafts, setDrafts] = React.useState({});
1113
+ const [originals, setOriginals] = React.useState({});
1114
+ // Initialize drafts from current inputs whenever selection or valuesTick change,
1115
+ // but do not clobber fields currently being edited (dirty drafts)
1116
+ React.useEffect(() => {
1117
+ const shallowEqual = (a, b) => {
1118
+ const ak = Object.keys(a);
1119
+ const bk = Object.keys(b);
1120
+ if (ak.length !== bk.length)
1121
+ return false;
1122
+ for (const k of ak)
1123
+ if (a[k] !== b[k])
1124
+ return false;
1125
+ return true;
1126
+ };
1127
+ if (!selectedNodeId) {
1128
+ if (Object.keys(drafts).length || Object.keys(originals).length) {
1129
+ setDrafts({});
1130
+ setOriginals({});
1131
+ }
1132
+ return;
1133
+ }
1134
+ const desc = selectedDesc;
1135
+ const handles = Object.keys(desc?.inputs ?? {});
1136
+ const nextDrafts = { ...drafts };
1137
+ const nextOriginals = { ...originals };
1138
+ for (const h of handles) {
1139
+ const typeId = desc?.inputs?.[h];
1140
+ const current = nodeInputs[h];
1141
+ const display = toDisplay(typeId, current);
1142
+ const wasOriginal = originals[h];
1143
+ const isDirty = drafts[h] !== undefined &&
1144
+ wasOriginal !== undefined &&
1145
+ drafts[h] !== wasOriginal;
1146
+ if (!isDirty) {
1147
+ nextDrafts[h] = display;
1148
+ nextOriginals[h] = display;
1149
+ }
1150
+ }
1151
+ if (!shallowEqual(drafts, nextDrafts))
1152
+ setDrafts(nextDrafts);
1153
+ if (!shallowEqual(originals, nextOriginals))
1154
+ setOriginals(nextOriginals);
1155
+ }, [selectedNodeId, selectedDesc, valuesTick]);
1156
+ const widthClass = debug ? "w-[480px]" : "w-[320px]";
1157
+ return (jsxRuntime.jsxs("div", { className: `${widthClass} border-l border-gray-300 p-3 flex flex-col h-full min-h-0 overflow-hidden`, children: [jsxRuntime.jsx("div", { className: "font-semibold mb-2", children: "Inspector" }), jsxRuntime.jsx("div", { className: "flex-1 overflow-auto", children: !selectedNode && !selectedEdge ? (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("div", { className: "text-gray-500", children: "Select a node or edge." }), globalValidationIssues && globalValidationIssues.length > 0 && (jsxRuntime.jsxs("div", { className: "mt-2 text-xs bg-red-50 border border-red-200 rounded px-2 py-1", children: [jsxRuntime.jsx("div", { className: "font-semibold mb-1", children: "Validation" }), jsxRuntime.jsx("ul", { className: "list-disc ml-4", children: globalValidationIssues.map((m, i) => (jsxRuntime.jsxs("li", { className: "flex items-center gap-1", children: [jsxRuntime.jsx(IssueBadge, { level: m.level, size: 24, className: "w-6 h-6" }), jsxRuntime.jsx("span", { children: `${m.code}: ${m.message}` })] }, i))) })] }))] })) : selectedEdge ? (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsxs("div", { className: "mb-2", children: [jsxRuntime.jsxs("div", { children: ["Edge: ", selectedEdge.id] }), jsxRuntime.jsxs("div", { children: [selectedEdge.source.nodeId, ".", selectedEdge.source.handle, " \u2192", " ", selectedEdge.target.nodeId, ".", selectedEdge.target.handle] }), jsxRuntime.jsxs("div", { children: ["Type: ", selectedEdge.typeId] })] }), selectedEdgeValidation.length > 0 && (jsxRuntime.jsxs("div", { className: "mt-2 text-xs bg-red-50 border border-red-200 rounded px-2 py-1", children: [jsxRuntime.jsx("div", { className: "font-semibold mb-1", children: "Validation" }), jsxRuntime.jsx("ul", { className: "list-disc ml-4", children: selectedEdgeValidation.map((m, i) => (jsxRuntime.jsxs("li", { className: "flex items-center gap-1", children: [jsxRuntime.jsx(IssueBadge, { level: m.level, size: 24, className: "w-6 h-6" }), jsxRuntime.jsx("span", { children: `${m.code}: ${m.message}` })] }, i))) })] }))] })) : (jsxRuntime.jsxs("div", { children: [selectedNode && (jsxRuntime.jsxs("div", { className: "mb-2", children: [jsxRuntime.jsxs("div", { children: ["Node: ", selectedNode.nodeId] }), jsxRuntime.jsxs("div", { children: ["Type: ", selectedNode.typeId] }), !!selectedNodeStatus?.lastError && (jsxRuntime.jsx("div", { className: "mt-2 text-sm text-red-700 bg-red-50 border border-red-200 rounded px-2 py-1 break-words", children: String(selectedNodeStatus.lastError?.message ??
1158
+ selectedNodeStatus.lastError) }))] })), jsxRuntime.jsxs("div", { className: "mb-2", children: [jsxRuntime.jsx("div", { className: "font-semibold mb-1", children: "Inputs" }), inputHandles.length === 0 ? (jsxRuntime.jsx("div", { className: "text-gray-500", children: "No inputs" })) : (inputHandles.map((h) => {
1159
+ const typeId = (selectedDesc?.inputs ?? {})[h];
1160
+ const isLinked = def.edges.some((e) => e.target.nodeId === selectedNodeId && e.target.handle === h);
1161
+ const commonProps = {
1162
+ style: { flex: 1 },
1163
+ disabled: isLinked,
1164
+ };
1165
+ const current = nodeInputs[h];
1166
+ const value = drafts[h] ?? toDisplay(typeId, current);
1167
+ const onChangeText = (text) => setDrafts((d) => ({ ...d, [h]: text }));
1168
+ const commit = () => {
1169
+ const draft = drafts[h];
1170
+ if (draft === undefined)
1171
+ return;
1172
+ setInput(h, draft);
1173
+ setOriginals((o) => ({ ...o, [h]: draft }));
1174
+ };
1175
+ const revert = () => {
1176
+ const orig = originals[h] ?? toDisplay(typeId, current);
1177
+ setDrafts((d) => ({ ...d, [h]: orig }));
1178
+ };
1179
+ const isEnum = typeId?.startsWith("enum:");
1180
+ const inIssues = selectedNodeHandleValidation.inputs.filter((m) => m.handle === h);
1181
+ const hasValidation = inIssues.length > 0;
1182
+ const hasErr = inIssues.some((m) => m.level === "error");
1183
+ const title = inIssues
1184
+ .map((v) => `${v.code}: ${v.message}`)
1185
+ .join("; ");
1186
+ return (jsxRuntime.jsxs("div", { className: "flex items-center gap-2 mb-1", children: [jsxRuntime.jsxs("label", { className: "w-28", children: [h, jsxRuntime.jsx("span", { className: "text-gray-500 ml-1 text-[11px]", children: selectedDesc?.inputs?.[h] })] }), hasValidation && (jsxRuntime.jsx(IssueBadge, { level: hasErr ? "error" : "warning", size: 24, className: "ml-1 w-6 h-6", title: title })), isEnum ? (jsxRuntime.jsxs("select", { className: "border border-gray-300 rounded px-2 py-1 focus:outline-none focus:ring-2 focus:ring-blue-500 w-full", value: drafts[h] ?? toDisplay(typeId, current), onChange: (e) => {
1187
+ const label = String(e.target.value);
1188
+ const byLabel = registry.getEnumValue(typeId, label);
1189
+ let raw = (byLabel !== undefined ? byLabel : Number(label));
1190
+ if (!Number.isFinite(raw))
1191
+ raw = undefined;
1192
+ setInput(h, raw);
1193
+ const display = toDisplay(typeId, raw);
1194
+ setDrafts((d) => ({ ...d, [h]: display }));
1195
+ setOriginals((o) => ({ ...o, [h]: display }));
1196
+ }, ...commonProps, children: [jsxRuntime.jsx("option", { value: "", children: "(select)" }), registry.getEnumOptions?.(typeId).map((opt) => (jsxRuntime.jsx("option", { value: opt.label, children: opt.label }, opt.value)))] })) : (jsxRuntime.jsx("input", { className: "border border-gray-300 rounded px-2 py-1 focus:outline-none focus:ring-2 focus:ring-blue-500 w-full", placeholder: isLinked ? "wired" : undefined, value: value, onChange: (e) => onChangeText(e.target.value), onBlur: commit, onKeyDown: (e) => {
1197
+ if (e.key === "Enter")
1198
+ commit();
1199
+ if (e.key === "Escape")
1200
+ revert();
1201
+ }, ...commonProps }))] }, h));
1202
+ }))] }), jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("div", { className: "font-semibold mb-1", children: "Outputs" }), outputHandles.length === 0 ? (jsxRuntime.jsx("div", { className: "text-gray-500", children: "No outputs" })) : (outputHandles.map((h) => (jsxRuntime.jsxs("div", { className: "flex items-center gap-2 mb-1", children: [jsxRuntime.jsx("label", { className: "w-20", children: h }), jsxRuntime.jsx("div", { className: "flex-1", children: toDisplay(selectedDesc?.outputs?.[h], nodeOutputs[h]) }), (() => {
1203
+ const outIssues = selectedNodeHandleValidation.outputs.filter((m) => m.handle === h);
1204
+ if (outIssues.length === 0)
1205
+ return null;
1206
+ const outErr = outIssues.some((m) => m.level === "error");
1207
+ const outTitle = outIssues
1208
+ .map((v) => `${v.code}: ${v.message}`)
1209
+ .join("; ");
1210
+ return (jsxRuntime.jsx(IssueBadge, { level: outErr ? "error" : "warning", size: 24, className: "ml-1 w-6 h-6", title: outTitle }));
1211
+ })()] }, h))))] }), selectedNodeValidation.length > 0 && (jsxRuntime.jsxs("div", { className: "mt-2 text-xs bg-red-50 border border-red-200 rounded px-2 py-1", children: [jsxRuntime.jsx("div", { className: "font-semibold mb-1", children: "Validation" }), jsxRuntime.jsx("ul", { className: "list-disc ml-4", children: selectedNodeValidation.map((m, i) => (jsxRuntime.jsxs("li", { className: "flex items-center gap-1", children: [jsxRuntime.jsx(IssueBadge, { level: m.level, size: 24, className: "w-6 h-6" }), jsxRuntime.jsx("span", { children: `${m.code}: ${m.message}` })] }, i))) })] }))] })) }), debug && (jsxRuntime.jsx("div", { className: "mt-3 flex-none min-h-0 h-[50%]", children: jsxRuntime.jsx(DebugEvents, { autoScroll: !!autoScroll, hideWorkbench: !!hideWorkbench, onAutoScrollChange: onAutoScrollChange, onHideWorkbenchChange: onHideWorkbenchChange }) }))] }));
1212
+ }
1213
+
1214
+ class GraphRunner {
1215
+ constructor(registry, backend) {
1216
+ this.registry = registry;
1217
+ this.listeners = new Map();
1218
+ this.stagedInputs = {};
1219
+ this.backend = { kind: "local" };
1220
+ if (backend)
1221
+ this.backend = backend;
1222
+ }
1223
+ build(def) {
1224
+ if (this.backend.kind === "local") {
1225
+ const builder = new sparkGraph.GraphBuilder(this.registry);
1226
+ this.runtime = builder.build(def);
1227
+ return;
1228
+ }
1229
+ // Remote: no-op here; build is performed on remote server during launch
1230
+ }
1231
+ update(def) {
1232
+ if (this.backend.kind === "local") {
1233
+ if (!this.runtime)
1234
+ return;
1235
+ this.runtime.update(def, this.registry);
1236
+ this.emit("invalidate", { reason: "graph-updated" });
1237
+ return;
1238
+ }
1239
+ // Remote: forward update; ignore errors (fire-and-forget)
1240
+ void this.ensureRemote().then(async (rc) => {
1241
+ try {
1242
+ await rc.runner.update(def);
1243
+ this.emit("invalidate", { reason: "graph-updated" });
1244
+ }
1245
+ catch { }
1246
+ });
1247
+ }
1248
+ launch(def, opts) {
1249
+ if (this.engine) {
1250
+ throw new Error("Engine already running. Stop the current engine first.");
1251
+ }
1252
+ if (this.backend.kind === "local") {
1253
+ this.build(def);
1254
+ if (!this.runtime)
1255
+ throw new Error("Runtime not built");
1256
+ const rt = this.runtime;
1257
+ switch (opts.engine) {
1258
+ case "push":
1259
+ this.engine = new sparkGraph.PushEngine(rt);
1260
+ break;
1261
+ case "batched":
1262
+ this.engine = new sparkGraph.BatchedEngine(rt, {
1263
+ flushIntervalMs: opts.batched?.flushIntervalMs ?? 0,
1264
+ });
1265
+ break;
1266
+ case "pull":
1267
+ this.engine = new sparkGraph.PullEngine(rt);
1268
+ break;
1269
+ case "hybrid":
1270
+ this.engine = new sparkGraph.HybridEngine(rt, {
1271
+ windowMs: opts.hybrid?.windowMs ?? 250,
1272
+ batchThreshold: opts.hybrid?.batchThreshold ?? 3,
1273
+ });
1274
+ break;
1275
+ case "step":
1276
+ this.engine = new sparkGraph.StepEngine(rt);
1277
+ break;
1278
+ default:
1279
+ throw new Error("Unknown engine kind");
1280
+ }
1281
+ this.engine.on("value", (e) => this.emit("value", e));
1282
+ this.engine.on("error", (e) => this.emit("error", e));
1283
+ this.engine.on("invalidate", (e) => this.emit("invalidate", e));
1284
+ this.engine.on("stats", (e) => this.emit("stats", e));
1285
+ this.engine.launch();
1286
+ this.runningKind = opts.engine;
1287
+ this.emit("status", { running: true, engine: this.runningKind });
1288
+ for (const [nodeId, map] of Object.entries(this.stagedInputs)) {
1289
+ for (const [handle, value] of Object.entries(map)) {
1290
+ this.engine.setInput(nodeId, handle, value);
1291
+ }
1292
+ }
1293
+ return;
1294
+ }
1295
+ // Remote: build remotely then launch
1296
+ void this.ensureRemote().then(async (rc) => {
1297
+ await rc.runner.build(def);
1298
+ const eng = rc.runner.getEngine();
1299
+ if (!rc.listenersBound) {
1300
+ eng.on("value", (e) => {
1301
+ rc.valueCache.set(`${e.nodeId}.${e.handle}`, {
1302
+ io: e.io,
1303
+ value: e.value,
1304
+ });
1305
+ this.emit("value", e);
1306
+ });
1307
+ eng.on("error", (e) => this.emit("error", e));
1308
+ eng.on("invalidate", (e) => this.emit("invalidate", e));
1309
+ eng.on("stats", (e) => this.emit("stats", e));
1310
+ rc.listenersBound = true;
1311
+ }
1312
+ this.engine = eng;
1313
+ this.engine.launch();
1314
+ this.runningKind = "push";
1315
+ this.emit("status", { running: true, engine: this.runningKind });
1316
+ for (const [nodeId, map] of Object.entries(this.stagedInputs)) {
1317
+ for (const [handle, value] of Object.entries(map)) {
1318
+ this.engine.setInput(nodeId, handle, value);
1319
+ }
1320
+ }
1321
+ });
1322
+ }
1323
+ setInput(nodeId, handle, value) {
1324
+ if (!this.stagedInputs[nodeId])
1325
+ this.stagedInputs[nodeId] = {};
1326
+ this.stagedInputs[nodeId][handle] = value;
1327
+ if (this.engine)
1328
+ this.engine.setInput(nodeId, handle, value);
1329
+ }
1330
+ async step() {
1331
+ if (this.backend.kind !== "local")
1332
+ return; // unsupported remotely
1333
+ const eng = this.engine;
1334
+ if (eng instanceof sparkGraph.StepEngine)
1335
+ await eng.step();
1336
+ }
1337
+ async computeNode(nodeId) {
1338
+ if (this.backend.kind !== "local")
1339
+ return; // unsupported remotely
1340
+ const eng = this.engine;
1341
+ if (eng instanceof sparkGraph.PullEngine)
1342
+ await eng.computeNode(nodeId);
1343
+ }
1344
+ flush() {
1345
+ if (this.backend.kind !== "local")
1346
+ return; // unsupported remotely
1347
+ const eng = this.engine;
1348
+ if (eng instanceof sparkGraph.BatchedEngine)
1349
+ eng.flush();
1350
+ }
1351
+ getOutputs(def) {
1352
+ const out = {};
1353
+ if (this.backend.kind === "local") {
1354
+ if (!this.runtime)
1355
+ return out;
1356
+ for (const n of def.nodes) {
1357
+ const desc = this.registry.nodes.get(n.typeId);
1358
+ const handles = Object.keys(desc?.outputs ?? {});
1359
+ for (const h of handles) {
1360
+ const v = this.runtime.getOutput(n.nodeId, h);
1361
+ if (v !== undefined) {
1362
+ if (!out[n.nodeId])
1363
+ out[n.nodeId] = {};
1364
+ out[n.nodeId][h] = v;
1365
+ }
1366
+ }
1367
+ }
1368
+ return out;
1369
+ }
1370
+ const cache = this.remote?.valueCache;
1371
+ if (!cache)
1372
+ return out;
1373
+ for (const n of def.nodes) {
1374
+ const desc = this.registry.nodes.get(n.typeId);
1375
+ const handles = Object.keys(desc?.outputs ?? {});
1376
+ for (const h of handles) {
1377
+ const key = `${n.nodeId}.${h}`;
1378
+ const rec = cache.get(key);
1379
+ if (rec && rec.io === "output") {
1380
+ if (!out[n.nodeId])
1381
+ out[n.nodeId] = {};
1382
+ out[n.nodeId][h] = rec.value;
1383
+ }
1384
+ }
1385
+ }
1386
+ return out;
1387
+ }
1388
+ getInputs(def) {
1389
+ const out = {};
1390
+ if (this.backend.kind === "local") {
1391
+ for (const n of def.nodes) {
1392
+ const staged = this.stagedInputs[n.nodeId] ?? {};
1393
+ const runtimeInputs = this.runtime
1394
+ ? this.runtime.__unsafe_getNodeData?.(n.nodeId)?.inputs ?? {}
1395
+ : {};
1396
+ if (this.isRunning()) {
1397
+ out[n.nodeId] = runtimeInputs;
1398
+ }
1399
+ else {
1400
+ const merged = { ...runtimeInputs, ...staged };
1401
+ if (Object.keys(merged).length > 0)
1402
+ out[n.nodeId] = merged;
1403
+ }
1404
+ }
1405
+ return out;
1406
+ }
1407
+ const cache = this.remote?.valueCache;
1408
+ for (const n of def.nodes) {
1409
+ const staged = this.stagedInputs[n.nodeId] ?? {};
1410
+ const desc = this.registry.nodes.get(n.typeId);
1411
+ const handles = Object.keys(desc?.inputs ?? {});
1412
+ const cur = {};
1413
+ for (const h of handles) {
1414
+ const rec = cache?.get(`${n.nodeId}.${h}`);
1415
+ if (rec && rec.io === "input")
1416
+ cur[h] = rec.value;
1417
+ }
1418
+ const merged = this.isRunning() ? cur : { ...cur, ...staged };
1419
+ if (Object.keys(merged).length > 0)
1420
+ out[n.nodeId] = merged;
1421
+ }
1422
+ return out;
1423
+ }
1424
+ async whenIdle() {
1425
+ await this.engine?.whenIdle();
1426
+ }
1427
+ on(event, handler) {
1428
+ if (!this.listeners.has(event))
1429
+ this.listeners.set(event, new Set());
1430
+ const set = this.listeners.get(event);
1431
+ set.add(handler);
1432
+ return () => set.delete(handler);
1433
+ }
1434
+ emit(event, payload) {
1435
+ const set = this.listeners.get(event);
1436
+ if (set)
1437
+ for (const h of Array.from(set))
1438
+ h(payload);
1439
+ }
1440
+ dispose() {
1441
+ this.engine?.dispose();
1442
+ this.engine = undefined;
1443
+ this.runtime?.dispose();
1444
+ this.runtime = undefined;
1445
+ this.remote = undefined;
1446
+ if (this.runningKind) {
1447
+ this.runningKind = undefined;
1448
+ this.emit("status", { running: false, engine: undefined });
1449
+ }
1450
+ }
1451
+ isRunning() {
1452
+ return !!this.engine;
1453
+ }
1454
+ getRunningEngine() {
1455
+ return this.runningKind;
1456
+ }
1457
+ // Ensure remote transport/runner
1458
+ async ensureRemote() {
1459
+ if (this.remote)
1460
+ return this.remote;
1461
+ let transport;
1462
+ if (this.backend.kind === "remote-http") {
1463
+ if (!sparkRemote.HttpPollingTransport)
1464
+ throw new Error("HttpPollingTransport not available");
1465
+ transport = new sparkRemote.HttpPollingTransport(this.backend.baseUrl);
1466
+ await transport.connect();
1467
+ }
1468
+ else if (this.backend.kind === "remote-ws") {
1469
+ if (!sparkRemote.WebSocketTransport)
1470
+ throw new Error("WebSocketTransport not available");
1471
+ transport = new sparkRemote.WebSocketTransport(this.backend.url);
1472
+ await transport.connect();
1473
+ }
1474
+ else {
1475
+ throw new Error("Remote backend not configured");
1476
+ }
1477
+ const runner = new sparkRemote.RemoteRunner(transport);
1478
+ this.remote = {
1479
+ runner,
1480
+ transport,
1481
+ valueCache: new Map(),
1482
+ listenersBound: false,
1483
+ };
1484
+ return this.remote;
1485
+ }
1486
+ }
1487
+
1488
+ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, example, onExampleChange, engine, onEngineChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, }) {
1489
+ const { wb, runner, registry, def, selectedNodeId, runAutoLayout } = useWorkbenchContext();
1490
+ const selectedNode = def.nodes.find((n) => n.nodeId === selectedNodeId);
1491
+ const selectedDesc = selectedNode
1492
+ ? registry.nodes.get(selectedNode.typeId)
1493
+ : undefined;
1494
+ const [exampleState, setExampleState] = React.useState(example ?? "simple");
1495
+ const lastAutoLaunched = React.useRef(undefined);
1496
+ const autoLayoutRan = React.useRef(false);
1497
+ const applyExample = React.useCallback(async (key) => {
1498
+ if (runner.isRunning()) {
1499
+ alert(`Stop engine before switching example.`);
1500
+ return;
1501
+ }
1502
+ switch (key) {
1503
+ case "simple": {
1504
+ const r = sparkGraph.createSimpleGraphRegistry();
1505
+ setRegistry(r);
1506
+ wb.setRegistry(r);
1507
+ await wb.load(sparkGraph.createSimpleGraphDef());
1508
+ break;
1509
+ }
1510
+ case "async": {
1511
+ const r = sparkGraph.createAsyncGraphRegistry();
1512
+ setRegistry(r);
1513
+ wb.setRegistry(r);
1514
+ await wb.load(sparkGraph.createAsyncGraphDef());
1515
+ break;
1516
+ }
1517
+ case "progress": {
1518
+ const r = sparkGraph.createProgressGraphRegistry();
1519
+ setRegistry(r);
1520
+ wb.setRegistry(r);
1521
+ await wb.load(sparkGraph.createProgressGraphDef());
1522
+ break;
1523
+ }
1524
+ case "validation": {
1525
+ const r = sparkGraph.createValidationGraphRegistry();
1526
+ setRegistry(r);
1527
+ wb.setRegistry(r);
1528
+ await wb.load(sparkGraph.createValidationGraphDef());
1529
+ break;
1530
+ }
1531
+ default: {
1532
+ const r = sparkGraph.createSimpleGraphRegistry();
1533
+ setRegistry(r);
1534
+ wb.setRegistry(r);
1535
+ await wb.load(sparkGraph.createSimpleGraphDef());
1536
+ }
1537
+ }
1538
+ runAutoLayout();
1539
+ setExampleState(key);
1540
+ onExampleChange?.(key);
1541
+ }, [runner, wb, onExampleChange, runAutoLayout]);
1542
+ // Ensure initial example is loaded (and sync when example prop changes)
1543
+ React.useEffect(() => {
1544
+ applyExample(example ?? "simple");
1545
+ }, [example, wb]);
1546
+ React.useEffect(() => {
1547
+ if (!engine)
1548
+ return;
1549
+ if (runner.isRunning())
1550
+ return;
1551
+ const d = wb.export();
1552
+ if (!d.nodes || d.nodes.length === 0)
1553
+ return;
1554
+ if (lastAutoLaunched.current === engine)
1555
+ return;
1556
+ try {
1557
+ runner.launch(d, { engine: engine });
1558
+ lastAutoLaunched.current = engine;
1559
+ }
1560
+ catch {
1561
+ // ignore
1562
+ }
1563
+ }, [engine, runner, wb]);
1564
+ React.useEffect(() => {
1565
+ if (autoLayoutRan.current)
1566
+ return;
1567
+ const cur = wb.export();
1568
+ const allMissing = cur.nodes.every((n) => !wb.getPositions()[n.nodeId]);
1569
+ if (allMissing) {
1570
+ autoLayoutRan.current = true;
1571
+ runAutoLayout();
1572
+ }
1573
+ }, [wb, runAutoLayout]);
1574
+ const setInput = React.useCallback((handle, raw) => {
1575
+ if (!selectedNodeId)
1576
+ return;
1577
+ // If selected input is wired (has inbound edge), ignore user input to respect runtime value
1578
+ const isLinked = def.edges.some((e) => e.target.nodeId === selectedNodeId && e.target.handle === handle);
1579
+ if (isLinked)
1580
+ return;
1581
+ const typeId = selectedDesc?.inputs?.[handle];
1582
+ let value = raw;
1583
+ const parseArray = (s, map) => {
1584
+ const str = String(s).trim();
1585
+ try {
1586
+ const parsed = JSON.parse(str);
1587
+ if (Array.isArray(parsed))
1588
+ return parsed.map((x) => map(String(x)));
1589
+ }
1590
+ catch { }
1591
+ if (!str)
1592
+ return [];
1593
+ return str
1594
+ .split(",")
1595
+ .map((t) => t.trim())
1596
+ .filter((t) => t.length > 0)
1597
+ .map(map);
1598
+ };
1599
+ switch (typeId) {
1600
+ case "float": {
1601
+ const n = Number(raw);
1602
+ value = Number.isFinite(n) ? n : 0;
1603
+ break;
1604
+ }
1605
+ case "bool": {
1606
+ value = Boolean(raw);
1607
+ break;
1608
+ }
1609
+ case "string": {
1610
+ value = String(raw);
1611
+ break;
1612
+ }
1613
+ case "float[]": {
1614
+ value = parseArray(String(raw), (x) => Number(x));
1615
+ break;
1616
+ }
1617
+ case "bool[]": {
1618
+ value = parseArray(String(raw), (x) => /^(true|1)$/i.test(x));
1619
+ break;
1620
+ }
1621
+ case "vec3": {
1622
+ const arr = parseArray(String(raw), (x) => Number(x));
1623
+ value = [arr[0] ?? 0, arr[1] ?? 0, arr[2] ?? 0];
1624
+ break;
1625
+ }
1626
+ case "vec3[]": {
1627
+ try {
1628
+ const parsed = JSON.parse(String(raw));
1629
+ if (Array.isArray(parsed)) {
1630
+ value = parsed.map((v) => [
1631
+ Number(v?.[0] ?? 0),
1632
+ Number(v?.[1] ?? 0),
1633
+ Number(v?.[2] ?? 0),
1634
+ ]);
1635
+ break;
1636
+ }
1637
+ }
1638
+ catch { }
1639
+ // fallback CSV triples: "x1,y1,z1; x2,y2,z2"
1640
+ value = String(raw)
1641
+ .split(";")
1642
+ .map((seg) => seg.trim())
1643
+ .filter(Boolean)
1644
+ .map((seg) => seg.split(",").map((n) => Number(n.trim())))
1645
+ .map((a) => [a[0] ?? 0, a[1] ?? 0, a[2] ?? 0]);
1646
+ break;
1647
+ }
1648
+ default: {
1649
+ // fallback to string
1650
+ value = raw;
1651
+ }
1652
+ }
1653
+ runner.setInput(selectedNodeId, handle, value);
1654
+ }, [selectedNodeId, def.edges, selectedDesc, runner]);
1655
+ const toDisplay = React.useCallback((typeId, value) => {
1656
+ if (value === undefined || value === null)
1657
+ return "";
1658
+ if (typeId && typeId.startsWith("enum:")) {
1659
+ const n = Number(value);
1660
+ const label = registry.getEnumLabel(typeId, n);
1661
+ return label ?? String(n);
1662
+ }
1663
+ const round4 = (n) => Math.round(Number(n) * 10000) / 10000;
1664
+ if (typeId === "vec3" && Array.isArray(value)) {
1665
+ const a = value;
1666
+ return [round4(a[0] ?? 0), round4(a[1] ?? 0), round4(a[2] ?? 0)].join(",");
1667
+ }
1668
+ const stringifyRounded = (v) => {
1669
+ try {
1670
+ return JSON.stringify(v, (_k, val) => typeof val === "number" ? round4(val) : val);
1671
+ }
1672
+ catch {
1673
+ return String(v);
1674
+ }
1675
+ };
1676
+ if (typeId?.endsWith("[]") ||
1677
+ Array.isArray(value) ||
1678
+ (typeof value === "object" && value !== null)) {
1679
+ return stringifyRounded(value);
1680
+ }
1681
+ if (typeof value === "number") {
1682
+ const rounded = Math.round(Number(value) * 10000) / 10000;
1683
+ return String(rounded);
1684
+ }
1685
+ return String(value);
1686
+ }, [registry]);
1687
+ return (jsxRuntime.jsxs("div", { className: "w-full h-screen flex flex-col", children: [jsxRuntime.jsxs("div", { className: "p-2 border-b border-gray-300 flex gap-2 items-center", children: [runner.isRunning() ? (jsxRuntime.jsxs("span", { className: "ml-2 text-sm text-green-700", children: ["Running: ", runner.getRunningEngine()] })) : (jsxRuntime.jsx("span", { className: "ml-2 text-sm text-gray-500", children: "Stopped" })), jsxRuntime.jsx("label", { className: "ml-2 text-sm", children: "Example:" }), jsxRuntime.jsxs("select", { className: "border border-gray-300 rounded px-2 py-1", value: exampleState, onChange: (e) => applyExample(e.target.value), disabled: runner.isRunning(), title: runner.isRunning()
1688
+ ? "Stop engine before switching example"
1689
+ : undefined, children: [jsxRuntime.jsx("option", { value: "simple", children: "Simple" }), jsxRuntime.jsx("option", { value: "async", children: "Async Chain" }), jsxRuntime.jsx("option", { value: "progress", children: "Progress + Errors" }), jsxRuntime.jsx("option", { value: "validation", children: "Validation" })] }), jsxRuntime.jsx("label", { className: "ml-2 text-sm", children: "Backend:" }), jsxRuntime.jsxs("select", { className: "border border-gray-300 rounded px-2 py-1", value: backendKind, onChange: (e) => onBackendKindChange(e.target.value), disabled: runner.isRunning(), title: runner.isRunning()
1690
+ ? "Stop engine before switching backend"
1691
+ : undefined, children: [jsxRuntime.jsx("option", { value: "local", children: "Local" }), jsxRuntime.jsx("option", { value: "remote-http", children: "Remote (HTTP)" }), jsxRuntime.jsx("option", { value: "remote-ws", children: "Remote (WebSocket)" })] }), backendKind === "remote-http" && (jsxRuntime.jsx("input", { className: "ml-2 border border-gray-300 rounded px-2 py-1 w-72", placeholder: "http://127.0.0.1:18080", value: httpBaseUrl, onChange: (e) => onHttpBaseUrlChange(e.target.value) })), backendKind === "remote-ws" && (jsxRuntime.jsx("input", { className: "ml-2 border border-gray-300 rounded px-2 py-1 w-72", placeholder: "ws://127.0.0.1:18081", value: wsUrl, onChange: (e) => onWsUrlChange(e.target.value) })), jsxRuntime.jsxs("select", { className: "border border-gray-300 rounded px-2 py-1", value: runner.getRunningEngine() ?? engine ?? "", onChange: (e) => {
1692
+ const kind = e.target.value || undefined;
1693
+ onEngineChange?.(kind);
1694
+ }, children: [jsxRuntime.jsx("option", { value: "", children: "Select Engine\u2026" }), jsxRuntime.jsx("option", { value: "push", children: "Push" }), jsxRuntime.jsx("option", { value: "batched", children: "Batched" }), jsxRuntime.jsx("option", { value: "pull", children: "Pull" }), jsxRuntime.jsx("option", { value: "hybrid", children: "Hybrid" }), jsxRuntime.jsx("option", { value: "step", children: "Step" })] }), runner.getRunningEngine() === "step" && (jsxRuntime.jsx("button", { className: "ml-2", onClick: () => runner.step(), disabled: !runner.isRunning(), children: "Step" })), runner.getRunningEngine() === "batched" && (jsxRuntime.jsx("button", { className: "ml-2", onClick: () => runner.flush(), disabled: !runner.isRunning(), children: "Flush" })), runner.isRunning() ? (jsxRuntime.jsx("button", { onClick: () => runner.dispose(), disabled: !runner.isRunning(), children: "Stop" })) : (jsxRuntime.jsx("button", { onClick: () => {
1695
+ const kind = engine;
1696
+ if (!kind)
1697
+ return alert("Select an engine first.");
1698
+ try {
1699
+ runner.launch(wb.export(), { engine: kind });
1700
+ }
1701
+ catch (err) {
1702
+ alert(String(err?.message ?? err));
1703
+ }
1704
+ }, disabled: !engine, children: "Start" })), jsxRuntime.jsx("button", { onClick: runAutoLayout, children: "Auto Layout" }), jsxRuntime.jsxs("label", { className: "ml-2 flex items-center gap-1", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: debug, onChange: (e) => onDebugChange(e.target.checked) }), jsxRuntime.jsx("span", { children: "Debug events" })] }), jsxRuntime.jsxs("label", { className: "ml-2 flex items-center gap-1", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: showValues, onChange: (e) => onShowValuesChange(e.target.checked) }), jsxRuntime.jsx("span", { children: "Show values in nodes" })] })] }), jsxRuntime.jsxs("div", { className: "flex flex-1 min-h-0", children: [jsxRuntime.jsx("div", { className: "flex-1 min-w-0", children: jsxRuntime.jsx(WorkbenchCanvas, { showValues: showValues, toDisplay: toDisplay }, exampleState) }), jsxRuntime.jsx(Inspector, { setInput: setInput, debug: debug, autoScroll: autoScroll, hideWorkbench: hideWorkbench, onAutoScrollChange: onAutoScrollChange, onHideWorkbenchChange: onHideWorkbenchChange, toDisplay: toDisplay })] })] }));
1705
+ }
1706
+ function WorkbenchStudio({ engine, onEngineChange, example, onExampleChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, autoScroll, onAutoScrollChange, }) {
1707
+ const [registry, setRegistry] = React.useState(sparkGraph.createSimpleGraphRegistry());
1708
+ const [wb] = React.useState(() => new InMemoryWorkbench({ ui: new DefaultUIExtensionRegistry() }));
1709
+ const runner = React.useMemo(() => {
1710
+ const backend = backendKind === "remote-http"
1711
+ ? { kind: "remote-http", baseUrl: httpBaseUrl }
1712
+ : backendKind === "remote-ws"
1713
+ ? { kind: "remote-ws", url: wsUrl }
1714
+ : { kind: "local" };
1715
+ return new GraphRunner(registry, backend);
1716
+ }, [registry, backendKind, httpBaseUrl, wsUrl]);
1717
+ return (jsxRuntime.jsx(WorkbenchProvider, { wb: wb, runner: runner, registry: registry, setRegistry: setRegistry, children: jsxRuntime.jsx(WorkbenchStudioCanvas, { setRegistry: setRegistry, autoScroll: autoScroll, onAutoScrollChange: onAutoScrollChange, example: example, onExampleChange: onExampleChange, engine: engine, onEngineChange: onEngineChange, backendKind: backendKind, onBackendKindChange: (v) => {
1718
+ if (runner.isRunning())
1719
+ runner.dispose();
1720
+ onBackendKindChange(v);
1721
+ }, httpBaseUrl: httpBaseUrl, onHttpBaseUrlChange: onHttpBaseUrlChange, wsUrl: wsUrl, onWsUrlChange: onWsUrlChange, debug: debug, onDebugChange: onDebugChange, showValues: showValues, onShowValuesChange: onShowValuesChange, hideWorkbench: hideWorkbench, onHideWorkbenchChange: onHideWorkbenchChange }) }));
1722
+ }
1723
+
1724
+ function App() {
1725
+ const [engine, setEngine] = useQueryParamString("engine", "");
1726
+ const [example, setExample] = useQueryParamString("example", "simple");
1727
+ const [debug, setDebug] = useQueryParamBoolean("debug", false);
1728
+ const [showValues, setShowValues] = useQueryParamBoolean("values", false);
1729
+ const [hideWorkbench, setHideWorkbench] = useQueryParamBoolean("hideWb", false);
1730
+ const [autoScroll, setAutoScroll] = useQueryParamBoolean("autoScroll", true);
1731
+ // Backend selection via URL params
1732
+ const [backendKind, setBackendKind] = useQueryParamString("backend", "local");
1733
+ const [httpBaseUrl, setHttpBaseUrl] = useQueryParamString("sparkHttp", "http://127.0.0.1:18080");
1734
+ const [wsUrl, setWsUrl] = useQueryParamString("sparkWs", "ws://127.0.0.1:18081");
1735
+ React.useEffect(() => {
1736
+ document.getElementById("loading-screen")?.remove();
1737
+ }, []);
1738
+ return (jsxRuntime.jsx(WorkbenchStudio, { engine: engine, onEngineChange: setEngine, example: example, onExampleChange: setExample, backendKind: (backendKind || "local"), onBackendKindChange: (v) => setBackendKind(v), httpBaseUrl: httpBaseUrl || "http://127.0.0.1:18080", onHttpBaseUrlChange: setHttpBaseUrl, wsUrl: wsUrl || "ws://127.0.0.1:18081", onWsUrlChange: setWsUrl, debug: debug, onDebugChange: setDebug, showValues: showValues, onShowValuesChange: setShowValues, hideWorkbench: hideWorkbench, onHideWorkbenchChange: setHideWorkbench, autoScroll: autoScroll, onAutoScrollChange: setAutoScroll }));
1739
+ }
1740
+
1741
+ exports.AbstractWorkbench = AbstractWorkbench;
1742
+ exports.App = App;
1743
+ exports.CLIWorkbench = CLIWorkbench;
1744
+ exports.DefaultUIExtensionRegistry = DefaultUIExtensionRegistry;
1745
+ exports.InMemoryWorkbench = InMemoryWorkbench;
1746
+ exports.ReactFlowWorkbench = ReactFlowWorkbench;
1747
+ exports.toReactFlow = toReactFlow$1;
1748
+ //# sourceMappingURL=index.cjs.map