@bian-womp/spark-workbench 0.2.34 → 0.2.35

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 (37) hide show
  1. package/lib/cjs/index.cjs +214 -98
  2. package/lib/cjs/index.cjs.map +1 -1
  3. package/lib/cjs/src/misc/DefaultNode.d.ts.map +1 -1
  4. package/lib/cjs/src/misc/Inspector.d.ts.map +1 -1
  5. package/lib/cjs/src/misc/WorkbenchCanvas.d.ts.map +1 -1
  6. package/lib/cjs/src/misc/context/WorkbenchContext.d.ts +1 -0
  7. package/lib/cjs/src/misc/context/WorkbenchContext.d.ts.map +1 -1
  8. package/lib/cjs/src/misc/context/WorkbenchContext.provider.d.ts.map +1 -1
  9. package/lib/cjs/src/misc/mapping.d.ts +2 -0
  10. package/lib/cjs/src/misc/mapping.d.ts.map +1 -1
  11. package/lib/cjs/src/runtime/AbstractGraphRunner.d.ts +4 -2
  12. package/lib/cjs/src/runtime/AbstractGraphRunner.d.ts.map +1 -1
  13. package/lib/cjs/src/runtime/IGraphRunner.d.ts +3 -2
  14. package/lib/cjs/src/runtime/IGraphRunner.d.ts.map +1 -1
  15. package/lib/cjs/src/runtime/LocalGraphRunner.d.ts +3 -2
  16. package/lib/cjs/src/runtime/LocalGraphRunner.d.ts.map +1 -1
  17. package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts +3 -3
  18. package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
  19. package/lib/esm/index.js +214 -98
  20. package/lib/esm/index.js.map +1 -1
  21. package/lib/esm/src/misc/DefaultNode.d.ts.map +1 -1
  22. package/lib/esm/src/misc/Inspector.d.ts.map +1 -1
  23. package/lib/esm/src/misc/WorkbenchCanvas.d.ts.map +1 -1
  24. package/lib/esm/src/misc/context/WorkbenchContext.d.ts +1 -0
  25. package/lib/esm/src/misc/context/WorkbenchContext.d.ts.map +1 -1
  26. package/lib/esm/src/misc/context/WorkbenchContext.provider.d.ts.map +1 -1
  27. package/lib/esm/src/misc/mapping.d.ts +2 -0
  28. package/lib/esm/src/misc/mapping.d.ts.map +1 -1
  29. package/lib/esm/src/runtime/AbstractGraphRunner.d.ts +4 -2
  30. package/lib/esm/src/runtime/AbstractGraphRunner.d.ts.map +1 -1
  31. package/lib/esm/src/runtime/IGraphRunner.d.ts +3 -2
  32. package/lib/esm/src/runtime/IGraphRunner.d.ts.map +1 -1
  33. package/lib/esm/src/runtime/LocalGraphRunner.d.ts +3 -2
  34. package/lib/esm/src/runtime/LocalGraphRunner.d.ts.map +1 -1
  35. package/lib/esm/src/runtime/RemoteGraphRunner.d.ts +3 -3
  36. package/lib/esm/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
  37. package/package.json +4 -4
package/lib/cjs/index.cjs CHANGED
@@ -339,6 +339,7 @@ class AbstractGraphRunner {
339
339
  if (this.engine) {
340
340
  throw new Error("Engine already running. Stop the current engine first.");
341
341
  }
342
+ this.currentDef = def;
342
343
  }
343
344
  setInput(nodeId, handle, value) {
344
345
  if (!this.stagedInputs[nodeId])
@@ -395,6 +396,7 @@ class AbstractGraphRunner {
395
396
  this.engine = undefined;
396
397
  this.runtime?.dispose();
397
398
  this.runtime = undefined;
399
+ this.currentDef = undefined;
398
400
  if (this.runningKind) {
399
401
  this.runningKind = undefined;
400
402
  this.emit("status", { running: false, engine: undefined });
@@ -429,12 +431,14 @@ class LocalGraphRunner extends AbstractGraphRunner {
429
431
  this.emit("transport", { state: "local" });
430
432
  }
431
433
  build(def) {
434
+ this.currentDef = def;
432
435
  const builder = new sparkGraph.GraphBuilder(this.registry);
433
436
  this.runtime = builder.build(def);
434
437
  // Signal UI that freshly built graph should be considered invalidated
435
438
  this.emit("invalidate", { reason: "graph-built" });
436
439
  }
437
440
  update(def) {
441
+ this.currentDef = def;
438
442
  if (!this.runtime)
439
443
  return;
440
444
  // Prevent mid-run churn while wiring changes are applied
@@ -499,11 +503,11 @@ class LocalGraphRunner extends AbstractGraphRunner {
499
503
  if (eng instanceof sparkGraph.BatchedEngine)
500
504
  eng.flush();
501
505
  }
502
- getOutputs(def) {
506
+ getOutputs() {
503
507
  const out = {};
504
- if (!this.runtime)
508
+ if (!this.runtime || !this.currentDef)
505
509
  return out;
506
- for (const n of def.nodes) {
510
+ for (const n of this.currentDef.nodes) {
507
511
  const desc = this.registry.nodes.get(n.typeId);
508
512
  const handles = Object.keys(desc?.outputs ?? {});
509
513
  for (const h of handles) {
@@ -517,15 +521,17 @@ class LocalGraphRunner extends AbstractGraphRunner {
517
521
  }
518
522
  return out;
519
523
  }
520
- getInputs(def) {
524
+ getInputs() {
521
525
  const out = {};
522
- for (const n of def.nodes) {
526
+ if (!this.currentDef)
527
+ return out;
528
+ for (const n of this.currentDef.nodes) {
523
529
  const staged = this.stagedInputs[n.nodeId] ?? {};
524
530
  const runtimeInputs = this.runtime
525
531
  ? this.runtime.getNodeData?.(n.nodeId)?.inputs ?? {}
526
532
  : {};
527
533
  // Build inbound handle set for this node from current def
528
- const inbound = new Set(def.edges
534
+ const inbound = new Set(this.currentDef.edges
529
535
  .filter((e) => e.target.nodeId === n.nodeId)
530
536
  .map((e) => e.target.handle));
531
537
  // Merge staged only for non-inbound handles so UI reflects runtime values for wired inputs
@@ -539,26 +545,22 @@ class LocalGraphRunner extends AbstractGraphRunner {
539
545
  }
540
546
  return out;
541
547
  }
548
+ getInputDefaults() {
549
+ const out = {};
550
+ if (!this.currentDef)
551
+ return out;
552
+ for (const n of this.currentDef.nodes) {
553
+ const dynDefaults = n.resolvedHandles?.inputDefaults ?? {};
554
+ if (Object.keys(dynDefaults).length > 0) {
555
+ out[n.nodeId] = dynDefaults;
556
+ }
557
+ }
558
+ return out;
559
+ }
542
560
  async snapshotFull() {
543
561
  const def = undefined; // UI will supply def/positions on download for local
544
- const inputs = this.getInputs(this.runtime
545
- ? {
546
- nodes: Array.from(this.runtime.getNodeIds()).map((id) => ({
547
- nodeId: id,
548
- typeId: "",
549
- })),
550
- edges: [],
551
- }
552
- : { nodes: [], edges: [] });
553
- const outputs = this.getOutputs(this.runtime
554
- ? {
555
- nodes: Array.from(this.runtime.getNodeIds()).map((id) => ({
556
- nodeId: id,
557
- typeId: "",
558
- })),
559
- edges: [],
560
- }
561
- : { nodes: [], edges: [] });
562
+ const inputs = this.getInputs();
563
+ const outputs = this.getOutputs();
562
564
  const environment = this.getEnvironment() || {};
563
565
  return { def, environment, inputs, outputs };
564
566
  }
@@ -635,8 +637,8 @@ class RemoteGraphRunner extends AbstractGraphRunner {
635
637
  this.emit("registry", this.registry);
636
638
  // Trigger update so validation/UI refreshes using last known graph
637
639
  try {
638
- if (this.lastDef)
639
- this.update(this.lastDef);
640
+ if (this.currentDef)
641
+ this.update(this.currentDef);
640
642
  }
641
643
  catch {
642
644
  console.error("Failed to update graph definition after registry changed");
@@ -654,12 +656,12 @@ class RemoteGraphRunner extends AbstractGraphRunner {
654
656
  console.warn("Unsupported operation for remote runner");
655
657
  }
656
658
  update(def) {
659
+ this.currentDef = def;
657
660
  // Remote: forward update; ignore errors (fire-and-forget)
658
661
  this.ensureRemoteRunner().then(async (runner) => {
659
662
  try {
660
663
  await runner.update(def);
661
664
  this.emit("invalidate", { reason: "graph-updated" });
662
- this.lastDef = def;
663
665
  }
664
666
  catch { }
665
667
  });
@@ -671,7 +673,6 @@ class RemoteGraphRunner extends AbstractGraphRunner {
671
673
  await runner.build(def);
672
674
  // Signal UI after remote build as well
673
675
  this.emit("invalidate", { reason: "graph-built" });
674
- this.lastDef = def;
675
676
  // Hydrate current remote inputs/outputs (including defaults) into cache
676
677
  try {
677
678
  const snap = await runner.snapshot();
@@ -807,12 +808,12 @@ class RemoteGraphRunner extends AbstractGraphRunner {
807
808
  // For now, we expose an async helper on RemoteRunner. Keep sync signature per interface.
808
809
  return undefined;
809
810
  }
810
- getOutputs(def) {
811
+ getOutputs() {
811
812
  const out = {};
812
813
  const cache = this.valueCache;
813
- if (!cache)
814
+ if (!cache || !this.currentDef)
814
815
  return out;
815
- for (const n of def.nodes) {
816
+ for (const n of this.currentDef.nodes) {
816
817
  const resolved = n.resolvedHandles?.outputs;
817
818
  const desc = this.registry.nodes.get(n.typeId);
818
819
  const handles = Object.keys(resolved ?? desc?.outputs ?? {});
@@ -828,17 +829,19 @@ class RemoteGraphRunner extends AbstractGraphRunner {
828
829
  }
829
830
  return out;
830
831
  }
831
- getInputs(def) {
832
+ getInputs() {
832
833
  const out = {};
833
834
  const cache = this.valueCache;
834
- for (const n of def.nodes) {
835
+ if (!this.currentDef)
836
+ return out;
837
+ for (const n of this.currentDef.nodes) {
835
838
  const staged = this.stagedInputs[n.nodeId] ?? {};
836
839
  const resolved = n.resolvedHandles?.inputs;
837
840
  const desc = this.registry.nodes.get(n.typeId);
838
841
  const handles = Object.keys(resolved ?? desc?.inputs ?? {});
839
842
  const cur = {};
840
843
  // Build inbound handle set for this node to honor wiring precedence
841
- const inbound = new Set(def.edges
844
+ const inbound = new Set(this.currentDef.edges
842
845
  .filter((e) => e.target.nodeId === n.nodeId)
843
846
  .map((e) => e.target.handle));
844
847
  for (const h of handles) {
@@ -857,6 +860,18 @@ class RemoteGraphRunner extends AbstractGraphRunner {
857
860
  }
858
861
  return out;
859
862
  }
863
+ getInputDefaults() {
864
+ const out = {};
865
+ if (!this.currentDef)
866
+ return out;
867
+ for (const n of this.currentDef.nodes) {
868
+ const dynDefaults = n.resolvedHandles?.inputDefaults ?? {};
869
+ if (Object.keys(dynDefaults).length > 0) {
870
+ out[n.nodeId] = dynDefaults;
871
+ }
872
+ }
873
+ return out;
874
+ }
860
875
  dispose() {
861
876
  super.dispose();
862
877
  this.runner = undefined;
@@ -1608,6 +1623,7 @@ function toReactFlow(def, positions, registry, opts) {
1608
1623
  renderWidth: initialWidth,
1609
1624
  renderHeight: initialHeight,
1610
1625
  inputValues: opts.inputs?.[n.nodeId],
1626
+ inputDefaults: opts.inputDefaults?.[n.nodeId],
1611
1627
  outputValues: opts.outputs?.[n.nodeId],
1612
1628
  status: opts.nodeStatus?.[n.nodeId],
1613
1629
  validation: {
@@ -1773,8 +1789,9 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
1773
1789
  const valuesTick = versionTick + graphTick + graphUiTick;
1774
1790
  // Def and IO values
1775
1791
  const def = wb.export();
1776
- const inputsMap = React.useMemo(() => runner.getInputs(def), [runner, def, valuesTick]);
1777
- const outputsMap = React.useMemo(() => runner.getOutputs(def), [runner, def, valuesTick]);
1792
+ const inputsMap = React.useMemo(() => runner.getInputs(), [runner, valuesTick]);
1793
+ const inputDefaultsMap = React.useMemo(() => runner.getInputDefaults(), [runner, valuesTick]);
1794
+ const outputsMap = React.useMemo(() => runner.getOutputs(), [runner, valuesTick]);
1778
1795
  const outputTypesMap = React.useMemo(() => {
1779
1796
  const out = {};
1780
1797
  // Local: runtimeTypeId is not stored; derive from typed wrapper in outputsMap
@@ -1937,7 +1954,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
1937
1954
  }
1938
1955
  catch { }
1939
1956
  };
1940
- const off1 = runner.on("value", (e) => {
1957
+ const offRunnerValue = runner.on("value", (e) => {
1941
1958
  if (e?.io === "input") {
1942
1959
  const nodeId = e?.nodeId;
1943
1960
  setNodeStatus((s) => ({
@@ -1947,7 +1964,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
1947
1964
  }
1948
1965
  return add("runner", "value")(e);
1949
1966
  });
1950
- const off2 = runner.on("error", (e) => {
1967
+ const offRunnerError = runner.on("error", (e) => {
1951
1968
  const edgeError = e;
1952
1969
  const nodeError = e;
1953
1970
  const registryError = e;
@@ -2000,14 +2017,14 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
2000
2017
  }
2001
2018
  return add("runner", "error")(e);
2002
2019
  });
2003
- const off3 = runner.on("invalidate", (e) => {
2020
+ const offRunnerInvalidate = runner.on("invalidate", (e) => {
2004
2021
  // After build/update, pull resolved handles and merge in-place (no graphChanged)
2005
2022
  if (e?.reason === "graph-updated" || e?.reason === "graph-built") {
2006
2023
  refreshResolvedHandles();
2007
2024
  }
2008
2025
  return add("runner", "invalidate")(e);
2009
2026
  });
2010
- const off3b = runner.on("stats", (s) => {
2027
+ const offRunnerStats = runner.on("stats", (s) => {
2011
2028
  if (!s)
2012
2029
  return;
2013
2030
  if (s.kind === "node-start") {
@@ -2110,9 +2127,11 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
2110
2127
  }
2111
2128
  return add("runner", "stats")(s);
2112
2129
  });
2113
- const off4 = wb.on("graphChanged", add("workbench", "graphChanged"));
2130
+ const offWbGraphChanged = wb.on("graphChanged", add("workbench", "graphChanged"));
2131
+ const offWbGraphUiChanged = wb.on("graphUiChanged", add("workbench", "graphUiChanged"));
2132
+ const offWbValidationChanged = wb.on("validationChanged", add("workbench", "validationChanged"));
2114
2133
  // Ensure newly added nodes start as invalidated until first evaluation
2115
- const off4c = wb.on("graphChanged", (e) => {
2134
+ const offWbAddNode = wb.on("graphChanged", (e) => {
2116
2135
  const change = e.change;
2117
2136
  if (change?.type === "addNode" && typeof change.nodeId === "string") {
2118
2137
  const id = change.nodeId;
@@ -2122,17 +2141,14 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
2122
2141
  }));
2123
2142
  }
2124
2143
  });
2125
- const off4b = wb.on("graphUiChanged", add("workbench", "graphUiChanged"));
2126
- const off5 = wb.on("validationChanged", add("workbench", "validationChanged"));
2127
- const off5b = wb.on("validationChanged", (r) => setValidation(r));
2128
- const off6 = wb.on("selectionChanged", (sel) => {
2144
+ const offWbdSetValidation = wb.on("validationChanged", (r) => setValidation(r));
2145
+ const offWbSelectionChanged = wb.on("selectionChanged", (sel) => {
2129
2146
  setSelectedNodeId(sel.nodes?.[0]);
2130
2147
  setSelectedEdgeId(sel.edges?.[0]);
2131
2148
  });
2132
- const off7 = wb.on("error", add("workbench", "error"));
2133
- wb.refreshValidation();
2149
+ const offWbError = wb.on("error", add("workbench", "error"));
2134
2150
  // Registry updates: swap registry and refresh graph validation/UI
2135
- const offReg = runner.on("registry", (newReg) => {
2151
+ const offRunnerRegistry = runner.on("registry", (newReg) => {
2136
2152
  try {
2137
2153
  setRegistry(newReg);
2138
2154
  wb.setRegistry(newReg);
@@ -2148,29 +2164,40 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
2148
2164
  console.error("Failed to handle registry changed event");
2149
2165
  }
2150
2166
  });
2167
+ // Handle transport disconnect: reset runtime status when connection is lost
2168
+ const offRunnerTransport = runner.on("transport", (t) => {
2169
+ if (t.state === "disconnected") {
2170
+ console.info("[WorkbenchContext] Transport disconnected, resetting node status");
2171
+ setNodeStatus({});
2172
+ setEdgeStatus({});
2173
+ setFallbackStarts({});
2174
+ errorRunsRef.current = {};
2175
+ }
2176
+ });
2177
+ wb.refreshValidation();
2151
2178
  return () => {
2152
- off1();
2153
- off2();
2154
- off3();
2155
- off3b();
2156
- off4();
2157
- off4b();
2158
- off4c();
2159
- off5();
2160
- off5b();
2161
- off6();
2162
- off7();
2163
- offReg();
2179
+ offRunnerValue();
2180
+ offRunnerError();
2181
+ offRunnerInvalidate();
2182
+ offRunnerStats();
2183
+ offWbGraphChanged();
2184
+ offWbGraphUiChanged();
2185
+ offWbValidationChanged();
2186
+ offWbError();
2187
+ offWbAddNode();
2188
+ offWbdSetValidation();
2189
+ offWbSelectionChanged();
2190
+ offRunnerRegistry();
2191
+ offRunnerTransport();
2164
2192
  };
2165
2193
  }, [runner, wb]);
2166
- // Push incremental updates into running engine without full reload
2194
+ // Keep runner.currentDef in sync with the workbench graph at all times.
2195
+ // When an engine/runtime exists, this also pushes incremental wiring changes into it.
2167
2196
  React.useEffect(() => {
2168
- if (runner.isRunning()) {
2169
- try {
2170
- runner.update(def);
2171
- }
2172
- catch { }
2197
+ try {
2198
+ runner.update(def);
2173
2199
  }
2200
+ catch { }
2174
2201
  }, [runner, def, graphTick]);
2175
2202
  const validationByNode = React.useMemo(() => {
2176
2203
  const inputs = {};
@@ -2259,6 +2286,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
2259
2286
  edgeStatus,
2260
2287
  valuesTick,
2261
2288
  inputsMap,
2289
+ inputDefaultsMap,
2262
2290
  outputsMap,
2263
2291
  outputTypesMap,
2264
2292
  validationByNode,
@@ -2301,6 +2329,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
2301
2329
  removeSystemError,
2302
2330
  removeRegistryError,
2303
2331
  inputsMap,
2332
+ inputDefaultsMap,
2304
2333
  outputsMap,
2305
2334
  validationByNode,
2306
2335
  validationByEdge,
@@ -2386,7 +2415,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
2386
2415
  return String(value ?? "");
2387
2416
  }
2388
2417
  };
2389
- const { registry, def, selectedNodeId, selectedEdgeId, inputsMap, outputsMap, outputTypesMap, nodeStatus, edgeStatus, validationByNode, validationByEdge, validationGlobal, valuesTick, updateEdgeType, systemErrors, registryErrors, clearSystemErrors, clearRegistryErrors, removeSystemError, removeRegistryError, } = useWorkbenchContext();
2418
+ const { registry, def, selectedNodeId, selectedEdgeId, inputsMap, inputDefaultsMap, outputsMap, outputTypesMap, nodeStatus, edgeStatus, validationByNode, validationByEdge, validationGlobal, valuesTick, updateEdgeType, systemErrors, registryErrors, clearSystemErrors, clearRegistryErrors, removeSystemError, removeRegistryError, } = useWorkbenchContext();
2390
2419
  const nodeValidationIssues = validationByNode.issues;
2391
2420
  const edgeValidationIssues = validationByEdge.issues;
2392
2421
  const nodeValidationHandles = validationByNode;
@@ -2401,8 +2430,33 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
2401
2430
  .filter(([k]) => !sparkGraph.isInputPrivate(effectiveHandles.inputs, k))
2402
2431
  .map(([k]) => k);
2403
2432
  const outputHandles = Object.keys(effectiveHandles.outputs);
2404
- const nodeInputs = selectedNodeId ? inputsMap[selectedNodeId] ?? {} : {};
2433
+ const nodeInputsRaw = selectedNodeId ? inputsMap[selectedNodeId] ?? {} : {};
2434
+ const nodeInputsDefaults = selectedNodeId
2435
+ ? inputDefaultsMap[selectedNodeId] ?? {}
2436
+ : {};
2437
+ // Keep defaults separate for placeholder use (don't merge into nodeInputs)
2438
+ const nodeInputs = nodeInputsRaw;
2405
2439
  const nodeOutputs = selectedNodeId ? outputsMap[selectedNodeId] ?? {} : {};
2440
+ // Helper to truncate long values
2441
+ const truncateValue = (str, maxLen = 50) => {
2442
+ if (str.length <= maxLen)
2443
+ return str;
2444
+ const start = Math.floor(maxLen / 2) - 5;
2445
+ const end = str.length - Math.floor(maxLen / 2) + 5;
2446
+ return `${str.slice(0, start)}...${str.slice(end)}`;
2447
+ };
2448
+ // Helper to copy to clipboard
2449
+ const copyToClipboard = (text) => {
2450
+ navigator.clipboard.writeText(text).catch(() => {
2451
+ // Fallback for older browsers
2452
+ const textarea = document.createElement("textarea");
2453
+ textarea.value = text;
2454
+ document.body.appendChild(textarea);
2455
+ textarea.select();
2456
+ document.execCommand("copy");
2457
+ document.body.removeChild(textarea);
2458
+ });
2459
+ };
2406
2460
  const selectedNodeStatus = selectedNodeId
2407
2461
  ? nodeStatus?.[selectedNodeId]
2408
2462
  : undefined;
@@ -2488,19 +2542,30 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
2488
2542
  }, children: [jsxRuntime.jsx("option", { value: "", children: "(infer from source)" }), Array.from(registry.types.keys()).map((tid) => (jsxRuntime.jsx("option", { value: tid, children: tid }, tid)))] })] })] }), 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}` }), jsxRuntime.jsx("button", { className: "ml-2 text-[10px] px-1 py-[2px] border border-red-300 rounded text-red-700 hover:bg-red-50", onClick: (e) => {
2489
2543
  e.stopPropagation();
2490
2544
  deleteEdgeById(selectedEdge.id);
2491
- }, title: "Delete this edge", children: "Delete edge" })] }, 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?.activeRuns &&
2545
+ }, title: "Delete this edge", children: "Delete edge" })] }, 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?.activeRuns &&
2492
2546
  selectedNodeStatus.activeRuns > 0 && (jsxRuntime.jsxs("div", { className: "mt-1 text-xs text-blue-700 bg-blue-50 border border-blue-200 rounded px-2 py-1", children: [jsxRuntime.jsxs("div", { className: "font-semibold", children: ["Running (", selectedNodeStatus.activeRuns, ")"] }), selectedNodeStatus.activeRunIds &&
2493
2547
  selectedNodeStatus.activeRunIds.length > 0 ? (jsxRuntime.jsxs("div", { className: "mt-1", children: [jsxRuntime.jsx("div", { className: "text-[10px] text-blue-600", children: "RunIds:" }), jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1 mt-1", children: selectedNodeStatus.activeRunIds.map((runId, idx) => (jsxRuntime.jsx("span", { className: "text-[10px] px-1.5 py-0.5 bg-blue-100 border border-blue-300 rounded font-mono", children: runId }, idx))) })] })) : (jsxRuntime.jsx("div", { className: "text-[10px] text-blue-600 mt-1", children: "RunIds not available (some runs may have started without runId)" }))] })), !!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 ??
2494
2548
  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) => {
2495
2549
  const typeId = sparkGraph.getInputTypeId(effectiveHandles.inputs, h);
2496
2550
  const isLinked = def.edges.some((e) => e.target.nodeId === selectedNodeId &&
2497
2551
  e.target.handle === h);
2552
+ const inbound = new Set(def.edges
2553
+ .filter((e) => e.target.nodeId === selectedNodeId &&
2554
+ e.target.handle === h)
2555
+ .map((e) => e.target.handle));
2556
+ const hasDefault = !inbound.has(h) && nodeInputsDefaults[h] !== undefined;
2557
+ const defaultStr = hasDefault
2558
+ ? safeToString(typeId, nodeInputsDefaults[h])
2559
+ : undefined;
2498
2560
  const commonProps = {
2499
2561
  style: { flex: 1 },
2500
2562
  disabled: isLinked,
2501
2563
  };
2502
2564
  const current = nodeInputs[h];
2565
+ const hasValue = current !== undefined && current !== null;
2503
2566
  const value = drafts[h] ?? safeToString(typeId, current);
2567
+ const displayValue = hasValue ? value : "";
2568
+ const placeholder = hasDefault ? defaultStr : undefined;
2504
2569
  const onChangeText = (text) => setDrafts((d) => ({ ...d, [h]: text }));
2505
2570
  const commit = () => {
2506
2571
  const draft = drafts[h];
@@ -2513,6 +2578,11 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
2513
2578
  const orig = originals[h] ?? safeToString(typeId, current);
2514
2579
  setDrafts((d) => ({ ...d, [h]: orig }));
2515
2580
  };
2581
+ const clearInput = () => {
2582
+ setInput(h, undefined);
2583
+ setDrafts((d) => ({ ...d, [h]: "" }));
2584
+ setOriginals((o) => ({ ...o, [h]: "" }));
2585
+ };
2516
2586
  const isEnum = typeId?.startsWith("enum:");
2517
2587
  const inIssues = selectedNodeHandleValidation.inputs.filter((m) => m.handle === h);
2518
2588
  const hasValidation = inIssues.length > 0;
@@ -2520,25 +2590,43 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
2520
2590
  const title = inIssues
2521
2591
  .map((v) => `${v.code}: ${v.message}`)
2522
2592
  .join("; ");
2523
- return (jsxRuntime.jsxs("div", { className: "flex items-center gap-2 mb-1", children: [jsxRuntime.jsxs("label", { className: "w-32 flex flex-col", children: [jsxRuntime.jsx("span", { children: prettyHandle(h) }), jsxRuntime.jsx("span", { className: "text-gray-500 text-[11px]", children: typeId })] }), 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: current !== undefined && current !== null
2524
- ? String(current)
2525
- : "", onChange: (e) => {
2526
- const val = e.target.value;
2527
- const raw = val === "" ? undefined : Number(val);
2528
- setInput(h, raw);
2529
- // keep drafts/originals in sync with label for display elsewhere
2530
- const display = safeToString(typeId, raw);
2531
- setDrafts((d) => ({ ...d, [h]: display }));
2532
- setOriginals((o) => ({ ...o, [h]: display }));
2533
- }, ...commonProps, children: [jsxRuntime.jsx("option", { value: "", children: "(select)" }), registry.enums.get(typeId)?.options.map((opt) => (jsxRuntime.jsx("option", { value: String(opt.value), children: opt.label }, opt.value)))] })) : isLinked ? (toElement(typeId, current)) : (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) => {
2534
- if (e.key === "Enter")
2535
- commit();
2536
- if (e.key === "Escape")
2537
- revert();
2538
- }, ...commonProps }))] }, h));
2539
- }))] }), 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.jsxs("label", { className: "w-20 flex flex-col", children: [jsxRuntime.jsx("span", { children: prettyHandle(h) }), jsxRuntime.jsx("span", { className: "text-gray-500 text-[11px]", children: outputTypesMap[selectedNodeId]?.[h] ?? "" })] }), jsxRuntime.jsx("div", { className: "flex-1", children: (() => {
2593
+ return (jsxRuntime.jsxs("div", { className: "flex items-center gap-2 mb-1", children: [jsxRuntime.jsxs("label", { className: "w-32 flex flex-col", children: [jsxRuntime.jsx("span", { children: prettyHandle(h) }), jsxRuntime.jsx("span", { className: "text-gray-500 text-[11px]", children: typeId })] }), hasValidation && (jsxRuntime.jsx(IssueBadge, { level: hasErr ? "error" : "warning", size: 24, className: "ml-1 w-6 h-6", title: title })), isEnum ? (jsxRuntime.jsxs("div", { className: "flex items-center gap-1 flex-1", children: [jsxRuntime.jsxs("select", { className: "border border-gray-300 rounded px-2 py-1 focus:outline-none focus:ring-2 focus:ring-blue-500 flex-1", value: current !== undefined && current !== null
2594
+ ? String(current)
2595
+ : "", onChange: (e) => {
2596
+ const val = e.target.value;
2597
+ const raw = val === "" ? undefined : Number(val);
2598
+ setInput(h, raw);
2599
+ // keep drafts/originals in sync with label for display elsewhere
2600
+ const display = safeToString(typeId, raw);
2601
+ setDrafts((d) => ({ ...d, [h]: display }));
2602
+ setOriginals((o) => ({ ...o, [h]: display }));
2603
+ }, ...commonProps, children: [jsxRuntime.jsx("option", { value: "", children: placeholder
2604
+ ? `Default: ${placeholder}`
2605
+ : "(select)" }), registry.enums
2606
+ .get(typeId)
2607
+ ?.options.map((opt) => (jsxRuntime.jsx("option", { value: String(opt.value), children: opt.label }, opt.value)))] }), hasValue && !isLinked && (jsxRuntime.jsx("button", { className: "flex-shrink-0 p-1 hover:bg-gray-100 rounded text-gray-500 hover:text-gray-700", onClick: clearInput, title: "Clear input value", children: jsxRuntime.jsx(react$1.XCircleIcon, { size: 16 }) }))] })) : isLinked ? (jsxRuntime.jsx("div", { className: "flex items-center gap-1 flex-1", children: jsxRuntime.jsx("div", { className: "flex-1 min-w-0", children: (() => {
2608
+ const displayStr = safeToString(typeId, current);
2609
+ const isLong = displayStr.length > 50;
2610
+ const truncated = isLong
2611
+ ? truncateValue(displayStr)
2612
+ : displayStr;
2613
+ return (jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [jsxRuntime.jsx("span", { className: "truncate", children: truncated }), isLong && (jsxRuntime.jsx("button", { className: "flex-shrink-0 p-1 hover:bg-gray-100 rounded", onClick: () => copyToClipboard(displayStr), title: "Copy full value", children: jsxRuntime.jsx(react$1.CopyIcon, { size: 14 }) }))] }));
2614
+ })() }) })) : (jsxRuntime.jsxs("div", { className: "flex items-center gap-1 flex-1", children: [jsxRuntime.jsx("input", { className: "border border-gray-300 rounded px-2 py-1 focus:outline-none focus:ring-2 focus:ring-blue-500 flex-1", placeholder: placeholder
2615
+ ? `Default: ${placeholder}`
2616
+ : undefined, value: displayValue, onChange: (e) => onChangeText(e.target.value), onBlur: commit, onKeyDown: (e) => {
2617
+ if (e.key === "Enter")
2618
+ commit();
2619
+ if (e.key === "Escape")
2620
+ revert();
2621
+ }, ...commonProps }), hasValue && !isLinked && (jsxRuntime.jsx("button", { className: "flex-shrink-0 p-1 hover:bg-gray-100 rounded text-gray-500 hover:text-gray-700", onClick: clearInput, title: "Clear input value", children: jsxRuntime.jsx(react$1.XCircleIcon, { size: 16 }) }))] }))] }, h));
2622
+ }))] }), 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.jsxs("label", { className: "w-20 flex flex-col", children: [jsxRuntime.jsx("span", { children: prettyHandle(h) }), jsxRuntime.jsx("span", { className: "text-gray-500 text-[11px]", children: outputTypesMap[selectedNodeId]?.[h] ?? "" })] }), jsxRuntime.jsx("div", { className: "flex-1 min-w-0", children: (() => {
2540
2623
  const { typeId, value } = resolveOutputDisplay(nodeOutputs[h], effectiveHandles.outputs[h]);
2541
- return toElement(typeId, value);
2624
+ const displayStr = safeToString(typeId, value);
2625
+ const isLong = displayStr.length > 50;
2626
+ const truncated = isLong
2627
+ ? truncateValue(displayStr)
2628
+ : displayStr;
2629
+ return (jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [jsxRuntime.jsx("span", { className: "truncate", children: truncated }), isLong && (jsxRuntime.jsx("button", { className: "flex-shrink-0 p-1 hover:bg-gray-100 rounded", onClick: () => copyToClipboard(displayStr), title: "Copy full value", children: jsxRuntime.jsx(react$1.CopyIcon, { size: 14 }) }))] }));
2542
2630
  })() }), (() => {
2543
2631
  const outIssues = selectedNodeHandleValidation.outputs.filter((m) => m.handle === h);
2544
2632
  if (outIssues.length === 0)
@@ -2680,7 +2768,7 @@ function DefaultNodeHeader({ id, title, validation, right, showId, onInvalidate,
2680
2768
  .join("; ") })), showId && jsxRuntime.jsxs("span", { className: "text-[10px] opacity-70", children: ["(", id, ")"] })] })] }));
2681
2769
  }
2682
2770
  function DefaultNodeContent({ data, isConnectable, }) {
2683
- const { showValues, inputValues, outputValues, toString } = data;
2771
+ const { showValues, inputValues, inputDefaults, outputValues, toString } = data;
2684
2772
  const inputEntries = data.inputHandles ?? [];
2685
2773
  const outputEntries = data.outputHandles ?? [];
2686
2774
  const status = data.status ?? { activeRuns: 0 };
@@ -2709,14 +2797,23 @@ function DefaultNodeContent({ data, isConnectable, }) {
2709
2797
  if (!showValues)
2710
2798
  return undefined;
2711
2799
  if (kind === "input") {
2712
- const txt = toString(entry.typeId, inputValues?.[entry.id]);
2713
- return typeof txt === "string" ? txt : String(txt);
2800
+ const value = inputValues?.[entry.id];
2801
+ const isDefault = value !== undefined &&
2802
+ inputDefaults?.[entry.id] !== undefined &&
2803
+ JSON.stringify(value) ===
2804
+ JSON.stringify(inputDefaults[entry.id]);
2805
+ const txt = toString(entry.typeId, value);
2806
+ const str = typeof txt === "string" ? txt : String(txt);
2807
+ return { text: str, isDefault };
2714
2808
  }
2715
2809
  const resolved = resolveOutputDisplay(outputValues?.[entry.id], entry.typeId);
2716
2810
  const txt = toString(resolved.typeId, resolved.value);
2717
- return typeof txt === "string" ? txt : String(txt);
2811
+ return {
2812
+ text: typeof txt === "string" ? txt : String(txt),
2813
+ isDefault: false,
2814
+ };
2718
2815
  })();
2719
- return (jsxRuntime.jsxs("span", { className: "flex items-center gap-1 w-full", children: [kind === "output" ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [valueText !== undefined ? (jsxRuntime.jsx("span", { className: "opacity-60 truncate pl-1", style: { flex: 1, minWidth: 0, maxWidth: "100%" }, children: valueText })) : (jsxRuntime.jsx("span", { style: { flex: 1, minWidth: 0, maxWidth: "100%" } })), jsxRuntime.jsx("span", { className: "truncate shrink-0", style: valueText !== undefined ? { maxWidth: "40%" } : {}, children: prettyHandle(handleId) })] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("span", { className: "truncate shrink-0", style: valueText !== undefined ? { maxWidth: "40%" } : {}, children: prettyHandle(handleId) }), valueText !== undefined && (jsxRuntime.jsx("span", { className: "opacity-60 truncate pr-1", style: { flex: 1, minWidth: 0, maxWidth: "100%" }, children: valueText }))] })), hasAny && (jsxRuntime.jsx(IssueBadge, { level: hasErr ? "error" : "warning", size: 12, className: "shrink-0", title: title }))] }));
2816
+ return (jsxRuntime.jsxs("span", { className: `flex items-center gap-1 w-full ${valueText?.isDefault ? "text-gray-400" : ""}`, children: [kind === "output" ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [valueText !== undefined ? (jsxRuntime.jsx("span", { className: "opacity-60 truncate pl-1", style: { flex: 1, minWidth: 0, maxWidth: "100%" }, children: valueText.text })) : (jsxRuntime.jsx("span", { style: { flex: 1, minWidth: 0, maxWidth: "100%" } })), jsxRuntime.jsx("span", { className: "truncate shrink-0", style: valueText !== undefined ? { maxWidth: "40%" } : {}, children: prettyHandle(handleId) })] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("span", { className: "truncate shrink-0", style: valueText !== undefined ? { maxWidth: "40%" } : {}, children: prettyHandle(handleId) }), valueText !== undefined && (jsxRuntime.jsx("span", { className: `truncate pr-1 ${valueText.isDefault ? "text-gray-400" : "opacity-60"}`, style: { flex: 1, minWidth: 0, maxWidth: "100%" }, children: valueText.text }))] })), hasAny && (jsxRuntime.jsx(IssueBadge, { level: hasErr ? "error" : "warning", size: 12, className: "shrink-0", title: title }))] }));
2720
2817
  } })] }));
2721
2818
  }
2722
2819
 
@@ -3013,7 +3110,7 @@ function NodeContextMenu({ open, clientPos, nodeId, onClose, }) {
3013
3110
  }
3014
3111
 
3015
3112
  const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, getDefaultNodeSize }, ref) => {
3016
- const { wb, registry, inputsMap, outputsMap, valuesTick, nodeStatus, edgeStatus, validationByNode, validationByEdge, uiVersion, } = useWorkbenchContext();
3113
+ const { wb, registry, inputsMap, inputDefaultsMap, outputsMap, valuesTick, nodeStatus, edgeStatus, validationByNode, validationByEdge, uiVersion, } = useWorkbenchContext();
3017
3114
  const nodeValidation = validationByNode;
3018
3115
  const edgeValidation = validationByEdge.errors;
3019
3116
  // Keep stable references for nodes/edges to avoid unnecessary updates
@@ -3108,9 +3205,28 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
3108
3205
  const { nodes, edges } = React.useMemo(() => {
3109
3206
  const def = wb.export();
3110
3207
  const sel = wb.getSelection();
3208
+ // Merge defaults with inputs for node display (defaults shown in lighter gray)
3209
+ const inputsWithDefaults = {};
3210
+ for (const n of def.nodes) {
3211
+ const nodeInputs = inputsMap[n.nodeId] ?? {};
3212
+ const nodeDefaults = inputDefaultsMap[n.nodeId] ?? {};
3213
+ const inbound = new Set(def.edges
3214
+ .filter((e) => e.target.nodeId === n.nodeId)
3215
+ .map((e) => e.target.handle));
3216
+ const merged = { ...nodeInputs };
3217
+ for (const [h, v] of Object.entries(nodeDefaults)) {
3218
+ if (!inbound.has(h) && merged[h] === undefined) {
3219
+ merged[h] = v;
3220
+ }
3221
+ }
3222
+ if (Object.keys(merged).length > 0) {
3223
+ inputsWithDefaults[n.nodeId] = merged;
3224
+ }
3225
+ }
3111
3226
  const out = toReactFlow(def, wb.getPositions(), registry, {
3112
3227
  showValues,
3113
- inputs: inputsMap,
3228
+ inputs: inputsWithDefaults,
3229
+ inputDefaults: inputDefaultsMap,
3114
3230
  outputs: outputsMap,
3115
3231
  resolveNodeType,
3116
3232
  toString,
@@ -3349,7 +3465,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
3349
3465
  const off1 = wb.on("graphChanged", () => {
3350
3466
  try {
3351
3467
  const cur = wb.export();
3352
- const inputs = runner.getInputs(cur);
3468
+ const inputs = runner.getInputs();
3353
3469
  onChange({ def: cur, inputs });
3354
3470
  }
3355
3471
  catch { }
@@ -3357,7 +3473,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
3357
3473
  const off2 = runner.on("value", () => {
3358
3474
  try {
3359
3475
  const cur = wb.export();
3360
- const inputs = runner.getInputs(cur);
3476
+ const inputs = runner.getInputs();
3361
3477
  onChange({ def: cur, inputs });
3362
3478
  }
3363
3479
  catch { }
@@ -3403,7 +3519,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
3403
3519
  const downloadGraph = React.useCallback(() => {
3404
3520
  try {
3405
3521
  const def = wb.export();
3406
- const inputs = runner.getInputs(def);
3522
+ const inputs = runner.getInputs();
3407
3523
  const payload = { def, inputs };
3408
3524
  const pretty = JSON.stringify(payload, null, 2);
3409
3525
  const blob = new Blob([pretty], { type: "application/json" });