@bian-womp/spark-workbench 0.2.22 → 0.2.23
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 +146 -47
- package/lib/cjs/index.cjs.map +1 -1
- package/lib/cjs/src/misc/NodeHandles.d.ts.map +1 -1
- package/lib/cjs/src/misc/mapping.d.ts +1 -0
- package/lib/cjs/src/misc/mapping.d.ts.map +1 -1
- package/lib/cjs/src/runtime/LocalGraphRunner.d.ts.map +1 -1
- package/lib/esm/index.js +146 -47
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/src/misc/NodeHandles.d.ts.map +1 -1
- package/lib/esm/src/misc/mapping.d.ts +1 -0
- package/lib/esm/src/misc/mapping.d.ts.map +1 -1
- package/lib/esm/src/runtime/LocalGraphRunner.d.ts.map +1 -1
- package/package.json +4 -4
package/lib/cjs/index.cjs
CHANGED
|
@@ -534,13 +534,19 @@ class LocalGraphRunner extends AbstractGraphRunner {
|
|
|
534
534
|
const def = undefined; // UI will supply def/positions on download for local
|
|
535
535
|
const inputs = this.getInputs(this.runtime
|
|
536
536
|
? {
|
|
537
|
-
nodes: Array.from(this.runtime.getNodeIds()).map((id) => ({
|
|
537
|
+
nodes: Array.from(this.runtime.getNodeIds()).map((id) => ({
|
|
538
|
+
nodeId: id,
|
|
539
|
+
typeId: "",
|
|
540
|
+
})),
|
|
538
541
|
edges: [],
|
|
539
542
|
}
|
|
540
543
|
: { nodes: [], edges: [] });
|
|
541
544
|
const outputs = this.getOutputs(this.runtime
|
|
542
545
|
? {
|
|
543
|
-
nodes: Array.from(this.runtime.getNodeIds()).map((id) => ({
|
|
546
|
+
nodes: Array.from(this.runtime.getNodeIds()).map((id) => ({
|
|
547
|
+
nodeId: id,
|
|
548
|
+
typeId: "",
|
|
549
|
+
})),
|
|
544
550
|
edges: [],
|
|
545
551
|
}
|
|
546
552
|
: { nodes: [], edges: [] });
|
|
@@ -1261,18 +1267,44 @@ function layoutNode(args) {
|
|
|
1261
1267
|
}
|
|
1262
1268
|
|
|
1263
1269
|
function toReactFlow(def, positions, registry, opts) {
|
|
1270
|
+
const EDGE_STYLE_MISSING = { stroke: "#f59e0b", strokeWidth: 2 }; // amber-500
|
|
1264
1271
|
const EDGE_STYLE_ERROR = { stroke: "#ef4444", strokeWidth: 2 };
|
|
1265
1272
|
const EDGE_STYLE_RUNNING = { stroke: "#3b82f6" };
|
|
1266
|
-
|
|
1267
|
-
|
|
1273
|
+
// Build a map of valid handles per node up-front
|
|
1274
|
+
const validHandleMap = {};
|
|
1275
|
+
for (const n of def.nodes) {
|
|
1276
|
+
const { inputs, outputs } = computeEffectiveHandles(n, registry);
|
|
1277
|
+
const inputOrder = Object.keys(inputs).filter((k) => !sparkGraph.isInputPrivate(inputs, k));
|
|
1278
|
+
const outputOrder = Object.keys(outputs);
|
|
1279
|
+
validHandleMap[n.nodeId] = {
|
|
1280
|
+
inputs: new Set(inputOrder),
|
|
1281
|
+
outputs: new Set(outputOrder),
|
|
1282
|
+
};
|
|
1283
|
+
}
|
|
1284
|
+
// Track which inputs are connected (for UI) and which handles are missing (for layout)
|
|
1268
1285
|
const connectedInputs = {};
|
|
1286
|
+
const missingInputsByNode = {};
|
|
1287
|
+
const missingOutputsByNode = {};
|
|
1269
1288
|
for (const e of def.edges) {
|
|
1270
|
-
const
|
|
1271
|
-
const
|
|
1272
|
-
if (!connectedInputs[
|
|
1273
|
-
connectedInputs[
|
|
1274
|
-
connectedInputs[
|
|
1275
|
-
|
|
1289
|
+
const tgtId = e.target.nodeId;
|
|
1290
|
+
const tgtHandle = e.target.handle;
|
|
1291
|
+
if (!connectedInputs[tgtId])
|
|
1292
|
+
connectedInputs[tgtId] = new Set();
|
|
1293
|
+
connectedInputs[tgtId].add(tgtHandle);
|
|
1294
|
+
const tgtValid = !!validHandleMap[tgtId]?.inputs.has(tgtHandle);
|
|
1295
|
+
if (!tgtValid) {
|
|
1296
|
+
(missingInputsByNode[tgtId] || (missingInputsByNode[tgtId] = new Set())).add(tgtHandle);
|
|
1297
|
+
}
|
|
1298
|
+
const srcId = e.source.nodeId;
|
|
1299
|
+
const srcHandle = e.source.handle;
|
|
1300
|
+
const srcValid = !!validHandleMap[srcId]?.outputs.has(srcHandle);
|
|
1301
|
+
if (!srcValid) {
|
|
1302
|
+
(missingOutputsByNode[srcId] || (missingOutputsByNode[srcId] = new Set())).add(srcHandle);
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
// This map is still used later for certain checks; align with valid handles
|
|
1306
|
+
const nodeHandleMap = {};
|
|
1307
|
+
Object.assign(nodeHandleMap, validHandleMap);
|
|
1276
1308
|
const nodes = def.nodes.map((n) => {
|
|
1277
1309
|
const { inputs: inputSource, outputs: outputSource } = computeEffectiveHandles(n, registry);
|
|
1278
1310
|
const overrideSize = opts.getDefaultNodeSize?.(n.typeId);
|
|
@@ -1294,11 +1326,61 @@ function toReactFlow(def, positions, registry, opts) {
|
|
|
1294
1326
|
inputs: new Set(inputHandles.map((h) => h.id)),
|
|
1295
1327
|
outputs: new Set(outputHandles.map((h) => h.id)),
|
|
1296
1328
|
};
|
|
1297
|
-
//
|
|
1329
|
+
// Append placeholder entries for any missing handles (below valid ones)
|
|
1330
|
+
const baseLeftCount = geom.inputOrder.length;
|
|
1331
|
+
const baseRightCount = geom.outputOrder.length;
|
|
1332
|
+
const extraInputs = Array.from(missingInputsByNode[n.nodeId] || []);
|
|
1333
|
+
const extraOutputs = Array.from(missingOutputsByNode[n.nodeId] || []);
|
|
1334
|
+
const HEADER = NODE_HEADER_HEIGHT_PX;
|
|
1335
|
+
const ROW = NODE_ROW_HEIGHT_PX;
|
|
1336
|
+
const extraHandleLayoutLeft = extraInputs.map((id, i) => ({
|
|
1337
|
+
id,
|
|
1338
|
+
type: "target",
|
|
1339
|
+
position: react.Position.Left,
|
|
1340
|
+
y: HEADER + (baseLeftCount + i) * ROW + ROW / 2,
|
|
1341
|
+
missing: true,
|
|
1342
|
+
}));
|
|
1343
|
+
const extraHandleLayoutRight = extraOutputs.map((id, i) => ({
|
|
1344
|
+
id,
|
|
1345
|
+
type: "source",
|
|
1346
|
+
position: react.Position.Right,
|
|
1347
|
+
y: HEADER + (baseRightCount + i) * ROW + ROW / 2,
|
|
1348
|
+
missing: true,
|
|
1349
|
+
}));
|
|
1350
|
+
const handleLayout = [
|
|
1351
|
+
...geom.handleLayout,
|
|
1352
|
+
...extraHandleLayoutLeft,
|
|
1353
|
+
...extraHandleLayoutRight,
|
|
1354
|
+
];
|
|
1355
|
+
// Precompute handle bounds (including missing) so edges can render immediately
|
|
1356
|
+
const missingBoundsLeft = extraInputs.map((id, i) => ({
|
|
1357
|
+
id,
|
|
1358
|
+
type: "target",
|
|
1359
|
+
position: react.Position.Left,
|
|
1360
|
+
x: 0,
|
|
1361
|
+
y: HEADER + (baseLeftCount + i) * ROW,
|
|
1362
|
+
width: 1,
|
|
1363
|
+
height: ROW + 2,
|
|
1364
|
+
}));
|
|
1365
|
+
const missingBoundsRight = extraOutputs.map((id, i) => ({
|
|
1366
|
+
id,
|
|
1367
|
+
type: "source",
|
|
1368
|
+
position: react.Position.Right,
|
|
1369
|
+
x: geom.width - 1,
|
|
1370
|
+
y: HEADER + (baseRightCount + i) * ROW,
|
|
1371
|
+
width: 1,
|
|
1372
|
+
height: ROW + 2,
|
|
1373
|
+
}));
|
|
1374
|
+
const handles = [
|
|
1375
|
+
...geom.handles,
|
|
1376
|
+
...missingBoundsLeft,
|
|
1377
|
+
...missingBoundsRight,
|
|
1378
|
+
];
|
|
1379
|
+
// Adjust node height to accommodate missing handle rows
|
|
1380
|
+
const baseRows = Math.max(baseLeftCount, baseRightCount);
|
|
1381
|
+
const newRows = Math.max(baseLeftCount + extraInputs.length, baseRightCount + extraOutputs.length);
|
|
1298
1382
|
const initialWidth = geom.width;
|
|
1299
|
-
const initialHeight = geom.height;
|
|
1300
|
-
// Precompute handle bounds so edges can render immediately without waiting for measurement
|
|
1301
|
-
const handles = geom.handles;
|
|
1383
|
+
const initialHeight = geom.height + Math.max(0, newRows - baseRows) * ROW;
|
|
1302
1384
|
return {
|
|
1303
1385
|
id: n.nodeId,
|
|
1304
1386
|
data: {
|
|
@@ -1310,7 +1392,7 @@ function toReactFlow(def, positions, registry, opts) {
|
|
|
1310
1392
|
h.id,
|
|
1311
1393
|
!!connectedInputs[n.nodeId]?.has(h.id),
|
|
1312
1394
|
])),
|
|
1313
|
-
handleLayout
|
|
1395
|
+
handleLayout,
|
|
1314
1396
|
showValues: opts.showValues,
|
|
1315
1397
|
renderWidth: initialWidth,
|
|
1316
1398
|
renderHeight: initialHeight,
|
|
@@ -1337,24 +1419,21 @@ function toReactFlow(def, positions, registry, opts) {
|
|
|
1337
1419
|
height: initialHeight,
|
|
1338
1420
|
};
|
|
1339
1421
|
});
|
|
1340
|
-
const edges = def.edges
|
|
1341
|
-
.filter((e) => {
|
|
1342
|
-
const src = nodeHandleMap[e.source.nodeId];
|
|
1343
|
-
const dst = nodeHandleMap[e.target.nodeId];
|
|
1344
|
-
if (!src || !dst)
|
|
1345
|
-
return false;
|
|
1346
|
-
return (src.outputs.has(e.source.handle) && dst.inputs.has(e.target.handle));
|
|
1347
|
-
})
|
|
1348
|
-
.map((e) => {
|
|
1422
|
+
const edges = def.edges.map((e) => {
|
|
1349
1423
|
const st = opts.edgeStatus?.[e.id];
|
|
1350
1424
|
const isRunning = !!st?.activeRuns;
|
|
1351
1425
|
const hasError = !!st?.lastError;
|
|
1352
1426
|
const isInvalidEdge = !!opts.edgeValidation?.[e.id];
|
|
1353
|
-
const
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1427
|
+
const sourceMissing = !validHandleMap[e.source.nodeId]?.outputs.has(e.source.handle);
|
|
1428
|
+
const targetMissing = !validHandleMap[e.target.nodeId]?.inputs.has(e.target.handle);
|
|
1429
|
+
const isMissing = sourceMissing || targetMissing;
|
|
1430
|
+
const style = isMissing
|
|
1431
|
+
? EDGE_STYLE_MISSING
|
|
1432
|
+
: hasError || isInvalidEdge
|
|
1433
|
+
? EDGE_STYLE_ERROR
|
|
1434
|
+
: isRunning
|
|
1435
|
+
? EDGE_STYLE_RUNNING
|
|
1436
|
+
: undefined;
|
|
1358
1437
|
return {
|
|
1359
1438
|
id: e.id,
|
|
1360
1439
|
source: e.source.nodeId,
|
|
@@ -1364,7 +1443,7 @@ function toReactFlow(def, positions, registry, opts) {
|
|
|
1364
1443
|
selected: opts.selectedEdgeIds
|
|
1365
1444
|
? opts.selectedEdgeIds.has(e.id)
|
|
1366
1445
|
: undefined,
|
|
1367
|
-
animated: isRunning,
|
|
1446
|
+
animated: isRunning && !isMissing,
|
|
1368
1447
|
style,
|
|
1369
1448
|
label: e.typeId || undefined,
|
|
1370
1449
|
};
|
|
@@ -2161,6 +2240,17 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
|
|
|
2161
2240
|
}, title: "Delete referenced edge", children: "Delete edge" }))] }, i))) })] }))] })) }), debug && (jsxRuntime.jsx("div", { className: "mt-3 flex-none min-h-0 h-[50%]", children: jsxRuntime.jsx(DebugEvents, { autoScroll: !!autoScroll, hideWorkbench: !!hideWorkbench, onAutoScrollChange: onAutoScrollChange, onHideWorkbenchChange: onHideWorkbenchChange }) }))] }));
|
|
2162
2241
|
}
|
|
2163
2242
|
|
|
2243
|
+
function NodeHandleItem({ kind, id, type, position, y, isConnectable, className, labelClassName, renderLabel, }) {
|
|
2244
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(react.Handle, { id: id, type: type, position: position, isConnectable: isConnectable, className: className, style: y !== undefined ? { top: y } : undefined }), renderLabel && (jsxRuntime.jsx("div", { className: labelClassName + (kind === "input" ? " left-2" : " right-2"), style: {
|
|
2245
|
+
top: (y ?? 0) - 8,
|
|
2246
|
+
...(kind === "input"
|
|
2247
|
+
? { right: "50%" }
|
|
2248
|
+
: { left: "50%", textAlign: "right" }),
|
|
2249
|
+
whiteSpace: "nowrap",
|
|
2250
|
+
overflow: "hidden",
|
|
2251
|
+
textOverflow: "ellipsis",
|
|
2252
|
+
}, children: renderLabel({ kind, id }) }))] }));
|
|
2253
|
+
}
|
|
2164
2254
|
function NodeHandles({ data, isConnectable, inputClassName = "!w-2 !h-2 !bg-gray-600", outputClassName = "!w-2 !h-2 !bg-gray-600", getClassName, renderLabel, labelClassName = "absolute text-[11px] text-gray-700 dark:text-gray-300 pointer-events-none", }) {
|
|
2165
2255
|
const layout = data.handleLayout ?? [];
|
|
2166
2256
|
const byId = React.useMemo(() => {
|
|
@@ -2171,40 +2261,49 @@ function NodeHandles({ data, isConnectable, inputClassName = "!w-2 !h-2 !bg-gray
|
|
|
2171
2261
|
position: h.position,
|
|
2172
2262
|
y: h.y,
|
|
2173
2263
|
type: h.type,
|
|
2264
|
+
missing: h.missing,
|
|
2174
2265
|
});
|
|
2175
2266
|
// Back-compat: also store by id-only if not already set
|
|
2176
2267
|
if (!m.has(h.id))
|
|
2177
|
-
m.set(h.id, {
|
|
2268
|
+
m.set(h.id, {
|
|
2269
|
+
position: h.position,
|
|
2270
|
+
y: h.y,
|
|
2271
|
+
type: h.type,
|
|
2272
|
+
missing: h.missing,
|
|
2273
|
+
});
|
|
2178
2274
|
}
|
|
2179
2275
|
return m;
|
|
2180
2276
|
}, [layout]);
|
|
2277
|
+
const inputIds = React.useMemo(() => new Set((data.inputHandles ?? []).map((h) => h.id)), [data.inputHandles]);
|
|
2278
|
+
const outputIds = React.useMemo(() => new Set((data.outputHandles ?? []).map((h) => h.id)), [data.outputHandles]);
|
|
2279
|
+
const missingInputs = React.useMemo(() => (layout || []).filter((h) => h.type === "target" && (!inputIds.has(h.id) || h.missing)), [layout, inputIds]);
|
|
2280
|
+
const missingOutputs = React.useMemo(() => (layout || []).filter((h) => h.type === "source" && (!outputIds.has(h.id) || h.missing)), [layout, outputIds]);
|
|
2181
2281
|
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [(data.inputHandles ?? []).map((h) => {
|
|
2182
2282
|
const placed = byId.get(`target:${h.id}`) ?? byId.get(h.id);
|
|
2183
2283
|
const position = placed?.position ?? react.Position.Left;
|
|
2184
2284
|
const y = placed?.y;
|
|
2185
2285
|
const cls = getClassName?.({ kind: "input", id: h.id, type: "target" }) ??
|
|
2186
2286
|
inputClassName;
|
|
2187
|
-
return (jsxRuntime.
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2287
|
+
return (jsxRuntime.jsx(NodeHandleItem, { kind: "input", id: h.id, type: "target", position: position, y: y, isConnectable: isConnectable, className: cls, labelClassName: labelClassName, renderLabel: renderLabel }, h.id));
|
|
2288
|
+
}), missingInputs.map((h) => {
|
|
2289
|
+
const key = `missing-input:${h.id}`;
|
|
2290
|
+
const position = h.position ?? react.Position.Left;
|
|
2291
|
+
const y = h.y;
|
|
2292
|
+
const cls = "!w-3 !h-3 !bg-amber-400 !border-amber-500";
|
|
2293
|
+
return (jsxRuntime.jsx(NodeHandleItem, { kind: "input", id: h.id, type: "target", position: position, y: y, isConnectable: false, className: cls, labelClassName: labelClassName, renderLabel: renderLabel }, key));
|
|
2194
2294
|
}), (data.outputHandles ?? []).map((h) => {
|
|
2195
2295
|
const placed = byId.get(`source:${h.id}`) ?? byId.get(h.id);
|
|
2196
2296
|
const position = placed?.position ?? react.Position.Right;
|
|
2197
2297
|
const y = placed?.y;
|
|
2198
2298
|
const cls = getClassName?.({ kind: "output", id: h.id, type: "source" }) ??
|
|
2199
2299
|
outputClassName;
|
|
2200
|
-
return (jsxRuntime.
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
}, children: renderLabel({ kind: "output", id: h.id }) }))] }, h.id));
|
|
2300
|
+
return (jsxRuntime.jsx(NodeHandleItem, { kind: "output", id: h.id, type: "source", position: position, y: y, isConnectable: isConnectable, className: `${cls} wb-nodrag wb-nowheel`, labelClassName: labelClassName, renderLabel: renderLabel }, h.id));
|
|
2301
|
+
}), missingOutputs.map((h) => {
|
|
2302
|
+
const key = `missing-output:${h.id}`;
|
|
2303
|
+
const position = h.position ?? react.Position.Right;
|
|
2304
|
+
const y = h.y;
|
|
2305
|
+
const cls = "!w-3 !h-3 !bg-amber-400 !border-amber-500 !rounded-none wb-nodrag wb-nowheel";
|
|
2306
|
+
return (jsxRuntime.jsx(NodeHandleItem, { kind: "output", id: h.id, type: "source", position: position, y: y, isConnectable: false, className: cls, labelClassName: labelClassName, renderLabel: renderLabel }, key));
|
|
2208
2307
|
})] }));
|
|
2209
2308
|
}
|
|
2210
2309
|
|
|
@@ -2297,7 +2396,7 @@ function DefaultNodeContent({ data, isConnectable, }) {
|
|
|
2297
2396
|
const entries = kind === "input" ? inputEntries : outputEntries;
|
|
2298
2397
|
const entry = entries.find((e) => e.id === handleId);
|
|
2299
2398
|
if (!entry)
|
|
2300
|
-
return handleId;
|
|
2399
|
+
return prettyHandle(handleId);
|
|
2301
2400
|
const vIssues = (kind === "input" ? validation.inputs : validation.outputs).filter((v) => v.handle === handleId);
|
|
2302
2401
|
const hasAny = vIssues.length > 0;
|
|
2303
2402
|
const hasErr = vIssues.some((v) => v.level === "error");
|