@bian-womp/spark-workbench 0.2.10 → 0.2.11

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 (29) hide show
  1. package/lib/cjs/index.cjs +189 -42
  2. package/lib/cjs/index.cjs.map +1 -1
  3. package/lib/cjs/src/misc/NodeContextMenu.d.ts +1 -1
  4. package/lib/cjs/src/misc/NodeContextMenu.d.ts.map +1 -1
  5. package/lib/cjs/src/misc/WorkbenchCanvas.d.ts.map +1 -1
  6. package/lib/cjs/src/misc/WorkbenchStudio.d.ts.map +1 -1
  7. package/lib/cjs/src/runtime/AbstractGraphRunner.d.ts +7 -0
  8. package/lib/cjs/src/runtime/AbstractGraphRunner.d.ts.map +1 -1
  9. package/lib/cjs/src/runtime/IGraphRunner.d.ts +7 -0
  10. package/lib/cjs/src/runtime/IGraphRunner.d.ts.map +1 -1
  11. package/lib/cjs/src/runtime/LocalGraphRunner.d.ts +12 -0
  12. package/lib/cjs/src/runtime/LocalGraphRunner.d.ts.map +1 -1
  13. package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts +7 -0
  14. package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
  15. package/lib/esm/index.js +189 -42
  16. package/lib/esm/index.js.map +1 -1
  17. package/lib/esm/src/misc/NodeContextMenu.d.ts +1 -1
  18. package/lib/esm/src/misc/NodeContextMenu.d.ts.map +1 -1
  19. package/lib/esm/src/misc/WorkbenchCanvas.d.ts.map +1 -1
  20. package/lib/esm/src/misc/WorkbenchStudio.d.ts.map +1 -1
  21. package/lib/esm/src/runtime/AbstractGraphRunner.d.ts +7 -0
  22. package/lib/esm/src/runtime/AbstractGraphRunner.d.ts.map +1 -1
  23. package/lib/esm/src/runtime/IGraphRunner.d.ts +7 -0
  24. package/lib/esm/src/runtime/IGraphRunner.d.ts.map +1 -1
  25. package/lib/esm/src/runtime/LocalGraphRunner.d.ts +12 -0
  26. package/lib/esm/src/runtime/LocalGraphRunner.d.ts.map +1 -1
  27. package/lib/esm/src/runtime/RemoteGraphRunner.d.ts +7 -0
  28. package/lib/esm/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
  29. package/package.json +4 -4
package/lib/cjs/index.cjs CHANGED
@@ -411,6 +411,21 @@ class AbstractGraphRunner {
411
411
  class LocalGraphRunner extends AbstractGraphRunner {
412
412
  constructor(registry) {
413
413
  super(registry, { kind: "local" });
414
+ this.setEnvironment = (env, opts) => {
415
+ if (!this.runtime)
416
+ return;
417
+ if (opts?.merge) {
418
+ const current = this.runtime.getEnvironment();
419
+ const next = { ...(current || {}), ...(env || {}) };
420
+ this.runtime.setEnvironment(next);
421
+ }
422
+ else {
423
+ this.runtime.setEnvironment(env);
424
+ }
425
+ };
426
+ this.getEnvironment = () => {
427
+ return this.runtime?.getEnvironment?.();
428
+ };
414
429
  this.emit("transport", { state: "local" });
415
430
  }
416
431
  build(def) {
@@ -509,17 +524,36 @@ class LocalGraphRunner extends AbstractGraphRunner {
509
524
  const runtimeInputs = this.runtime
510
525
  ? this.runtime.getNodeData?.(n.nodeId)?.inputs ?? {}
511
526
  : {};
512
- if (this.isRunning()) {
513
- out[n.nodeId] = runtimeInputs;
514
- }
515
- else {
516
- const merged = { ...runtimeInputs, ...staged };
517
- if (Object.keys(merged).length > 0)
518
- out[n.nodeId] = merged;
519
- }
527
+ const merged = { ...runtimeInputs, ...staged };
528
+ if (Object.keys(merged).length > 0)
529
+ out[n.nodeId] = merged;
520
530
  }
521
531
  return out;
522
532
  }
533
+ async snapshotFull() {
534
+ const def = undefined; // UI will supply def/positions on download for local
535
+ const inputs = this.getInputs(this.runtime
536
+ ? {
537
+ nodes: Array.from(this.runtime.getNodeIds()).map((id) => ({ nodeId: id, typeId: "" })),
538
+ edges: [],
539
+ }
540
+ : { nodes: [], edges: [] });
541
+ const outputs = this.getOutputs(this.runtime
542
+ ? {
543
+ nodes: Array.from(this.runtime.getNodeIds()).map((id) => ({ nodeId: id, typeId: "" })),
544
+ edges: [],
545
+ }
546
+ : { nodes: [], edges: [] });
547
+ const environment = this.getEnvironment() || {};
548
+ return { def, environment, inputs, outputs };
549
+ }
550
+ async applySnapshotFull(payload) {
551
+ if (payload.def)
552
+ this.build(payload.def);
553
+ this.setEnvironment?.(payload.environment || {}, { merge: false });
554
+ // Hydrate via runtime for exact restore and re-emit
555
+ this.runtime?.hydrate({ inputs: payload.inputs || {}, outputs: payload.outputs || {} }, { reemit: true });
556
+ }
523
557
  dispose() {
524
558
  super.dispose();
525
559
  this.runtime = undefined;
@@ -691,6 +725,36 @@ class RemoteGraphRunner extends AbstractGraphRunner {
691
725
  return value;
692
726
  }
693
727
  }
728
+ async snapshotFull() {
729
+ const runner = await this.ensureRemoteRunner();
730
+ try {
731
+ return await runner.snapshotFull();
732
+ }
733
+ catch {
734
+ return { def: undefined, environment: {}, inputs: {}, outputs: {} };
735
+ }
736
+ }
737
+ async applySnapshotFull(payload) {
738
+ const runner = await this.ensureRemoteRunner();
739
+ await runner.applySnapshotFull(payload);
740
+ }
741
+ setEnvironment(env, opts) {
742
+ const t = this.transport;
743
+ if (!t)
744
+ return;
745
+ t.request({
746
+ message: {
747
+ type: "SetEnvironment",
748
+ payload: { environment: env, merge: opts?.merge },
749
+ },
750
+ }).catch(() => { });
751
+ }
752
+ getEnvironment() {
753
+ // Fetch from remote via lightweight command
754
+ // Note: returns undefined synchronously; callers needing value should use snapshotFull or call runner directly
755
+ // For now, we expose an async helper on RemoteRunner. Keep sync signature per interface.
756
+ return undefined;
757
+ }
694
758
  getOutputs(def) {
695
759
  const out = {};
696
760
  const cache = this.valueCache;
@@ -724,7 +788,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
724
788
  if (rec && rec.io === "input")
725
789
  cur[h] = rec.value;
726
790
  }
727
- const merged = this.isRunning() ? cur : { ...cur, ...staged };
791
+ const merged = { ...cur, ...staged };
728
792
  if (Object.keys(merged).length > 0)
729
793
  out[n.nodeId] = merged;
730
794
  }
@@ -2240,14 +2304,6 @@ function NodeContextMenu({ open, clientPos, nodeId, onClose, }) {
2240
2304
  if (open)
2241
2305
  ref.current?.focus();
2242
2306
  }, [open]);
2243
- if (!open || !clientPos || !nodeId)
2244
- return null;
2245
- // clamp
2246
- const MENU_MIN_WIDTH = 180;
2247
- const PADDING = 16;
2248
- const x = Math.min(clientPos.x, (typeof window !== "undefined" ? window.innerWidth : 0) -
2249
- (MENU_MIN_WIDTH + PADDING));
2250
- const y = Math.min(clientPos.y, (typeof window !== "undefined" ? window.innerHeight : 0) - 240);
2251
2307
  // Bake helpers
2252
2308
  const getBakeableOutputs = () => {
2253
2309
  try {
@@ -2314,43 +2370,40 @@ function NodeContextMenu({ open, clientPos, nodeId, onClose, }) {
2314
2370
  const arrTarget = isArray ? tArr?.bakeTarget : undefined;
2315
2371
  const elemTarget = isArray ? tElem?.bakeTarget : undefined;
2316
2372
  if (singleTarget) {
2317
- const nodeDesc = registry.nodes.get(String(singleTarget.nodeTypeId));
2318
- const inType = sparkGraph.getInputTypeId(nodeDesc?.inputs, String(singleTarget.inputHandle || "Value"));
2373
+ const nodeDesc = registry.nodes.get(singleTarget.nodeTypeId);
2374
+ const inType = sparkGraph.getInputTypeId(nodeDesc?.inputs, singleTarget.inputHandle);
2319
2375
  const coerced = await coerceIfNeeded(typeId, inType, unwrap(raw));
2320
2376
  const newId = wb.addNode({
2321
- typeId: String(singleTarget.nodeTypeId),
2377
+ typeId: singleTarget.nodeTypeId,
2322
2378
  position: { x: pos.x + 180, y: pos.y },
2323
2379
  params: {},
2324
2380
  });
2325
2381
  runner.update(wb.export());
2326
2382
  await runner.whenIdle();
2327
- runner.setInputs(newId, {
2328
- [String(singleTarget.inputHandle || "Value")]: coerced,
2329
- });
2383
+ runner.setInputs(newId, { [singleTarget.inputHandle]: coerced });
2330
2384
  return;
2331
2385
  }
2332
2386
  if (isArray && arrTarget) {
2333
- const nodeDesc = registry.nodes.get(String(arrTarget.nodeTypeId));
2334
- const inType = sparkGraph.getInputTypeId(nodeDesc?.inputs, String(arrTarget.inputHandle || "Value"));
2387
+ const nodeDesc = registry.nodes.get(arrTarget.nodeTypeId);
2388
+ const inType = sparkGraph.getInputTypeId(nodeDesc?.inputs, arrTarget.inputHandle);
2335
2389
  const coerced = await coerceIfNeeded(typeId, inType, unwrap(raw));
2336
2390
  const newId = `n${Math.random().toString(36).slice(2, 8)}`;
2337
2391
  wb.addNode({
2338
2392
  nodeId: newId,
2339
- typeId: String(arrTarget.nodeTypeId),
2393
+ typeId: arrTarget.nodeTypeId,
2340
2394
  position: { x: pos.x + 180, y: pos.y },
2341
2395
  params: {},
2342
2396
  });
2343
2397
  runner.update(wb.export());
2344
2398
  await runner.whenIdle();
2345
- runner.setInputs(newId, {
2346
- [String(arrTarget.inputHandle || "Value")]: coerced,
2347
- });
2399
+ runner.setInputs(newId, { [arrTarget.inputHandle]: coerced });
2348
2400
  return;
2349
2401
  }
2350
- if (isArray && elemTarget && Array.isArray(raw)) {
2351
- const nodeDesc = registry.nodes.get(String(elemTarget.nodeTypeId));
2352
- const inType = sparkGraph.getInputTypeId(nodeDesc?.inputs, String(elemTarget.inputHandle || "Value"));
2353
- const items = raw.map(unwrap);
2402
+ if (isArray && elemTarget) {
2403
+ const nodeDesc = registry.nodes.get(elemTarget.nodeTypeId);
2404
+ const inType = sparkGraph.getInputTypeId(nodeDesc?.inputs, elemTarget.inputHandle);
2405
+ const src = unwrap(raw);
2406
+ const items = Array.isArray(src) ? src : [src];
2354
2407
  const coercedItems = await Promise.all(items.map((v) => coerceIfNeeded(baseTypeId, inType, v)));
2355
2408
  const COLS = 4;
2356
2409
  const DX = 180;
@@ -2360,18 +2413,14 @@ function NodeContextMenu({ open, clientPos, nodeId, onClose, }) {
2360
2413
  const col = idx % COLS;
2361
2414
  const row = Math.floor(idx / COLS);
2362
2415
  const newId = wb.addNode({
2363
- typeId: String(elemTarget.nodeTypeId),
2416
+ typeId: elemTarget.nodeTypeId,
2364
2417
  position: { x: pos.x + (col + 1) * DX, y: pos.y + row * DY },
2365
2418
  params: {},
2366
- initialInputs: {
2367
- [String(elemTarget.inputHandle || "Value")]: clone(cv),
2368
- },
2419
+ initialInputs: { [elemTarget.inputHandle]: clone(cv) },
2369
2420
  });
2370
2421
  runner.update(wb.export());
2371
2422
  await runner.whenIdle();
2372
- runner.setInputs(newId, {
2373
- [String(elemTarget.inputHandle || "Value")]: cv,
2374
- });
2423
+ runner.setInputs(newId, { [elemTarget.inputHandle]: cv });
2375
2424
  }
2376
2425
  return;
2377
2426
  }
@@ -2414,6 +2463,14 @@ function NodeContextMenu({ open, clientPos, nodeId, onClose, }) {
2414
2463
  catch { }
2415
2464
  onClose();
2416
2465
  }, [nodeId, runner, onClose]);
2466
+ if (!open || !clientPos || !nodeId)
2467
+ return null;
2468
+ // clamp
2469
+ const MENU_MIN_WIDTH = 180;
2470
+ const PADDING = 16;
2471
+ const x = Math.min(clientPos.x, (typeof window !== "undefined" ? window.innerWidth : 0) -
2472
+ (MENU_MIN_WIDTH + PADDING));
2473
+ const y = Math.min(clientPos.y, (typeof window !== "undefined" ? window.innerHeight : 0) - 240);
2417
2474
  const canRunPull = engineKind()?.toString() === "pull";
2418
2475
  const outs = getBakeableOutputs();
2419
2476
  return (jsxRuntime.jsxs("div", { ref: ref, tabIndex: -1, 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: x, top: y }, onClick: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), onWheel: (e) => e.stopPropagation(), onContextMenu: (e) => {
@@ -2463,6 +2520,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
2463
2520
  outputValues: n.data.outputValues,
2464
2521
  status: n.data.status,
2465
2522
  validation: n.data.validation,
2523
+ inputConnected: n.data.inputConnected,
2466
2524
  },
2467
2525
  });
2468
2526
  return isEqual(pick(a), pick(b));
@@ -2666,7 +2724,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
2666
2724
  }, []);
2667
2725
  return (jsxRuntime.jsx("div", { className: "w-full h-full", onContextMenu: onContextMenu, children: jsxRuntime.jsx(react.ReactFlowProvider, { children: jsxRuntime.jsxs(react.ReactFlow, { nodes: throttled.nodes, edges: throttled.edges, nodeTypes: nodeTypes, selectionOnDrag: true, onInit: (inst) => {
2668
2726
  rfInstanceRef.current = inst;
2669
- }, onConnect: onConnect, onEdgesChange: onEdgesChange, onEdgesDelete: onEdgesDelete, onNodesDelete: onNodesDelete, onNodesChange: onNodesChange, deleteKeyCode: ["Backspace", "Delete"], proOptions: { hideAttribution: true }, noDragClassName: "wb-nodrag", noWheelClassName: "wb-nowheel", noPanClassName: "wb-nopan", fitView: true, children: [jsxRuntime.jsx(react.Background, { id: "workbench-canvas-background", variant: react.BackgroundVariant.Dots, gap: 12, size: 1 }), jsxRuntime.jsx(react.MiniMap, {}), jsxRuntime.jsx(react.Controls, {}), jsxRuntime.jsx(DefaultContextMenu, { open: menuOpen, clientPos: menuPos, onAdd: addNodeAt, onClose: onCloseMenu }), jsxRuntime.jsx(NodeContextMenu, { open: nodeMenuOpen, clientPos: nodeMenuPos, nodeId: nodeAtMenu, onClose: onCloseNodeMenu })] }) }) }));
2727
+ }, onConnect: onConnect, onEdgesChange: onEdgesChange, onEdgesDelete: onEdgesDelete, onNodesDelete: onNodesDelete, onNodesChange: onNodesChange, deleteKeyCode: ["Backspace", "Delete"], proOptions: { hideAttribution: true }, noDragClassName: "wb-nodrag", noWheelClassName: "wb-nowheel", noPanClassName: "wb-nopan", fitView: true, children: [jsxRuntime.jsx(react.Background, { id: "workbench-canvas-background", variant: react.BackgroundVariant.Dots, gap: 12, size: 1 }), jsxRuntime.jsx(react.MiniMap, {}), jsxRuntime.jsx(react.Controls, {}), jsxRuntime.jsx(DefaultContextMenu, { open: menuOpen, clientPos: menuPos, onAdd: addNodeAt, onClose: onCloseMenu }), !!nodeAtMenu && (jsxRuntime.jsx(NodeContextMenu, { open: nodeMenuOpen, clientPos: nodeMenuPos, nodeId: nodeAtMenu, onClose: onCloseNodeMenu }))] }) }) }));
2670
2728
  });
2671
2729
 
2672
2730
  function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, example, onExampleChange, engine, onEngineChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, overrides, onInit, onChange, }) {
@@ -2721,6 +2779,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
2721
2779
  const lastAutoLaunched = React.useRef(undefined);
2722
2780
  const autoLayoutRan = React.useRef(false);
2723
2781
  const canvasRef = React.useRef(null);
2782
+ const uploadInputRef = React.useRef(null);
2724
2783
  // Expose init callback with setInitialGraph helper
2725
2784
  const initCalled = React.useRef(false);
2726
2785
  React.useEffect(() => {
@@ -2825,6 +2884,66 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
2825
2884
  alert(String(err?.message ?? err));
2826
2885
  }
2827
2886
  }, [wb, runner]);
2887
+ const onUploadPicked = React.useCallback(async (e) => {
2888
+ try {
2889
+ const file = e.target.files?.[0];
2890
+ if (!file)
2891
+ return;
2892
+ const text = await file.text();
2893
+ const parsed = JSON.parse(text);
2894
+ // Support both Graph and Snapshot payloads
2895
+ const isSnapshot = parsed &&
2896
+ typeof parsed === "object" &&
2897
+ (parsed.def || parsed.inputs || parsed.outputs || parsed.environment);
2898
+ if (isSnapshot) {
2899
+ const def = parsed.def;
2900
+ const positions = parsed.positions || {};
2901
+ const environment = parsed.environment || {};
2902
+ const inputs = parsed.inputs || {};
2903
+ if (def) {
2904
+ // Remote exact restore path
2905
+ await runner.applySnapshotFull({
2906
+ def,
2907
+ environment,
2908
+ inputs,
2909
+ outputs: parsed.outputs || {},
2910
+ });
2911
+ await wb.load(def);
2912
+ if (positions && typeof positions === "object")
2913
+ wb.setPositions(positions);
2914
+ }
2915
+ }
2916
+ else {
2917
+ const def = parsed?.def ?? parsed;
2918
+ const inputs = parsed?.inputs ?? {};
2919
+ await wb.load(def);
2920
+ try {
2921
+ runner.build(wb.export());
2922
+ }
2923
+ catch { }
2924
+ if (inputs && typeof inputs === "object") {
2925
+ for (const [nodeId, map] of Object.entries(inputs)) {
2926
+ try {
2927
+ runner.setInputs(nodeId, map);
2928
+ }
2929
+ catch { }
2930
+ }
2931
+ }
2932
+ }
2933
+ runAutoLayout();
2934
+ }
2935
+ catch (err) {
2936
+ alert(String(err?.message ?? err));
2937
+ }
2938
+ finally {
2939
+ // reset input so same file can be picked again
2940
+ if (uploadInputRef.current)
2941
+ uploadInputRef.current.value = "";
2942
+ }
2943
+ }, [wb, runner, runAutoLayout]);
2944
+ const triggerUpload = React.useCallback(() => {
2945
+ uploadInputRef.current?.click();
2946
+ }, []);
2828
2947
  const hydrateFromBackend = React.useCallback(async (kind, base) => {
2829
2948
  try {
2830
2949
  const transport = kind === "remote-http"
@@ -3122,7 +3241,35 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
3122
3241
  catch (err) {
3123
3242
  alert(String(err?.message ?? err));
3124
3243
  }
3125
- }, disabled: !engine, children: "Start" })), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded px-2 py-1.5", onClick: runAutoLayout, children: "Auto Layout" }), jsxRuntime.jsx("button", { className: "ml-2 border border-gray-300 rounded px-2 py-1.5", onClick: () => canvasRef.current?.fitView?.(), title: "Fit View", children: "Fit View" }), jsxRuntime.jsx("button", { className: "ml-2 border border-gray-300 rounded px-2 py-1.5", onClick: downloadGraph, children: "Download Graph" }), 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, { ref: canvasRef, showValues: showValues, toString: toString, toElement: toElement, getDefaultNodeSize: overrides?.getDefaultNodeSize }) }), jsxRuntime.jsx(Inspector, { setInput: setInput, debug: debug, autoScroll: autoScroll, hideWorkbench: hideWorkbench, onAutoScrollChange: onAutoScrollChange, onHideWorkbenchChange: onHideWorkbenchChange, toString: toString, toElement: toElement, contextPanel: overrides?.contextPanel })] })] }));
3244
+ }, disabled: !engine, children: "Start" })), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded px-2 py-1.5", onClick: runAutoLayout, children: "Auto Layout" }), jsxRuntime.jsx("button", { className: "ml-2 border border-gray-300 rounded px-2 py-1.5", onClick: () => canvasRef.current?.fitView?.(), title: "Fit View", children: "Fit View" }), jsxRuntime.jsx("button", { className: "ml-2 border border-gray-300 rounded px-2 py-1.5", onClick: downloadGraph, children: "Download Graph" }), jsxRuntime.jsx("button", { className: "ml-2 border border-gray-300 rounded px-2 py-1.5", onClick: async () => {
3245
+ try {
3246
+ const def = wb.export();
3247
+ const positions = wb.getPositions();
3248
+ const snapshot = await runner.snapshotFull();
3249
+ const payload = {
3250
+ ...snapshot,
3251
+ def,
3252
+ positions,
3253
+ schemaVersion: 1,
3254
+ };
3255
+ const pretty = JSON.stringify(payload, null, 2);
3256
+ const blob = new Blob([pretty], { type: "application/json" });
3257
+ const url = URL.createObjectURL(blob);
3258
+ const a = document.createElement("a");
3259
+ const d = new Date();
3260
+ const pad = (n) => String(n).padStart(2, "0");
3261
+ const ts = `${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}`;
3262
+ a.href = url;
3263
+ a.download = `spark-snapshot-${ts}.json`;
3264
+ document.body.appendChild(a);
3265
+ a.click();
3266
+ a.remove();
3267
+ URL.revokeObjectURL(url);
3268
+ }
3269
+ catch (err) {
3270
+ alert(String(err?.message ?? err));
3271
+ }
3272
+ }, children: "Download Snapshot" }), jsxRuntime.jsx("input", { ref: uploadInputRef, type: "file", accept: "application/json,.json", className: "hidden", onChange: onUploadPicked }), jsxRuntime.jsx("button", { className: "ml-2 border border-gray-300 rounded px-2 py-1.5", onClick: triggerUpload, children: "Upload Graph/Snapshot" }), 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, { ref: canvasRef, showValues: showValues, toString: toString, toElement: toElement, getDefaultNodeSize: overrides?.getDefaultNodeSize }) }), jsxRuntime.jsx(Inspector, { setInput: setInput, debug: debug, autoScroll: autoScroll, hideWorkbench: hideWorkbench, onAutoScrollChange: onAutoScrollChange, onHideWorkbenchChange: onHideWorkbenchChange, toString: toString, toElement: toElement, contextPanel: overrides?.contextPanel })] })] }));
3126
3273
  }
3127
3274
  function WorkbenchStudio({ engine, onEngineChange, example, onExampleChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, autoScroll, onAutoScrollChange, overrides, onInit, onChange, }) {
3128
3275
  const [registry, setRegistry] = React.useState(sparkGraph.createSimpleGraphRegistry());