@bian-womp/spark-workbench 0.2.34 → 0.2.36

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 +175 -53
  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 +2 -1
  12. package/lib/cjs/src/runtime/AbstractGraphRunner.d.ts.map +1 -1
  13. package/lib/cjs/src/runtime/IGraphRunner.d.ts +1 -0
  14. package/lib/cjs/src/runtime/IGraphRunner.d.ts.map +1 -1
  15. package/lib/cjs/src/runtime/LocalGraphRunner.d.ts +1 -0
  16. package/lib/cjs/src/runtime/LocalGraphRunner.d.ts.map +1 -1
  17. package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts +1 -0
  18. package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
  19. package/lib/esm/index.js +175 -53
  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 +2 -1
  30. package/lib/esm/src/runtime/AbstractGraphRunner.d.ts.map +1 -1
  31. package/lib/esm/src/runtime/IGraphRunner.d.ts +1 -0
  32. package/lib/esm/src/runtime/IGraphRunner.d.ts.map +1 -1
  33. package/lib/esm/src/runtime/LocalGraphRunner.d.ts +1 -0
  34. package/lib/esm/src/runtime/LocalGraphRunner.d.ts.map +1 -1
  35. package/lib/esm/src/runtime/RemoteGraphRunner.d.ts +1 -0
  36. package/lib/esm/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
  37. package/package.json +4 -4
package/lib/cjs/index.cjs CHANGED
@@ -539,6 +539,16 @@ class LocalGraphRunner extends AbstractGraphRunner {
539
539
  }
540
540
  return out;
541
541
  }
542
+ getInputDefaults(def) {
543
+ const out = {};
544
+ for (const n of def.nodes) {
545
+ const dynDefaults = n.resolvedHandles?.inputDefaults ?? {};
546
+ if (Object.keys(dynDefaults).length > 0) {
547
+ out[n.nodeId] = dynDefaults;
548
+ }
549
+ }
550
+ return out;
551
+ }
542
552
  async snapshotFull() {
543
553
  const def = undefined; // UI will supply def/positions on download for local
544
554
  const inputs = this.getInputs(this.runtime
@@ -857,6 +867,16 @@ class RemoteGraphRunner extends AbstractGraphRunner {
857
867
  }
858
868
  return out;
859
869
  }
870
+ getInputDefaults(def) {
871
+ const out = {};
872
+ for (const n of def.nodes) {
873
+ const dynDefaults = n.resolvedHandles?.inputDefaults ?? {};
874
+ if (Object.keys(dynDefaults).length > 0) {
875
+ out[n.nodeId] = dynDefaults;
876
+ }
877
+ }
878
+ return out;
879
+ }
860
880
  dispose() {
861
881
  super.dispose();
862
882
  this.runner = undefined;
@@ -1608,6 +1628,7 @@ function toReactFlow(def, positions, registry, opts) {
1608
1628
  renderWidth: initialWidth,
1609
1629
  renderHeight: initialHeight,
1610
1630
  inputValues: opts.inputs?.[n.nodeId],
1631
+ inputDefaults: opts.inputDefaults?.[n.nodeId],
1611
1632
  outputValues: opts.outputs?.[n.nodeId],
1612
1633
  status: opts.nodeStatus?.[n.nodeId],
1613
1634
  validation: {
@@ -1774,6 +1795,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
1774
1795
  // Def and IO values
1775
1796
  const def = wb.export();
1776
1797
  const inputsMap = React.useMemo(() => runner.getInputs(def), [runner, def, valuesTick]);
1798
+ const inputDefaultsMap = React.useMemo(() => runner.getInputDefaults(def), [runner, def, valuesTick]);
1777
1799
  const outputsMap = React.useMemo(() => runner.getOutputs(def), [runner, def, valuesTick]);
1778
1800
  const outputTypesMap = React.useMemo(() => {
1779
1801
  const out = {};
@@ -1937,7 +1959,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
1937
1959
  }
1938
1960
  catch { }
1939
1961
  };
1940
- const off1 = runner.on("value", (e) => {
1962
+ const offRunnerValue = runner.on("value", (e) => {
1941
1963
  if (e?.io === "input") {
1942
1964
  const nodeId = e?.nodeId;
1943
1965
  setNodeStatus((s) => ({
@@ -1947,7 +1969,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
1947
1969
  }
1948
1970
  return add("runner", "value")(e);
1949
1971
  });
1950
- const off2 = runner.on("error", (e) => {
1972
+ const offRunnerError = runner.on("error", (e) => {
1951
1973
  const edgeError = e;
1952
1974
  const nodeError = e;
1953
1975
  const registryError = e;
@@ -2000,14 +2022,14 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
2000
2022
  }
2001
2023
  return add("runner", "error")(e);
2002
2024
  });
2003
- const off3 = runner.on("invalidate", (e) => {
2025
+ const offRunnerInvalidate = runner.on("invalidate", (e) => {
2004
2026
  // After build/update, pull resolved handles and merge in-place (no graphChanged)
2005
2027
  if (e?.reason === "graph-updated" || e?.reason === "graph-built") {
2006
2028
  refreshResolvedHandles();
2007
2029
  }
2008
2030
  return add("runner", "invalidate")(e);
2009
2031
  });
2010
- const off3b = runner.on("stats", (s) => {
2032
+ const offRunnerStats = runner.on("stats", (s) => {
2011
2033
  if (!s)
2012
2034
  return;
2013
2035
  if (s.kind === "node-start") {
@@ -2110,9 +2132,11 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
2110
2132
  }
2111
2133
  return add("runner", "stats")(s);
2112
2134
  });
2113
- const off4 = wb.on("graphChanged", add("workbench", "graphChanged"));
2135
+ const offWbGraphChanged = wb.on("graphChanged", add("workbench", "graphChanged"));
2136
+ const offWbGraphUiChanged = wb.on("graphUiChanged", add("workbench", "graphUiChanged"));
2137
+ const offWbValidationChanged = wb.on("validationChanged", add("workbench", "validationChanged"));
2114
2138
  // Ensure newly added nodes start as invalidated until first evaluation
2115
- const off4c = wb.on("graphChanged", (e) => {
2139
+ const offWbAddNode = wb.on("graphChanged", (e) => {
2116
2140
  const change = e.change;
2117
2141
  if (change?.type === "addNode" && typeof change.nodeId === "string") {
2118
2142
  const id = change.nodeId;
@@ -2122,17 +2146,14 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
2122
2146
  }));
2123
2147
  }
2124
2148
  });
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) => {
2149
+ const offWbdSetValidation = wb.on("validationChanged", (r) => setValidation(r));
2150
+ const offWbSelectionChanged = wb.on("selectionChanged", (sel) => {
2129
2151
  setSelectedNodeId(sel.nodes?.[0]);
2130
2152
  setSelectedEdgeId(sel.edges?.[0]);
2131
2153
  });
2132
- const off7 = wb.on("error", add("workbench", "error"));
2133
- wb.refreshValidation();
2154
+ const offWbError = wb.on("error", add("workbench", "error"));
2134
2155
  // Registry updates: swap registry and refresh graph validation/UI
2135
- const offReg = runner.on("registry", (newReg) => {
2156
+ const offRunnerRegistry = runner.on("registry", (newReg) => {
2136
2157
  try {
2137
2158
  setRegistry(newReg);
2138
2159
  wb.setRegistry(newReg);
@@ -2148,19 +2169,31 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
2148
2169
  console.error("Failed to handle registry changed event");
2149
2170
  }
2150
2171
  });
2172
+ // Handle transport disconnect: reset runtime status when connection is lost
2173
+ const offRunnerTransport = runner.on("transport", (t) => {
2174
+ if (t.state === "disconnected") {
2175
+ console.info("[WorkbenchContext] Transport disconnected, resetting node status");
2176
+ setNodeStatus({});
2177
+ setEdgeStatus({});
2178
+ setFallbackStarts({});
2179
+ errorRunsRef.current = {};
2180
+ }
2181
+ });
2182
+ wb.refreshValidation();
2151
2183
  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();
2184
+ offRunnerValue();
2185
+ offRunnerError();
2186
+ offRunnerInvalidate();
2187
+ offRunnerStats();
2188
+ offWbGraphChanged();
2189
+ offWbGraphUiChanged();
2190
+ offWbValidationChanged();
2191
+ offWbError();
2192
+ offWbAddNode();
2193
+ offWbdSetValidation();
2194
+ offWbSelectionChanged();
2195
+ offRunnerRegistry();
2196
+ offRunnerTransport();
2164
2197
  };
2165
2198
  }, [runner, wb]);
2166
2199
  // Push incremental updates into running engine without full reload
@@ -2259,6 +2292,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
2259
2292
  edgeStatus,
2260
2293
  valuesTick,
2261
2294
  inputsMap,
2295
+ inputDefaultsMap,
2262
2296
  outputsMap,
2263
2297
  outputTypesMap,
2264
2298
  validationByNode,
@@ -2301,6 +2335,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
2301
2335
  removeSystemError,
2302
2336
  removeRegistryError,
2303
2337
  inputsMap,
2338
+ inputDefaultsMap,
2304
2339
  outputsMap,
2305
2340
  validationByNode,
2306
2341
  validationByEdge,
@@ -2386,7 +2421,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
2386
2421
  return String(value ?? "");
2387
2422
  }
2388
2423
  };
2389
- const { registry, def, selectedNodeId, selectedEdgeId, inputsMap, outputsMap, outputTypesMap, nodeStatus, edgeStatus, validationByNode, validationByEdge, validationGlobal, valuesTick, updateEdgeType, systemErrors, registryErrors, clearSystemErrors, clearRegistryErrors, removeSystemError, removeRegistryError, } = useWorkbenchContext();
2424
+ const { registry, def, selectedNodeId, selectedEdgeId, inputsMap, inputDefaultsMap, outputsMap, outputTypesMap, nodeStatus, edgeStatus, validationByNode, validationByEdge, validationGlobal, valuesTick, updateEdgeType, systemErrors, registryErrors, clearSystemErrors, clearRegistryErrors, removeSystemError, removeRegistryError, } = useWorkbenchContext();
2390
2425
  const nodeValidationIssues = validationByNode.issues;
2391
2426
  const edgeValidationIssues = validationByEdge.issues;
2392
2427
  const nodeValidationHandles = validationByNode;
@@ -2401,8 +2436,33 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
2401
2436
  .filter(([k]) => !sparkGraph.isInputPrivate(effectiveHandles.inputs, k))
2402
2437
  .map(([k]) => k);
2403
2438
  const outputHandles = Object.keys(effectiveHandles.outputs);
2404
- const nodeInputs = selectedNodeId ? inputsMap[selectedNodeId] ?? {} : {};
2439
+ const nodeInputsRaw = selectedNodeId ? inputsMap[selectedNodeId] ?? {} : {};
2440
+ const nodeInputsDefaults = selectedNodeId
2441
+ ? inputDefaultsMap[selectedNodeId] ?? {}
2442
+ : {};
2443
+ // Keep defaults separate for placeholder use (don't merge into nodeInputs)
2444
+ const nodeInputs = nodeInputsRaw;
2405
2445
  const nodeOutputs = selectedNodeId ? outputsMap[selectedNodeId] ?? {} : {};
2446
+ // Helper to truncate long values
2447
+ const truncateValue = (str, maxLen = 50) => {
2448
+ if (str.length <= maxLen)
2449
+ return str;
2450
+ const start = Math.floor(maxLen / 2) - 5;
2451
+ const end = str.length - Math.floor(maxLen / 2) + 5;
2452
+ return `${str.slice(0, start)}...${str.slice(end)}`;
2453
+ };
2454
+ // Helper to copy to clipboard
2455
+ const copyToClipboard = (text) => {
2456
+ navigator.clipboard.writeText(text).catch(() => {
2457
+ // Fallback for older browsers
2458
+ const textarea = document.createElement("textarea");
2459
+ textarea.value = text;
2460
+ document.body.appendChild(textarea);
2461
+ textarea.select();
2462
+ document.execCommand("copy");
2463
+ document.body.removeChild(textarea);
2464
+ });
2465
+ };
2406
2466
  const selectedNodeStatus = selectedNodeId
2407
2467
  ? nodeStatus?.[selectedNodeId]
2408
2468
  : undefined;
@@ -2488,19 +2548,30 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
2488
2548
  }, 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
2549
  e.stopPropagation();
2490
2550
  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 &&
2551
+ }, 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
2552
  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
2553
  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
2554
  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
2555
  const typeId = sparkGraph.getInputTypeId(effectiveHandles.inputs, h);
2496
2556
  const isLinked = def.edges.some((e) => e.target.nodeId === selectedNodeId &&
2497
2557
  e.target.handle === h);
2558
+ const inbound = new Set(def.edges
2559
+ .filter((e) => e.target.nodeId === selectedNodeId &&
2560
+ e.target.handle === h)
2561
+ .map((e) => e.target.handle));
2562
+ const hasDefault = !inbound.has(h) && nodeInputsDefaults[h] !== undefined;
2563
+ const defaultStr = hasDefault
2564
+ ? safeToString(typeId, nodeInputsDefaults[h])
2565
+ : undefined;
2498
2566
  const commonProps = {
2499
2567
  style: { flex: 1 },
2500
2568
  disabled: isLinked,
2501
2569
  };
2502
2570
  const current = nodeInputs[h];
2571
+ const hasValue = current !== undefined && current !== null;
2503
2572
  const value = drafts[h] ?? safeToString(typeId, current);
2573
+ const displayValue = hasValue ? value : "";
2574
+ const placeholder = hasDefault ? defaultStr : undefined;
2504
2575
  const onChangeText = (text) => setDrafts((d) => ({ ...d, [h]: text }));
2505
2576
  const commit = () => {
2506
2577
  const draft = drafts[h];
@@ -2513,6 +2584,11 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
2513
2584
  const orig = originals[h] ?? safeToString(typeId, current);
2514
2585
  setDrafts((d) => ({ ...d, [h]: orig }));
2515
2586
  };
2587
+ const clearInput = () => {
2588
+ setInput(h, undefined);
2589
+ setDrafts((d) => ({ ...d, [h]: "" }));
2590
+ setOriginals((o) => ({ ...o, [h]: "" }));
2591
+ };
2516
2592
  const isEnum = typeId?.startsWith("enum:");
2517
2593
  const inIssues = selectedNodeHandleValidation.inputs.filter((m) => m.handle === h);
2518
2594
  const hasValidation = inIssues.length > 0;
@@ -2520,25 +2596,43 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
2520
2596
  const title = inIssues
2521
2597
  .map((v) => `${v.code}: ${v.message}`)
2522
2598
  .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: (() => {
2599
+ 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
2600
+ ? String(current)
2601
+ : "", onChange: (e) => {
2602
+ const val = e.target.value;
2603
+ const raw = val === "" ? undefined : Number(val);
2604
+ setInput(h, raw);
2605
+ // keep drafts/originals in sync with label for display elsewhere
2606
+ const display = safeToString(typeId, raw);
2607
+ setDrafts((d) => ({ ...d, [h]: display }));
2608
+ setOriginals((o) => ({ ...o, [h]: display }));
2609
+ }, ...commonProps, children: [jsxRuntime.jsx("option", { value: "", children: placeholder
2610
+ ? `Default: ${placeholder}`
2611
+ : "(select)" }), registry.enums
2612
+ .get(typeId)
2613
+ ?.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: (() => {
2614
+ const displayStr = safeToString(typeId, current);
2615
+ const isLong = displayStr.length > 50;
2616
+ const truncated = isLong
2617
+ ? truncateValue(displayStr)
2618
+ : displayStr;
2619
+ 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 }) }))] }));
2620
+ })() }) })) : (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
2621
+ ? `Default: ${placeholder}`
2622
+ : undefined, value: displayValue, onChange: (e) => onChangeText(e.target.value), onBlur: commit, onKeyDown: (e) => {
2623
+ if (e.key === "Enter")
2624
+ commit();
2625
+ if (e.key === "Escape")
2626
+ revert();
2627
+ }, ...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));
2628
+ }))] }), 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
2629
  const { typeId, value } = resolveOutputDisplay(nodeOutputs[h], effectiveHandles.outputs[h]);
2541
- return toElement(typeId, value);
2630
+ const displayStr = safeToString(typeId, value);
2631
+ const isLong = displayStr.length > 50;
2632
+ const truncated = isLong
2633
+ ? truncateValue(displayStr)
2634
+ : displayStr;
2635
+ 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
2636
  })() }), (() => {
2543
2637
  const outIssues = selectedNodeHandleValidation.outputs.filter((m) => m.handle === h);
2544
2638
  if (outIssues.length === 0)
@@ -2680,7 +2774,7 @@ function DefaultNodeHeader({ id, title, validation, right, showId, onInvalidate,
2680
2774
  .join("; ") })), showId && jsxRuntime.jsxs("span", { className: "text-[10px] opacity-70", children: ["(", id, ")"] })] })] }));
2681
2775
  }
2682
2776
  function DefaultNodeContent({ data, isConnectable, }) {
2683
- const { showValues, inputValues, outputValues, toString } = data;
2777
+ const { showValues, inputValues, inputDefaults, outputValues, toString } = data;
2684
2778
  const inputEntries = data.inputHandles ?? [];
2685
2779
  const outputEntries = data.outputHandles ?? [];
2686
2780
  const status = data.status ?? { activeRuns: 0 };
@@ -2709,14 +2803,23 @@ function DefaultNodeContent({ data, isConnectable, }) {
2709
2803
  if (!showValues)
2710
2804
  return undefined;
2711
2805
  if (kind === "input") {
2712
- const txt = toString(entry.typeId, inputValues?.[entry.id]);
2713
- return typeof txt === "string" ? txt : String(txt);
2806
+ const value = inputValues?.[entry.id];
2807
+ const isDefault = value !== undefined &&
2808
+ inputDefaults?.[entry.id] !== undefined &&
2809
+ JSON.stringify(value) ===
2810
+ JSON.stringify(inputDefaults[entry.id]);
2811
+ const txt = toString(entry.typeId, value);
2812
+ const str = typeof txt === "string" ? txt : String(txt);
2813
+ return { text: str, isDefault };
2714
2814
  }
2715
2815
  const resolved = resolveOutputDisplay(outputValues?.[entry.id], entry.typeId);
2716
2816
  const txt = toString(resolved.typeId, resolved.value);
2717
- return typeof txt === "string" ? txt : String(txt);
2817
+ return {
2818
+ text: typeof txt === "string" ? txt : String(txt),
2819
+ isDefault: false,
2820
+ };
2718
2821
  })();
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 }))] }));
2822
+ 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
2823
  } })] }));
2721
2824
  }
2722
2825
 
@@ -3013,7 +3116,7 @@ function NodeContextMenu({ open, clientPos, nodeId, onClose, }) {
3013
3116
  }
3014
3117
 
3015
3118
  const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, getDefaultNodeSize }, ref) => {
3016
- const { wb, registry, inputsMap, outputsMap, valuesTick, nodeStatus, edgeStatus, validationByNode, validationByEdge, uiVersion, } = useWorkbenchContext();
3119
+ const { wb, registry, inputsMap, inputDefaultsMap, outputsMap, valuesTick, nodeStatus, edgeStatus, validationByNode, validationByEdge, uiVersion, } = useWorkbenchContext();
3017
3120
  const nodeValidation = validationByNode;
3018
3121
  const edgeValidation = validationByEdge.errors;
3019
3122
  // Keep stable references for nodes/edges to avoid unnecessary updates
@@ -3108,9 +3211,28 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
3108
3211
  const { nodes, edges } = React.useMemo(() => {
3109
3212
  const def = wb.export();
3110
3213
  const sel = wb.getSelection();
3214
+ // Merge defaults with inputs for node display (defaults shown in lighter gray)
3215
+ const inputsWithDefaults = {};
3216
+ for (const n of def.nodes) {
3217
+ const nodeInputs = inputsMap[n.nodeId] ?? {};
3218
+ const nodeDefaults = inputDefaultsMap[n.nodeId] ?? {};
3219
+ const inbound = new Set(def.edges
3220
+ .filter((e) => e.target.nodeId === n.nodeId)
3221
+ .map((e) => e.target.handle));
3222
+ const merged = { ...nodeInputs };
3223
+ for (const [h, v] of Object.entries(nodeDefaults)) {
3224
+ if (!inbound.has(h) && merged[h] === undefined) {
3225
+ merged[h] = v;
3226
+ }
3227
+ }
3228
+ if (Object.keys(merged).length > 0) {
3229
+ inputsWithDefaults[n.nodeId] = merged;
3230
+ }
3231
+ }
3111
3232
  const out = toReactFlow(def, wb.getPositions(), registry, {
3112
3233
  showValues,
3113
- inputs: inputsMap,
3234
+ inputs: inputsWithDefaults,
3235
+ inputDefaults: inputDefaultsMap,
3114
3236
  outputs: outputsMap,
3115
3237
  resolveNodeType,
3116
3238
  toString,