@bian-womp/spark-workbench 0.2.27 → 0.2.28

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.
package/lib/cjs/index.cjs CHANGED
@@ -524,7 +524,16 @@ class LocalGraphRunner extends AbstractGraphRunner {
524
524
  const runtimeInputs = this.runtime
525
525
  ? this.runtime.getNodeData?.(n.nodeId)?.inputs ?? {}
526
526
  : {};
527
- const merged = { ...runtimeInputs, ...staged };
527
+ // Build inbound handle set for this node from current def
528
+ const inbound = new Set(def.edges
529
+ .filter((e) => e.target.nodeId === n.nodeId)
530
+ .map((e) => e.target.handle));
531
+ // Merge staged only for non-inbound handles so UI reflects runtime values for wired inputs
532
+ const merged = { ...runtimeInputs };
533
+ for (const [h, v] of Object.entries(staged)) {
534
+ if (!inbound.has(h))
535
+ merged[h] = v;
536
+ }
528
537
  if (Object.keys(merged).length > 0)
529
538
  out[n.nodeId] = merged;
530
539
  }
@@ -795,12 +804,21 @@ class RemoteGraphRunner extends AbstractGraphRunner {
795
804
  const desc = this.registry.nodes.get(n.typeId);
796
805
  const handles = Object.keys(resolved ?? desc?.inputs ?? {});
797
806
  const cur = {};
807
+ // Build inbound handle set for this node to honor wiring precedence
808
+ const inbound = new Set(def.edges
809
+ .filter((e) => e.target.nodeId === n.nodeId)
810
+ .map((e) => e.target.handle));
798
811
  for (const h of handles) {
799
812
  const rec = cache.get(`${n.nodeId}.${h}`);
800
813
  if (rec && rec.io === "input")
801
814
  cur[h] = rec.value;
802
815
  }
803
- const merged = { ...cur, ...staged };
816
+ // Merge staged only for non-inbound handles so UI doesn't override runtime values
817
+ const merged = { ...cur };
818
+ for (const [h, v] of Object.entries(staged)) {
819
+ if (!inbound.has(h))
820
+ merged[h] = v;
821
+ }
804
822
  if (Object.keys(merged).length > 0)
805
823
  out[n.nodeId] = merged;
806
824
  }
@@ -1264,6 +1282,23 @@ function formatDeclaredTypeSignature(declared) {
1264
1282
  return declared.join(" | ");
1265
1283
  return declared ?? "";
1266
1284
  }
1285
+ /**
1286
+ * Formats a handle ID for display in the UI.
1287
+ * For handles with format "prefix:middle:suffix:extra" (4 parts), displays only the middle part.
1288
+ * Otherwise returns the handle ID as-is.
1289
+ */
1290
+ function prettyHandle(id) {
1291
+ try {
1292
+ const parts = String(id).split(":");
1293
+ // If there are exactly 3 colons (4 parts), display only the second part
1294
+ if (parts.length === 4)
1295
+ return parts[1] || id;
1296
+ return id;
1297
+ }
1298
+ catch {
1299
+ return id;
1300
+ }
1301
+ }
1267
1302
  // Pre-format common structures for display; return undefined to defer to caller
1268
1303
  function preformatValueForDisplay(typeId, value, registry) {
1269
1304
  if (value === undefined || value === null)
@@ -2230,6 +2265,7 @@ function IssueBadge({ level, title, size = 12, className, }) {
2230
2265
  function DebugEvents({ autoScroll, onAutoScrollChange, hideWorkbench, onHideWorkbenchChange, }) {
2231
2266
  const { events, clearEvents } = useWorkbenchContext();
2232
2267
  const scrollRef = React.useRef(null);
2268
+ const [copied, setCopied] = React.useState(false);
2233
2269
  const rows = React.useMemo(() => {
2234
2270
  const filtered = hideWorkbench
2235
2271
  ? events.filter((e) => e.source !== "workbench")
@@ -2253,7 +2289,25 @@ function DebugEvents({ autoScroll, onAutoScrollChange, hideWorkbench, onHideWork
2253
2289
  return String(v);
2254
2290
  }
2255
2291
  };
2256
- return (jsxRuntime.jsxs("div", { className: "flex flex-col h-full min-h-0", children: [jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-1", children: [jsxRuntime.jsx("div", { className: "font-semibold", children: "Events" }), jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [jsxRuntime.jsxs("label", { className: "flex items-center gap-1 text-xs text-gray-700", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: hideWorkbench, onChange: (e) => onHideWorkbenchChange?.(e.target.checked) }), jsxRuntime.jsx("span", { children: "Hide workbench" })] }), jsxRuntime.jsxs("label", { className: "flex items-center gap-1 text-xs text-gray-700", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: autoScroll, onChange: (e) => onAutoScrollChange?.(e.target.checked) }), jsxRuntime.jsx("span", { children: "Auto scroll" })] }), jsxRuntime.jsx("button", { onClick: clearEvents, className: "text-xs px-2 py-0.5 border border-gray-300 rounded", children: "Clear" })] })] }), jsxRuntime.jsx("div", { ref: scrollRef, className: "flex-1 overflow-auto text-[11px] leading-4 divide-y divide-gray-200", children: rows.map((ev) => (jsxRuntime.jsxs("div", { className: "opacity-85 odd:bg-gray-50 px-2 py-1", children: [jsxRuntime.jsxs("div", { className: "flex items-baseline gap-2", children: [jsxRuntime.jsx("span", { className: "w-12 shrink-0 text-right text-gray-500 select-none", children: ev.no }), jsxRuntime.jsxs("span", { className: "text-gray-500", children: [new Date(ev.at).toLocaleTimeString(), " \u00B7 ", ev.source, ":", ev.type] })] }), jsxRuntime.jsx("pre", { className: "m-0 whitespace-pre-wrap ml-12", children: renderPayload(ev.payload) })] }, `${ev.at}:${ev.no}`))) })] }));
2292
+ const handleCopyLogs = async () => {
2293
+ try {
2294
+ const formattedEvents = rows.map((ev) => ({
2295
+ no: ev.no,
2296
+ at: ev.at,
2297
+ source: ev.source,
2298
+ type: ev.type,
2299
+ payload: summarizeDeep(ev.payload),
2300
+ }));
2301
+ const jsonString = JSON.stringify(formattedEvents, null, 2);
2302
+ await navigator.clipboard.writeText(jsonString);
2303
+ setCopied(true);
2304
+ setTimeout(() => setCopied(false), 2000);
2305
+ }
2306
+ catch (err) {
2307
+ console.error("Failed to copy logs:", err);
2308
+ }
2309
+ };
2310
+ return (jsxRuntime.jsxs("div", { className: "flex flex-col h-full min-h-0", children: [jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-1", children: [jsxRuntime.jsx("div", { className: "font-semibold", children: "Events" }), jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [jsxRuntime.jsxs("label", { className: "flex items-center gap-1 text-xs text-gray-700", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: hideWorkbench, onChange: (e) => onHideWorkbenchChange?.(e.target.checked) }), jsxRuntime.jsx("span", { children: "Hide workbench" })] }), jsxRuntime.jsxs("label", { className: "flex items-center gap-1 text-xs text-gray-700", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: autoScroll, onChange: (e) => onAutoScrollChange?.(e.target.checked) }), jsxRuntime.jsx("span", { children: "Auto scroll" })] }), jsxRuntime.jsx("button", { onClick: handleCopyLogs, className: "p-2 border border-gray-300 rounded flex items-center justify-center", title: copied ? "Copied!" : "Copy logs as formatted JSON", children: jsxRuntime.jsx(react$1.CopyIcon, { size: 14 }) }), jsxRuntime.jsx("button", { onClick: clearEvents, className: "p-2 border border-gray-300 rounded flex items-center justify-center", title: "Clear all events", children: jsxRuntime.jsx(react$1.TrashIcon, { size: 14 }) })] })] }), jsxRuntime.jsx("div", { ref: scrollRef, className: "flex-1 overflow-auto text-[11px] leading-4 divide-y divide-gray-200", children: rows.map((ev) => (jsxRuntime.jsxs("div", { className: "opacity-85 odd:bg-gray-50 px-2 py-1", children: [jsxRuntime.jsxs("div", { className: "flex items-baseline gap-2", children: [jsxRuntime.jsx("span", { className: "w-12 shrink-0 text-right text-gray-500 select-none", children: ev.no }), jsxRuntime.jsxs("span", { className: "text-gray-500", children: [new Date(ev.at).toLocaleTimeString(), " \u00B7 ", ev.source, ":", ev.type] })] }), jsxRuntime.jsx("pre", { className: "m-0 whitespace-pre-wrap ml-12", children: renderPayload(ev.payload) })] }, `${ev.at}:${ev.no}`))) })] }));
2257
2311
  }
2258
2312
 
2259
2313
  function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHideWorkbenchChange, toString, toElement, contextPanel, setInput, }) {
@@ -2275,13 +2329,17 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
2275
2329
  const globalValidationIssues = validationGlobal;
2276
2330
  const selectedNode = def.nodes.find((n) => n.nodeId === selectedNodeId);
2277
2331
  const selectedEdge = def.edges.find((e) => e.id === selectedEdgeId);
2278
- const selectedDesc = selectedNode
2332
+ selectedNode
2279
2333
  ? registry.nodes.get(selectedNode.typeId)
2280
2334
  : undefined;
2281
- const inputHandles = Object.entries(selectedDesc?.inputs ?? {})
2282
- .filter(([k]) => !sparkGraph.isInputPrivate(selectedDesc?.inputs, k))
2335
+ // Use computeEffectiveHandles to merge registry defaults with dynamically resolved handles
2336
+ const effectiveHandles = selectedNode
2337
+ ? computeEffectiveHandles(selectedNode, registry)
2338
+ : { inputs: {}, outputs: {}};
2339
+ const inputHandles = Object.entries(effectiveHandles.inputs)
2340
+ .filter(([k]) => !sparkGraph.isInputPrivate(effectiveHandles.inputs, k))
2283
2341
  .map(([k]) => k);
2284
- const outputHandles = Object.keys(selectedDesc?.outputs ?? {});
2342
+ const outputHandles = Object.keys(effectiveHandles.outputs);
2285
2343
  const nodeInputs = selectedNodeId ? inputsMap[selectedNodeId] ?? {} : {};
2286
2344
  const nodeOutputs = selectedNodeId ? outputsMap[selectedNodeId] ?? {} : {};
2287
2345
  const selectedNodeStatus = selectedNodeId
@@ -2322,12 +2380,11 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
2322
2380
  }
2323
2381
  return;
2324
2382
  }
2325
- const desc = selectedDesc;
2326
- const handles = Object.keys(desc?.inputs ?? {});
2383
+ const handles = Object.keys(effectiveHandles.inputs);
2327
2384
  const nextDrafts = { ...drafts };
2328
2385
  const nextOriginals = { ...originals };
2329
2386
  for (const h of handles) {
2330
- const typeId = sparkGraph.getInputTypeId(desc?.inputs, h);
2387
+ const typeId = sparkGraph.getInputTypeId(effectiveHandles.inputs, h);
2331
2388
  const current = nodeInputs[h];
2332
2389
  const display = safeToString(typeId, current);
2333
2390
  const wasOriginal = originals[h];
@@ -2343,7 +2400,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
2343
2400
  setDrafts(nextDrafts);
2344
2401
  if (!shallowEqual(originals, nextOriginals))
2345
2402
  setOriginals(nextOriginals);
2346
- }, [selectedNodeId, selectedDesc, valuesTick]);
2403
+ }, [selectedNodeId, selectedNode, registry, valuesTick]);
2347
2404
  const widthClass = debug ? "w-[480px]" : "w-[320px]";
2348
2405
  const { wb } = useWorkbenchContext();
2349
2406
  const deleteEdgeById = (edgeId) => {
@@ -2369,7 +2426,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
2369
2426
  deleteEdgeById(selectedEdge.id);
2370
2427
  }, 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?.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 ??
2371
2428
  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) => {
2372
- const typeId = sparkGraph.getInputTypeId(selectedDesc?.inputs, h);
2429
+ const typeId = sparkGraph.getInputTypeId(effectiveHandles.inputs, h);
2373
2430
  const isLinked = def.edges.some((e) => e.target.nodeId === selectedNodeId &&
2374
2431
  e.target.handle === h);
2375
2432
  const commonProps = {
@@ -2397,7 +2454,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
2397
2454
  const title = inIssues
2398
2455
  .map((v) => `${v.code}: ${v.message}`)
2399
2456
  .join("; ");
2400
- 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: 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
2457
+ 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
2401
2458
  ? String(current)
2402
2459
  : "", onChange: (e) => {
2403
2460
  const val = e.target.value;
@@ -2413,8 +2470,8 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
2413
2470
  if (e.key === "Escape")
2414
2471
  revert();
2415
2472
  }, ...commonProps }))] }, h));
2416
- }))] }), 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: h }), jsxRuntime.jsx("span", { className: "text-gray-500 text-[11px]", children: outputTypesMap[selectedNodeId]?.[h] ?? "" })] }), jsxRuntime.jsx("div", { className: "flex-1", children: (() => {
2417
- const { typeId, value } = resolveOutputDisplay(nodeOutputs[h], selectedDesc?.outputs?.[h]);
2473
+ }))] }), 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: (() => {
2474
+ const { typeId, value } = resolveOutputDisplay(nodeOutputs[h], effectiveHandles.outputs[h]);
2418
2475
  return toElement(typeId, value);
2419
2476
  })() }), (() => {
2420
2477
  const outIssues = selectedNodeHandleValidation.outputs.filter((m) => m.handle === h);
@@ -2558,18 +2615,6 @@ function DefaultNodeHeader({ id, title, validation, right, showId, onInvalidate,
2558
2615
  }
2559
2616
  function DefaultNodeContent({ data, isConnectable, }) {
2560
2617
  const { showValues, inputValues, outputValues, toString } = data;
2561
- const prettyHandle = React.useCallback((id) => {
2562
- try {
2563
- const parts = String(id).split(":");
2564
- // If there are exactly 3 colons (4 parts), display only the second part
2565
- if (parts.length === 4)
2566
- return parts[1] || id;
2567
- return id;
2568
- }
2569
- catch {
2570
- return id;
2571
- }
2572
- }, []);
2573
2618
  const inputEntries = data.inputHandles ?? [];
2574
2619
  const outputEntries = data.outputHandles ?? [];
2575
2620
  const status = data.status ?? { activeRuns: 0 };
@@ -3741,6 +3786,7 @@ exports.formatDataUrlAsLabel = formatDataUrlAsLabel;
3741
3786
  exports.formatDeclaredTypeSignature = formatDeclaredTypeSignature;
3742
3787
  exports.getNodeBorderClassNames = getNodeBorderClassNames;
3743
3788
  exports.preformatValueForDisplay = preformatValueForDisplay;
3789
+ exports.prettyHandle = prettyHandle;
3744
3790
  exports.resolveOutputDisplay = resolveOutputDisplay;
3745
3791
  exports.summarizeDeep = summarizeDeep;
3746
3792
  exports.toReactFlow = toReactFlow;