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