@bian-womp/spark-workbench 0.2.26 → 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 +159 -39
- 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/WorkbenchStudio.d.ts.map +1 -1
- package/lib/cjs/src/misc/context/WorkbenchContext.d.ts +7 -1
- package/lib/cjs/src/misc/context/WorkbenchContext.d.ts.map +1 -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 +160 -41
- 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/WorkbenchStudio.d.ts.map +1 -1
- package/lib/esm/src/misc/context/WorkbenchContext.d.ts +7 -1
- package/lib/esm/src/misc/context/WorkbenchContext.d.ts.map +1 -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)
|
|
@@ -1647,6 +1682,16 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, child
|
|
|
1647
1682
|
const [edgeStatus, setEdgeStatus] = React.useState({});
|
|
1648
1683
|
const [events, setEvents] = React.useState([]);
|
|
1649
1684
|
const clearEvents = React.useCallback(() => setEvents([]), []);
|
|
1685
|
+
const [systemErrors, setSystemErrors] = React.useState([]);
|
|
1686
|
+
const [registryErrors, setRegistryErrors] = React.useState([]);
|
|
1687
|
+
const clearSystemErrors = React.useCallback(() => setSystemErrors([]), []);
|
|
1688
|
+
const clearRegistryErrors = React.useCallback(() => setRegistryErrors([]), []);
|
|
1689
|
+
const removeSystemError = React.useCallback((index) => {
|
|
1690
|
+
setSystemErrors((prev) => prev.filter((_, idx) => idx !== index));
|
|
1691
|
+
}, []);
|
|
1692
|
+
const removeRegistryError = React.useCallback((index) => {
|
|
1693
|
+
setRegistryErrors((prev) => prev.filter((_, idx) => idx !== index));
|
|
1694
|
+
}, []);
|
|
1650
1695
|
// Fallback progress animation: drive progress to 100% over ~2 minutes
|
|
1651
1696
|
const FALLBACK_TOTAL_MS = 2 * 60 * 1000;
|
|
1652
1697
|
const [fallbackStarts, setFallbackStarts] = React.useState({});
|
|
@@ -1829,7 +1874,10 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, child
|
|
|
1829
1874
|
if (remoteDef && Array.isArray(remoteDef.nodes)) {
|
|
1830
1875
|
// Mutate current def in-place to avoid emitting graphChanged and causing update loop
|
|
1831
1876
|
const cur = wb.export();
|
|
1832
|
-
const byId = new Map((remoteDef.nodes || []).map((n) => [
|
|
1877
|
+
const byId = new Map((remoteDef.nodes || []).map((n) => [
|
|
1878
|
+
n.nodeId,
|
|
1879
|
+
n,
|
|
1880
|
+
]));
|
|
1833
1881
|
let changed = false;
|
|
1834
1882
|
for (const n of cur.nodes) {
|
|
1835
1883
|
const rn = byId.get(n.nodeId);
|
|
@@ -1861,6 +1909,8 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, child
|
|
|
1861
1909
|
const off2 = runner.on("error", (e) => {
|
|
1862
1910
|
const edgeError = e;
|
|
1863
1911
|
const nodeError = e;
|
|
1912
|
+
const registryError = e;
|
|
1913
|
+
const systemError = e;
|
|
1864
1914
|
if (edgeError.kind === "edge-convert") {
|
|
1865
1915
|
const edgeId = edgeError.edgeId;
|
|
1866
1916
|
setEdgeStatus((s) => ({
|
|
@@ -1868,7 +1918,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, child
|
|
|
1868
1918
|
[edgeId]: { ...s[edgeId], lastError: edgeError.err },
|
|
1869
1919
|
}));
|
|
1870
1920
|
}
|
|
1871
|
-
else if (nodeError.nodeId) {
|
|
1921
|
+
else if (nodeError.kind === "node-run" && nodeError.nodeId) {
|
|
1872
1922
|
const nodeId = nodeError.nodeId;
|
|
1873
1923
|
const runId = nodeError.runId;
|
|
1874
1924
|
setNodeStatus((s) => ({
|
|
@@ -1886,6 +1936,27 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, child
|
|
|
1886
1936
|
};
|
|
1887
1937
|
}
|
|
1888
1938
|
}
|
|
1939
|
+
else if (registryError.kind === "registry") {
|
|
1940
|
+
// Track registry errors for UI display
|
|
1941
|
+
setRegistryErrors((prev) => {
|
|
1942
|
+
// Avoid duplicates by checking message
|
|
1943
|
+
if (prev.some((err) => err.message === registryError.message)) {
|
|
1944
|
+
return prev;
|
|
1945
|
+
}
|
|
1946
|
+
return [...prev, registryError];
|
|
1947
|
+
});
|
|
1948
|
+
}
|
|
1949
|
+
else if (systemError.kind === "system") {
|
|
1950
|
+
// Track custom errors for UI display
|
|
1951
|
+
setSystemErrors((prev) => {
|
|
1952
|
+
// Avoid duplicates by checking message and code
|
|
1953
|
+
if (prev.some((err) => err.message === systemError.message &&
|
|
1954
|
+
err.code === systemError.code)) {
|
|
1955
|
+
return prev;
|
|
1956
|
+
}
|
|
1957
|
+
return [...prev, systemError];
|
|
1958
|
+
});
|
|
1959
|
+
}
|
|
1889
1960
|
return add("runner", "error")(e);
|
|
1890
1961
|
});
|
|
1891
1962
|
const off3 = runner.on("invalidate", (e) => {
|
|
@@ -2133,6 +2204,12 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, child
|
|
|
2133
2204
|
validationGlobal,
|
|
2134
2205
|
events,
|
|
2135
2206
|
clearEvents,
|
|
2207
|
+
systemErrors,
|
|
2208
|
+
registryErrors,
|
|
2209
|
+
clearSystemErrors,
|
|
2210
|
+
clearRegistryErrors,
|
|
2211
|
+
removeSystemError,
|
|
2212
|
+
removeRegistryError,
|
|
2136
2213
|
isRunning,
|
|
2137
2214
|
engineKind,
|
|
2138
2215
|
start,
|
|
@@ -2154,6 +2231,12 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, child
|
|
|
2154
2231
|
nodeStatus,
|
|
2155
2232
|
edgeStatus,
|
|
2156
2233
|
valuesTick,
|
|
2234
|
+
systemErrors,
|
|
2235
|
+
registryErrors,
|
|
2236
|
+
clearSystemErrors,
|
|
2237
|
+
clearRegistryErrors,
|
|
2238
|
+
removeSystemError,
|
|
2239
|
+
removeRegistryError,
|
|
2157
2240
|
inputsMap,
|
|
2158
2241
|
outputsMap,
|
|
2159
2242
|
validationByNode,
|
|
@@ -2182,6 +2265,7 @@ function IssueBadge({ level, title, size = 12, className, }) {
|
|
|
2182
2265
|
function DebugEvents({ autoScroll, onAutoScrollChange, hideWorkbench, onHideWorkbenchChange, }) {
|
|
2183
2266
|
const { events, clearEvents } = useWorkbenchContext();
|
|
2184
2267
|
const scrollRef = React.useRef(null);
|
|
2268
|
+
const [copied, setCopied] = React.useState(false);
|
|
2185
2269
|
const rows = React.useMemo(() => {
|
|
2186
2270
|
const filtered = hideWorkbench
|
|
2187
2271
|
? events.filter((e) => e.source !== "workbench")
|
|
@@ -2205,7 +2289,25 @@ function DebugEvents({ autoScroll, onAutoScrollChange, hideWorkbench, onHideWork
|
|
|
2205
2289
|
return String(v);
|
|
2206
2290
|
}
|
|
2207
2291
|
};
|
|
2208
|
-
|
|
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}`))) })] }));
|
|
2209
2311
|
}
|
|
2210
2312
|
|
|
2211
2313
|
function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHideWorkbenchChange, toString, toElement, contextPanel, setInput, }) {
|
|
@@ -2220,20 +2322,24 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
|
|
|
2220
2322
|
return String(value ?? "");
|
|
2221
2323
|
}
|
|
2222
2324
|
};
|
|
2223
|
-
const { registry, def, selectedNodeId, selectedEdgeId, inputsMap, outputsMap, outputTypesMap, nodeStatus, validationByNode, validationByEdge, validationGlobal, valuesTick, updateEdgeType, } = useWorkbenchContext();
|
|
2325
|
+
const { registry, def, selectedNodeId, selectedEdgeId, inputsMap, outputsMap, outputTypesMap, nodeStatus, validationByNode, validationByEdge, validationGlobal, valuesTick, updateEdgeType, systemErrors, registryErrors, clearSystemErrors, clearRegistryErrors, removeSystemError, removeRegistryError, } = useWorkbenchContext();
|
|
2224
2326
|
const nodeValidationIssues = validationByNode.issues;
|
|
2225
2327
|
const edgeValidationIssues = validationByEdge.issues;
|
|
2226
2328
|
const nodeValidationHandles = validationByNode;
|
|
2227
2329
|
const globalValidationIssues = validationGlobal;
|
|
2228
2330
|
const selectedNode = def.nodes.find((n) => n.nodeId === selectedNodeId);
|
|
2229
2331
|
const selectedEdge = def.edges.find((e) => e.id === selectedEdgeId);
|
|
2230
|
-
|
|
2332
|
+
selectedNode
|
|
2231
2333
|
? registry.nodes.get(selectedNode.typeId)
|
|
2232
2334
|
: undefined;
|
|
2233
|
-
|
|
2234
|
-
|
|
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))
|
|
2235
2341
|
.map(([k]) => k);
|
|
2236
|
-
const outputHandles = Object.keys(
|
|
2342
|
+
const outputHandles = Object.keys(effectiveHandles.outputs);
|
|
2237
2343
|
const nodeInputs = selectedNodeId ? inputsMap[selectedNodeId] ?? {} : {};
|
|
2238
2344
|
const nodeOutputs = selectedNodeId ? outputsMap[selectedNodeId] ?? {} : {};
|
|
2239
2345
|
const selectedNodeStatus = selectedNodeId
|
|
@@ -2274,12 +2380,11 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
|
|
|
2274
2380
|
}
|
|
2275
2381
|
return;
|
|
2276
2382
|
}
|
|
2277
|
-
const
|
|
2278
|
-
const handles = Object.keys(desc?.inputs ?? {});
|
|
2383
|
+
const handles = Object.keys(effectiveHandles.inputs);
|
|
2279
2384
|
const nextDrafts = { ...drafts };
|
|
2280
2385
|
const nextOriginals = { ...originals };
|
|
2281
2386
|
for (const h of handles) {
|
|
2282
|
-
const typeId = sparkGraph.getInputTypeId(
|
|
2387
|
+
const typeId = sparkGraph.getInputTypeId(effectiveHandles.inputs, h);
|
|
2283
2388
|
const current = nodeInputs[h];
|
|
2284
2389
|
const display = safeToString(typeId, current);
|
|
2285
2390
|
const wasOriginal = originals[h];
|
|
@@ -2295,7 +2400,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
|
|
|
2295
2400
|
setDrafts(nextDrafts);
|
|
2296
2401
|
if (!shallowEqual(originals, nextOriginals))
|
|
2297
2402
|
setOriginals(nextOriginals);
|
|
2298
|
-
}, [selectedNodeId,
|
|
2403
|
+
}, [selectedNodeId, selectedNode, registry, valuesTick]);
|
|
2299
2404
|
const widthClass = debug ? "w-[480px]" : "w-[320px]";
|
|
2300
2405
|
const { wb } = useWorkbenchContext();
|
|
2301
2406
|
const deleteEdgeById = (edgeId) => {
|
|
@@ -2306,7 +2411,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
|
|
|
2306
2411
|
}
|
|
2307
2412
|
catch { }
|
|
2308
2413
|
};
|
|
2309
|
-
return (jsxRuntime.jsxs("div", { className: `${widthClass} border-l border-gray-300 p-3 flex flex-col h-full min-h-0 overflow-hidden`, children: [contextPanel && jsxRuntime.jsx("div", { className: "mb-2", children: contextPanel }), jsxRuntime.jsx("div", { className: "font-semibold mb-2", children: "Inspector" }), jsxRuntime.jsxs("div", { className: "text-xs text-gray-500 mb-2", children: ["valuesTick: ", valuesTick] }), jsxRuntime.jsx("div", { className: "flex-1 overflow-auto", children: !selectedNode && !selectedEdge ? (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("div", { className: "text-gray-500", children: "Select a node or edge." }), globalValidationIssues && globalValidationIssues.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: globalValidationIssues.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}` }), !!m.data?.edgeId && (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) => {
|
|
2414
|
+
return (jsxRuntime.jsxs("div", { className: `${widthClass} border-l border-gray-300 p-3 flex flex-col h-full min-h-0 overflow-hidden`, children: [contextPanel && jsxRuntime.jsx("div", { className: "mb-2", children: contextPanel }), systemErrors.length > 0 && (jsxRuntime.jsxs("div", { className: "mb-2 space-y-1", children: [systemErrors.map((err, i) => (jsxRuntime.jsxs("div", { className: "text-xs text-red-700 bg-red-50 border border-red-200 rounded px-2 py-1 flex items-start justify-between gap-2", children: [jsxRuntime.jsxs("div", { className: "flex-1", children: [jsxRuntime.jsx("div", { className: "font-semibold", children: err.code ? `Error ${err.code}` : "Error" }), jsxRuntime.jsx("div", { className: "break-words", children: err.message })] }), jsxRuntime.jsx("button", { className: "text-red-500 hover:text-red-700 text-[10px] px-1", onClick: () => removeSystemError(i), title: "Dismiss", children: "\u00D7" })] }, i))), systemErrors.length > 1 && (jsxRuntime.jsx("button", { className: "text-xs text-red-600 hover:text-red-800 underline", onClick: clearSystemErrors, children: "Clear all" }))] })), registryErrors.length > 0 && (jsxRuntime.jsxs("div", { className: "mb-2 space-y-1", children: [registryErrors.map((err, i) => (jsxRuntime.jsxs("div", { className: "text-xs text-amber-700 bg-amber-50 border border-amber-200 rounded px-2 py-1 flex items-start justify-between gap-2", children: [jsxRuntime.jsxs("div", { className: "flex-1", children: [jsxRuntime.jsx("div", { className: "font-semibold", children: "Registry Error" }), jsxRuntime.jsx("div", { className: "break-words", children: err.message }), err.attempt && err.maxAttempts && (jsxRuntime.jsxs("div", { className: "text-[10px] text-amber-600 mt-1", children: ["Attempt ", err.attempt, " of ", err.maxAttempts] }))] }), jsxRuntime.jsx("button", { className: "text-amber-500 hover:text-amber-700 text-[10px] px-1", onClick: () => removeRegistryError(i), title: "Dismiss", children: "\u00D7" })] }, i))), registryErrors.length > 1 && (jsxRuntime.jsx("button", { className: "text-xs text-amber-600 hover:text-amber-800 underline", onClick: clearRegistryErrors, children: "Clear all" }))] })), jsxRuntime.jsx("div", { className: "font-semibold mb-2", children: "Inspector" }), jsxRuntime.jsxs("div", { className: "text-xs text-gray-500 mb-2", children: ["valuesTick: ", valuesTick] }), jsxRuntime.jsx("div", { className: "flex-1 overflow-auto", children: !selectedNode && !selectedEdge ? (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("div", { className: "text-gray-500", children: "Select a node or edge." }), globalValidationIssues && globalValidationIssues.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: globalValidationIssues.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}` }), !!m.data?.edgeId && (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) => {
|
|
2310
2415
|
e.stopPropagation();
|
|
2311
2416
|
deleteEdgeById(m.data?.edgeId);
|
|
2312
2417
|
}, title: "Delete referenced edge", children: "Delete edge" }))] }, i))) })] }))] })) : selectedEdge ? (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsxs("div", { className: "mb-2", children: [jsxRuntime.jsxs("div", { children: ["Edge: ", selectedEdge.id] }), jsxRuntime.jsxs("div", { children: [selectedEdge.source.nodeId, ".", selectedEdge.source.handle, " \u2192", " ", selectedEdge.target.nodeId, ".", selectedEdge.target.handle] }), jsxRuntime.jsx("div", { className: "mt-1", children: jsxRuntime.jsx("button", { className: "text-xs px-2 py-1 border border-red-300 rounded text-red-700 hover:bg-red-50", onClick: (e) => {
|
|
@@ -2321,7 +2426,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
|
|
|
2321
2426
|
deleteEdgeById(selectedEdge.id);
|
|
2322
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 ??
|
|
2323
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) => {
|
|
2324
|
-
const typeId = sparkGraph.getInputTypeId(
|
|
2429
|
+
const typeId = sparkGraph.getInputTypeId(effectiveHandles.inputs, h);
|
|
2325
2430
|
const isLinked = def.edges.some((e) => e.target.nodeId === selectedNodeId &&
|
|
2326
2431
|
e.target.handle === h);
|
|
2327
2432
|
const commonProps = {
|
|
@@ -2349,7 +2454,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
|
|
|
2349
2454
|
const title = inIssues
|
|
2350
2455
|
.map((v) => `${v.code}: ${v.message}`)
|
|
2351
2456
|
.join("; ");
|
|
2352
|
-
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
|
|
2353
2458
|
? String(current)
|
|
2354
2459
|
: "", onChange: (e) => {
|
|
2355
2460
|
const val = e.target.value;
|
|
@@ -2365,8 +2470,8 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
|
|
|
2365
2470
|
if (e.key === "Escape")
|
|
2366
2471
|
revert();
|
|
2367
2472
|
}, ...commonProps }))] }, h));
|
|
2368
|
-
}))] }), 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: (() => {
|
|
2369
|
-
const { typeId, value } = resolveOutputDisplay(nodeOutputs[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]);
|
|
2370
2475
|
return toElement(typeId, value);
|
|
2371
2476
|
})() }), (() => {
|
|
2372
2477
|
const outIssues = selectedNodeHandleValidation.outputs.filter((m) => m.handle === h);
|
|
@@ -2510,18 +2615,6 @@ function DefaultNodeHeader({ id, title, validation, right, showId, onInvalidate,
|
|
|
2510
2615
|
}
|
|
2511
2616
|
function DefaultNodeContent({ data, isConnectable, }) {
|
|
2512
2617
|
const { showValues, inputValues, outputValues, toString } = data;
|
|
2513
|
-
const prettyHandle = React.useCallback((id) => {
|
|
2514
|
-
try {
|
|
2515
|
-
const parts = String(id).split(":");
|
|
2516
|
-
// If there are exactly 3 colons (4 parts), display only the second part
|
|
2517
|
-
if (parts.length === 4)
|
|
2518
|
-
return parts[1] || id;
|
|
2519
|
-
return id;
|
|
2520
|
-
}
|
|
2521
|
-
catch {
|
|
2522
|
-
return id;
|
|
2523
|
-
}
|
|
2524
|
-
}, []);
|
|
2525
2618
|
const inputEntries = data.inputHandles ?? [];
|
|
2526
2619
|
const outputEntries = data.outputHandles ?? [];
|
|
2527
2620
|
const status = data.status ?? { activeRuns: 0 };
|
|
@@ -3156,11 +3249,8 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
3156
3249
|
return backendKind === "local";
|
|
3157
3250
|
});
|
|
3158
3251
|
// Expose init callback with setInitialGraph helper
|
|
3159
|
-
|
|
3252
|
+
// Note: This runs whenever runner changes (e.g., when Flow is enabled and backendOptions changes)
|
|
3160
3253
|
React.useEffect(() => {
|
|
3161
|
-
if (initCalled.current)
|
|
3162
|
-
return;
|
|
3163
|
-
initCalled.current = true;
|
|
3164
3254
|
if (!onInit)
|
|
3165
3255
|
return;
|
|
3166
3256
|
const setInitialGraph = async (d, inputs) => {
|
|
@@ -3606,7 +3696,19 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
3606
3696
|
function WorkbenchStudio({ engine, onEngineChange, example, onExampleChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, autoScroll, onAutoScrollChange, backendOptions, overrides, onInit, onChange, }) {
|
|
3607
3697
|
const [registry, setRegistry] = React.useState(sparkGraph.createSimpleGraphRegistry());
|
|
3608
3698
|
const [wb] = React.useState(() => new InMemoryWorkbench({ ui: new DefaultUIExtensionRegistry() }));
|
|
3699
|
+
// Store previous runner for cleanup
|
|
3700
|
+
const prevRunnerRef = React.useRef(null);
|
|
3609
3701
|
const runner = React.useMemo(() => {
|
|
3702
|
+
// Dispose previous runner if it exists
|
|
3703
|
+
if (prevRunnerRef.current) {
|
|
3704
|
+
try {
|
|
3705
|
+
prevRunnerRef.current.dispose();
|
|
3706
|
+
}
|
|
3707
|
+
catch (err) {
|
|
3708
|
+
console.warn("Error disposing previous runner:", err);
|
|
3709
|
+
}
|
|
3710
|
+
}
|
|
3711
|
+
let newRunner;
|
|
3610
3712
|
if (backendKind === "remote-http") {
|
|
3611
3713
|
const backend = {
|
|
3612
3714
|
kind: "remote-http",
|
|
@@ -3618,9 +3720,9 @@ function WorkbenchStudio({ engine, onEngineChange, example, onExampleChange, bac
|
|
|
3618
3720
|
onCustomEvent: backendOptions.onCustomEvent,
|
|
3619
3721
|
}),
|
|
3620
3722
|
};
|
|
3621
|
-
|
|
3723
|
+
newRunner = new RemoteGraphRunner(registry, backend);
|
|
3622
3724
|
}
|
|
3623
|
-
if (backendKind === "remote-ws") {
|
|
3725
|
+
else if (backendKind === "remote-ws") {
|
|
3624
3726
|
const backend = {
|
|
3625
3727
|
kind: "remote-ws",
|
|
3626
3728
|
url: wsUrl,
|
|
@@ -3631,10 +3733,27 @@ function WorkbenchStudio({ engine, onEngineChange, example, onExampleChange, bac
|
|
|
3631
3733
|
onCustomEvent: backendOptions.onCustomEvent,
|
|
3632
3734
|
}),
|
|
3633
3735
|
};
|
|
3634
|
-
|
|
3736
|
+
newRunner = new RemoteGraphRunner(registry, backend);
|
|
3635
3737
|
}
|
|
3636
|
-
|
|
3738
|
+
else {
|
|
3739
|
+
newRunner = new LocalGraphRunner(registry);
|
|
3740
|
+
}
|
|
3741
|
+
prevRunnerRef.current = newRunner;
|
|
3742
|
+
return newRunner;
|
|
3637
3743
|
}, [registry, backendKind, httpBaseUrl, wsUrl, backendOptions]);
|
|
3744
|
+
// Cleanup runner on unmount
|
|
3745
|
+
React.useEffect(() => {
|
|
3746
|
+
return () => {
|
|
3747
|
+
if (prevRunnerRef.current) {
|
|
3748
|
+
try {
|
|
3749
|
+
prevRunnerRef.current.dispose();
|
|
3750
|
+
}
|
|
3751
|
+
catch (err) {
|
|
3752
|
+
console.warn("Error disposing runner on unmount:", err);
|
|
3753
|
+
}
|
|
3754
|
+
}
|
|
3755
|
+
};
|
|
3756
|
+
}, []);
|
|
3638
3757
|
// Allow external UI registration (e.g., node renderers) with access to wb
|
|
3639
3758
|
React.useEffect(() => {
|
|
3640
3759
|
const baseRegisterUI = (_wb) => { };
|
|
@@ -3667,6 +3786,7 @@ exports.formatDataUrlAsLabel = formatDataUrlAsLabel;
|
|
|
3667
3786
|
exports.formatDeclaredTypeSignature = formatDeclaredTypeSignature;
|
|
3668
3787
|
exports.getNodeBorderClassNames = getNodeBorderClassNames;
|
|
3669
3788
|
exports.preformatValueForDisplay = preformatValueForDisplay;
|
|
3789
|
+
exports.prettyHandle = prettyHandle;
|
|
3670
3790
|
exports.resolveOutputDisplay = resolveOutputDisplay;
|
|
3671
3791
|
exports.summarizeDeep = summarizeDeep;
|
|
3672
3792
|
exports.toReactFlow = toReactFlow;
|