@bian-womp/spark-workbench 0.2.27 → 0.2.29
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 +111 -49
- package/lib/cjs/index.cjs.map +1 -1
- package/lib/cjs/src/misc/DebugEvents.d.ts.map +1 -1
- package/lib/cjs/src/misc/DefaultNode.d.ts.map +1 -1
- package/lib/cjs/src/misc/Inspector.d.ts.map +1 -1
- package/lib/cjs/src/misc/WorkbenchCanvas.d.ts.map +1 -1
- package/lib/cjs/src/misc/WorkbenchStudio.d.ts.map +1 -1
- package/lib/cjs/src/misc/context/WorkbenchContext.d.ts +1 -0
- package/lib/cjs/src/misc/context/WorkbenchContext.d.ts.map +1 -1
- package/lib/cjs/src/misc/context/WorkbenchContext.provider.d.ts +2 -1
- package/lib/cjs/src/misc/context/WorkbenchContext.provider.d.ts.map +1 -1
- package/lib/cjs/src/misc/value.d.ts +6 -0
- package/lib/cjs/src/misc/value.d.ts.map +1 -1
- package/lib/cjs/src/runtime/LocalGraphRunner.d.ts.map +1 -1
- package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
- package/lib/esm/index.js +112 -51
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/src/misc/DebugEvents.d.ts.map +1 -1
- package/lib/esm/src/misc/DefaultNode.d.ts.map +1 -1
- package/lib/esm/src/misc/Inspector.d.ts.map +1 -1
- package/lib/esm/src/misc/WorkbenchCanvas.d.ts.map +1 -1
- package/lib/esm/src/misc/WorkbenchStudio.d.ts.map +1 -1
- package/lib/esm/src/misc/context/WorkbenchContext.d.ts +1 -0
- package/lib/esm/src/misc/context/WorkbenchContext.d.ts.map +1 -1
- package/lib/esm/src/misc/context/WorkbenchContext.provider.d.ts +2 -1
- package/lib/esm/src/misc/context/WorkbenchContext.provider.d.ts.map +1 -1
- package/lib/esm/src/misc/value.d.ts +6 -0
- package/lib/esm/src/misc/value.d.ts.map +1 -1
- package/lib/esm/src/runtime/LocalGraphRunner.d.ts.map +1 -1
- package/lib/esm/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
- package/package.json +4 -4
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
|
-
|
|
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
|
-
|
|
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)
|
|
@@ -1642,7 +1677,7 @@ function useWorkbenchContext() {
|
|
|
1642
1677
|
return ctx;
|
|
1643
1678
|
}
|
|
1644
1679
|
|
|
1645
|
-
function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, children, }) {
|
|
1680
|
+
function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVersion, children, }) {
|
|
1646
1681
|
const [nodeStatus, setNodeStatus] = React.useState({});
|
|
1647
1682
|
const [edgeStatus, setEdgeStatus] = React.useState({});
|
|
1648
1683
|
const [events, setEvents] = React.useState([]);
|
|
@@ -2184,6 +2219,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, child
|
|
|
2184
2219
|
runAutoLayout,
|
|
2185
2220
|
updateEdgeType,
|
|
2186
2221
|
triggerExternal,
|
|
2222
|
+
uiVersion,
|
|
2187
2223
|
}), [
|
|
2188
2224
|
wb,
|
|
2189
2225
|
runner,
|
|
@@ -2218,6 +2254,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, child
|
|
|
2218
2254
|
runAutoLayout,
|
|
2219
2255
|
wb,
|
|
2220
2256
|
runner,
|
|
2257
|
+
uiVersion,
|
|
2221
2258
|
]);
|
|
2222
2259
|
return (jsxRuntime.jsx(WorkbenchContext.Provider, { value: value, children: children }));
|
|
2223
2260
|
}
|
|
@@ -2230,6 +2267,7 @@ function IssueBadge({ level, title, size = 12, className, }) {
|
|
|
2230
2267
|
function DebugEvents({ autoScroll, onAutoScrollChange, hideWorkbench, onHideWorkbenchChange, }) {
|
|
2231
2268
|
const { events, clearEvents } = useWorkbenchContext();
|
|
2232
2269
|
const scrollRef = React.useRef(null);
|
|
2270
|
+
const [copied, setCopied] = React.useState(false);
|
|
2233
2271
|
const rows = React.useMemo(() => {
|
|
2234
2272
|
const filtered = hideWorkbench
|
|
2235
2273
|
? events.filter((e) => e.source !== "workbench")
|
|
@@ -2253,7 +2291,25 @@ function DebugEvents({ autoScroll, onAutoScrollChange, hideWorkbench, onHideWork
|
|
|
2253
2291
|
return String(v);
|
|
2254
2292
|
}
|
|
2255
2293
|
};
|
|
2256
|
-
|
|
2294
|
+
const handleCopyLogs = async () => {
|
|
2295
|
+
try {
|
|
2296
|
+
const formattedEvents = rows.map((ev) => ({
|
|
2297
|
+
no: ev.no,
|
|
2298
|
+
at: ev.at,
|
|
2299
|
+
source: ev.source,
|
|
2300
|
+
type: ev.type,
|
|
2301
|
+
payload: summarizeDeep(ev.payload),
|
|
2302
|
+
}));
|
|
2303
|
+
const jsonString = JSON.stringify(formattedEvents, null, 2);
|
|
2304
|
+
await navigator.clipboard.writeText(jsonString);
|
|
2305
|
+
setCopied(true);
|
|
2306
|
+
setTimeout(() => setCopied(false), 2000);
|
|
2307
|
+
}
|
|
2308
|
+
catch (err) {
|
|
2309
|
+
console.error("Failed to copy logs:", err);
|
|
2310
|
+
}
|
|
2311
|
+
};
|
|
2312
|
+
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
2313
|
}
|
|
2258
2314
|
|
|
2259
2315
|
function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHideWorkbenchChange, toString, toElement, contextPanel, setInput, }) {
|
|
@@ -2275,13 +2331,17 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
|
|
|
2275
2331
|
const globalValidationIssues = validationGlobal;
|
|
2276
2332
|
const selectedNode = def.nodes.find((n) => n.nodeId === selectedNodeId);
|
|
2277
2333
|
const selectedEdge = def.edges.find((e) => e.id === selectedEdgeId);
|
|
2278
|
-
|
|
2334
|
+
selectedNode
|
|
2279
2335
|
? registry.nodes.get(selectedNode.typeId)
|
|
2280
2336
|
: undefined;
|
|
2281
|
-
|
|
2282
|
-
|
|
2337
|
+
// Use computeEffectiveHandles to merge registry defaults with dynamically resolved handles
|
|
2338
|
+
const effectiveHandles = selectedNode
|
|
2339
|
+
? computeEffectiveHandles(selectedNode, registry)
|
|
2340
|
+
: { inputs: {}, outputs: {}};
|
|
2341
|
+
const inputHandles = Object.entries(effectiveHandles.inputs)
|
|
2342
|
+
.filter(([k]) => !sparkGraph.isInputPrivate(effectiveHandles.inputs, k))
|
|
2283
2343
|
.map(([k]) => k);
|
|
2284
|
-
const outputHandles = Object.keys(
|
|
2344
|
+
const outputHandles = Object.keys(effectiveHandles.outputs);
|
|
2285
2345
|
const nodeInputs = selectedNodeId ? inputsMap[selectedNodeId] ?? {} : {};
|
|
2286
2346
|
const nodeOutputs = selectedNodeId ? outputsMap[selectedNodeId] ?? {} : {};
|
|
2287
2347
|
const selectedNodeStatus = selectedNodeId
|
|
@@ -2322,12 +2382,11 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
|
|
|
2322
2382
|
}
|
|
2323
2383
|
return;
|
|
2324
2384
|
}
|
|
2325
|
-
const
|
|
2326
|
-
const handles = Object.keys(desc?.inputs ?? {});
|
|
2385
|
+
const handles = Object.keys(effectiveHandles.inputs);
|
|
2327
2386
|
const nextDrafts = { ...drafts };
|
|
2328
2387
|
const nextOriginals = { ...originals };
|
|
2329
2388
|
for (const h of handles) {
|
|
2330
|
-
const typeId = sparkGraph.getInputTypeId(
|
|
2389
|
+
const typeId = sparkGraph.getInputTypeId(effectiveHandles.inputs, h);
|
|
2331
2390
|
const current = nodeInputs[h];
|
|
2332
2391
|
const display = safeToString(typeId, current);
|
|
2333
2392
|
const wasOriginal = originals[h];
|
|
@@ -2343,7 +2402,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
|
|
|
2343
2402
|
setDrafts(nextDrafts);
|
|
2344
2403
|
if (!shallowEqual(originals, nextOriginals))
|
|
2345
2404
|
setOriginals(nextOriginals);
|
|
2346
|
-
}, [selectedNodeId,
|
|
2405
|
+
}, [selectedNodeId, selectedNode, registry, valuesTick]);
|
|
2347
2406
|
const widthClass = debug ? "w-[480px]" : "w-[320px]";
|
|
2348
2407
|
const { wb } = useWorkbenchContext();
|
|
2349
2408
|
const deleteEdgeById = (edgeId) => {
|
|
@@ -2369,7 +2428,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
|
|
|
2369
2428
|
deleteEdgeById(selectedEdge.id);
|
|
2370
2429
|
}, 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
2430
|
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(
|
|
2431
|
+
const typeId = sparkGraph.getInputTypeId(effectiveHandles.inputs, h);
|
|
2373
2432
|
const isLinked = def.edges.some((e) => e.target.nodeId === selectedNodeId &&
|
|
2374
2433
|
e.target.handle === h);
|
|
2375
2434
|
const commonProps = {
|
|
@@ -2397,7 +2456,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
|
|
|
2397
2456
|
const title = inIssues
|
|
2398
2457
|
.map((v) => `${v.code}: ${v.message}`)
|
|
2399
2458
|
.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
|
|
2459
|
+
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
2460
|
? String(current)
|
|
2402
2461
|
: "", onChange: (e) => {
|
|
2403
2462
|
const val = e.target.value;
|
|
@@ -2413,8 +2472,8 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
|
|
|
2413
2472
|
if (e.key === "Escape")
|
|
2414
2473
|
revert();
|
|
2415
2474
|
}, ...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],
|
|
2475
|
+
}))] }), 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: (() => {
|
|
2476
|
+
const { typeId, value } = resolveOutputDisplay(nodeOutputs[h], effectiveHandles.outputs[h]);
|
|
2418
2477
|
return toElement(typeId, value);
|
|
2419
2478
|
})() }), (() => {
|
|
2420
2479
|
const outIssues = selectedNodeHandleValidation.outputs.filter((m) => m.handle === h);
|
|
@@ -2558,18 +2617,6 @@ function DefaultNodeHeader({ id, title, validation, right, showId, onInvalidate,
|
|
|
2558
2617
|
}
|
|
2559
2618
|
function DefaultNodeContent({ data, isConnectable, }) {
|
|
2560
2619
|
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
2620
|
const inputEntries = data.inputHandles ?? [];
|
|
2574
2621
|
const outputEntries = data.outputHandles ?? [];
|
|
2575
2622
|
const status = data.status ?? { activeRuns: 0 };
|
|
@@ -2902,7 +2949,7 @@ function NodeContextMenu({ open, clientPos, nodeId, onClose, }) {
|
|
|
2902
2949
|
}
|
|
2903
2950
|
|
|
2904
2951
|
const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, getDefaultNodeSize }, ref) => {
|
|
2905
|
-
const { wb, registry, inputsMap, outputsMap, valuesTick, nodeStatus, edgeStatus, validationByNode, validationByEdge, } = useWorkbenchContext();
|
|
2952
|
+
const { wb, registry, inputsMap, outputsMap, valuesTick, nodeStatus, edgeStatus, validationByNode, validationByEdge, uiVersion, } = useWorkbenchContext();
|
|
2906
2953
|
const nodeValidation = validationByNode;
|
|
2907
2954
|
const edgeValidation = validationByEdge.errors;
|
|
2908
2955
|
// Keep stable references for nodes/edges to avoid unnecessary updates
|
|
@@ -2972,8 +3019,13 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
2972
3019
|
const { nodeTypes, resolveNodeType } = React.useMemo(() => {
|
|
2973
3020
|
// Build nodeTypes map using UI extension registry
|
|
2974
3021
|
const ui = wb.getUI();
|
|
2975
|
-
const custom = new Map();
|
|
2976
|
-
|
|
3022
|
+
const custom = new Map(); // Include all types present in registry AND current graph to avoid timing issues
|
|
3023
|
+
const def = wb.export();
|
|
3024
|
+
const ids = new Set([
|
|
3025
|
+
...Array.from(registry.nodes.keys()),
|
|
3026
|
+
...def.nodes.map((n) => n.typeId),
|
|
3027
|
+
]);
|
|
3028
|
+
for (const typeId of ids) {
|
|
2977
3029
|
const renderer = ui.getNodeRenderer(typeId);
|
|
2978
3030
|
if (renderer)
|
|
2979
3031
|
custom.set(typeId, renderer);
|
|
@@ -2987,8 +3039,8 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
2987
3039
|
}
|
|
2988
3040
|
const resolver = (nodeTypeId) => custom.has(nodeTypeId) ? `spark-${nodeTypeId}` : "spark-default";
|
|
2989
3041
|
return { nodeTypes: types, resolveNodeType: resolver };
|
|
2990
|
-
//
|
|
2991
|
-
}, [wb, registry]);
|
|
3042
|
+
// Include uiVersion to recompute when custom renderers are registered
|
|
3043
|
+
}, [wb, registry, uiVersion]);
|
|
2992
3044
|
const { nodes, edges } = React.useMemo(() => {
|
|
2993
3045
|
const def = wb.export();
|
|
2994
3046
|
const sel = wb.getSelection();
|
|
@@ -3028,7 +3080,11 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
3028
3080
|
})
|
|
3029
3081
|
.map((n) => n.id);
|
|
3030
3082
|
// Detect handle updates (ids/length changes) for targeted debug
|
|
3031
|
-
const toIds = (arr) => Array.isArray(arr)
|
|
3083
|
+
const toIds = (arr) => Array.isArray(arr)
|
|
3084
|
+
? arr
|
|
3085
|
+
.map((h) => h && typeof h === "object" && "id" in h ? String(h.id) : "")
|
|
3086
|
+
.filter(Boolean)
|
|
3087
|
+
: [];
|
|
3032
3088
|
const handlesEqual = (a, b) => {
|
|
3033
3089
|
const aIds = toIds(a);
|
|
3034
3090
|
const bIds = toIds(b);
|
|
@@ -3654,15 +3710,6 @@ function WorkbenchStudio({ engine, onEngineChange, example, onExampleChange, bac
|
|
|
3654
3710
|
// Store previous runner for cleanup
|
|
3655
3711
|
const prevRunnerRef = React.useRef(null);
|
|
3656
3712
|
const runner = React.useMemo(() => {
|
|
3657
|
-
// Dispose previous runner if it exists
|
|
3658
|
-
if (prevRunnerRef.current) {
|
|
3659
|
-
try {
|
|
3660
|
-
prevRunnerRef.current.dispose();
|
|
3661
|
-
}
|
|
3662
|
-
catch (err) {
|
|
3663
|
-
console.warn("Error disposing previous runner:", err);
|
|
3664
|
-
}
|
|
3665
|
-
}
|
|
3666
3713
|
let newRunner;
|
|
3667
3714
|
if (backendKind === "remote-http") {
|
|
3668
3715
|
const backend = {
|
|
@@ -3693,29 +3740,43 @@ function WorkbenchStudio({ engine, onEngineChange, example, onExampleChange, bac
|
|
|
3693
3740
|
else {
|
|
3694
3741
|
newRunner = new LocalGraphRunner(registry);
|
|
3695
3742
|
}
|
|
3696
|
-
prevRunnerRef.current = newRunner;
|
|
3697
3743
|
return newRunner;
|
|
3698
3744
|
}, [registry, backendKind, httpBaseUrl, wsUrl, backendOptions]);
|
|
3699
|
-
//
|
|
3745
|
+
// Dispose previous runner after commit; dispose current on unmount
|
|
3700
3746
|
React.useEffect(() => {
|
|
3747
|
+
const previous = prevRunnerRef.current;
|
|
3748
|
+
prevRunnerRef.current = runner;
|
|
3749
|
+
if (previous && previous !== runner) {
|
|
3750
|
+
try {
|
|
3751
|
+
previous.dispose();
|
|
3752
|
+
}
|
|
3753
|
+
catch (err) {
|
|
3754
|
+
console.warn("Error disposing previous runner:", err);
|
|
3755
|
+
}
|
|
3756
|
+
}
|
|
3701
3757
|
return () => {
|
|
3702
|
-
if
|
|
3758
|
+
// Only dispose if this runner is still the current one
|
|
3759
|
+
if (prevRunnerRef.current === runner) {
|
|
3703
3760
|
try {
|
|
3704
|
-
|
|
3761
|
+
runner.dispose();
|
|
3705
3762
|
}
|
|
3706
3763
|
catch (err) {
|
|
3707
3764
|
console.warn("Error disposing runner on unmount:", err);
|
|
3708
3765
|
}
|
|
3709
3766
|
}
|
|
3710
3767
|
};
|
|
3711
|
-
}, []);
|
|
3768
|
+
}, [runner]);
|
|
3769
|
+
// Track UI registration version to trigger nodeTypes recomputation
|
|
3770
|
+
const [uiVersion, setUiVersion] = React.useState(0);
|
|
3712
3771
|
// Allow external UI registration (e.g., node renderers) with access to wb
|
|
3713
3772
|
React.useEffect(() => {
|
|
3714
3773
|
const baseRegisterUI = (_wb) => { };
|
|
3715
3774
|
overrides?.registerUI?.(baseRegisterUI, { wb, wbRunner: runner });
|
|
3775
|
+
// Increment UI version to trigger nodeTypes recomputation in WorkbenchCanvas
|
|
3776
|
+
setUiVersion((v) => v + 1);
|
|
3716
3777
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
3717
3778
|
}, [wb, runner, overrides]);
|
|
3718
|
-
return (jsxRuntime.jsx(WorkbenchProvider, { wb: wb, runner: runner, registry: registry, setRegistry: setRegistry, overrides: overrides, children: jsxRuntime.jsx(WorkbenchStudioCanvas, { setRegistry: setRegistry, autoScroll: autoScroll, onAutoScrollChange: onAutoScrollChange, example: example, onExampleChange: onExampleChange, engine: engine, onEngineChange: onEngineChange, backendKind: backendKind, onBackendKindChange: (v) => {
|
|
3779
|
+
return (jsxRuntime.jsx(WorkbenchProvider, { wb: wb, runner: runner, registry: registry, setRegistry: setRegistry, overrides: overrides, uiVersion: uiVersion, children: jsxRuntime.jsx(WorkbenchStudioCanvas, { setRegistry: setRegistry, autoScroll: autoScroll, onAutoScrollChange: onAutoScrollChange, example: example, onExampleChange: onExampleChange, engine: engine, onEngineChange: onEngineChange, backendKind: backendKind, onBackendKindChange: (v) => {
|
|
3719
3780
|
if (runner.isRunning())
|
|
3720
3781
|
runner.dispose();
|
|
3721
3782
|
onBackendKindChange(v);
|
|
@@ -3741,6 +3802,7 @@ exports.formatDataUrlAsLabel = formatDataUrlAsLabel;
|
|
|
3741
3802
|
exports.formatDeclaredTypeSignature = formatDeclaredTypeSignature;
|
|
3742
3803
|
exports.getNodeBorderClassNames = getNodeBorderClassNames;
|
|
3743
3804
|
exports.preformatValueForDisplay = preformatValueForDisplay;
|
|
3805
|
+
exports.prettyHandle = prettyHandle;
|
|
3744
3806
|
exports.resolveOutputDisplay = resolveOutputDisplay;
|
|
3745
3807
|
exports.summarizeDeep = summarizeDeep;
|
|
3746
3808
|
exports.toReactFlow = toReactFlow;
|