@bian-womp/spark-workbench 0.2.54 → 0.2.56
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 +340 -232
- package/lib/cjs/index.cjs.map +1 -1
- package/lib/cjs/src/core/AbstractWorkbench.d.ts +2 -0
- package/lib/cjs/src/core/AbstractWorkbench.d.ts.map +1 -1
- package/lib/cjs/src/core/InMemoryWorkbench.d.ts +0 -1
- package/lib/cjs/src/core/InMemoryWorkbench.d.ts.map +1 -1
- package/lib/cjs/src/core/ui-extensions.d.ts +44 -47
- package/lib/cjs/src/core/ui-extensions.d.ts.map +1 -1
- package/lib/cjs/src/misc/DefaultContextMenu.d.ts +2 -12
- package/lib/cjs/src/misc/DefaultContextMenu.d.ts.map +1 -1
- package/lib/cjs/src/misc/NodeContextMenu.d.ts +2 -9
- package/lib/cjs/src/misc/NodeContextMenu.d.ts.map +1 -1
- package/lib/cjs/src/misc/NodeHandles.d.ts +1 -3
- package/lib/cjs/src/misc/NodeHandles.d.ts.map +1 -1
- package/lib/cjs/src/misc/WorkbenchCanvas.d.ts.map +1 -1
- package/lib/cjs/src/misc/constants.d.ts +2 -1
- package/lib/cjs/src/misc/constants.d.ts.map +1 -1
- package/lib/cjs/src/misc/context/ContextMenuHandlers.d.ts +41 -0
- package/lib/cjs/src/misc/context/ContextMenuHandlers.d.ts.map +1 -0
- package/lib/cjs/src/misc/context/ContextMenuHelpers.d.ts +7 -0
- package/lib/cjs/src/misc/context/ContextMenuHelpers.d.ts.map +1 -0
- package/lib/cjs/src/misc/layout.d.ts +46 -0
- package/lib/cjs/src/misc/layout.d.ts.map +1 -1
- package/lib/cjs/src/misc/mapping.d.ts.map +1 -1
- package/lib/cjs/src/runtime/AbstractGraphRunner.d.ts +3 -2
- package/lib/cjs/src/runtime/AbstractGraphRunner.d.ts.map +1 -1
- package/lib/cjs/src/runtime/IGraphRunner.d.ts +1 -0
- package/lib/cjs/src/runtime/IGraphRunner.d.ts.map +1 -1
- package/lib/cjs/src/runtime/LocalGraphRunner.d.ts +1 -1
- package/lib/cjs/src/runtime/LocalGraphRunner.d.ts.map +1 -1
- package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts +0 -1
- package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
- package/lib/esm/index.js +337 -234
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/src/core/AbstractWorkbench.d.ts +2 -0
- package/lib/esm/src/core/AbstractWorkbench.d.ts.map +1 -1
- package/lib/esm/src/core/InMemoryWorkbench.d.ts +0 -1
- package/lib/esm/src/core/InMemoryWorkbench.d.ts.map +1 -1
- package/lib/esm/src/core/ui-extensions.d.ts +44 -47
- package/lib/esm/src/core/ui-extensions.d.ts.map +1 -1
- package/lib/esm/src/misc/DefaultContextMenu.d.ts +2 -12
- package/lib/esm/src/misc/DefaultContextMenu.d.ts.map +1 -1
- package/lib/esm/src/misc/NodeContextMenu.d.ts +2 -9
- package/lib/esm/src/misc/NodeContextMenu.d.ts.map +1 -1
- package/lib/esm/src/misc/NodeHandles.d.ts +1 -3
- package/lib/esm/src/misc/NodeHandles.d.ts.map +1 -1
- package/lib/esm/src/misc/WorkbenchCanvas.d.ts.map +1 -1
- package/lib/esm/src/misc/constants.d.ts +2 -1
- package/lib/esm/src/misc/constants.d.ts.map +1 -1
- package/lib/esm/src/misc/context/ContextMenuHandlers.d.ts +41 -0
- package/lib/esm/src/misc/context/ContextMenuHandlers.d.ts.map +1 -0
- package/lib/esm/src/misc/context/ContextMenuHelpers.d.ts +7 -0
- package/lib/esm/src/misc/context/ContextMenuHelpers.d.ts.map +1 -0
- package/lib/esm/src/misc/layout.d.ts +46 -0
- package/lib/esm/src/misc/layout.d.ts.map +1 -1
- package/lib/esm/src/misc/mapping.d.ts.map +1 -1
- package/lib/esm/src/runtime/AbstractGraphRunner.d.ts +3 -2
- package/lib/esm/src/runtime/AbstractGraphRunner.d.ts.map +1 -1
- package/lib/esm/src/runtime/IGraphRunner.d.ts +1 -0
- package/lib/esm/src/runtime/IGraphRunner.d.ts.map +1 -1
- package/lib/esm/src/runtime/LocalGraphRunner.d.ts +1 -1
- package/lib/esm/src/runtime/LocalGraphRunner.d.ts.map +1 -1
- package/lib/esm/src/runtime/RemoteGraphRunner.d.ts +0 -1
- package/lib/esm/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
- package/package.json +4 -4
package/lib/esm/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { GraphBuilder, createEngine, StepEngine, PullEngine, BatchedEngine, isTypedOutput, getTypedOutputValue, getTypedOutputTypeId, isInputPrivate, getInputTypeId, createSimpleGraphRegistry, createSimpleGraphDef, createAsyncGraphDef, createAsyncGraphRegistry, createProgressGraphDef, createProgressGraphRegistry, createValidationGraphDef, createValidationGraphRegistry } from '@bian-womp/spark-graph';
|
|
1
|
+
import { generateId, GraphBuilder, createEngine, StepEngine, PullEngine, BatchedEngine, isTypedOutput, getTypedOutputValue, getTypedOutputTypeId, isInputPrivate, getInputTypeId, createSimpleGraphRegistry, createSimpleGraphDef, createAsyncGraphDef, createAsyncGraphRegistry, createProgressGraphDef, createProgressGraphRegistry, createValidationGraphDef, createValidationGraphRegistry } from '@bian-womp/spark-graph';
|
|
2
2
|
import { RuntimeApiClient } from '@bian-womp/spark-remote';
|
|
3
3
|
import { Position, Handle, useUpdateNodeInternals, useReactFlow, ReactFlowProvider, ReactFlow, Background, BackgroundVariant, MiniMap, Controls } from '@xyflow/react';
|
|
4
4
|
import React, { useCallback, useState, useRef, useEffect, useMemo, createContext, useContext, useImperativeHandle } from 'react';
|
|
@@ -10,71 +10,75 @@ import isEqual from 'lodash/isEqual';
|
|
|
10
10
|
class DefaultUIExtensionRegistry {
|
|
11
11
|
constructor() {
|
|
12
12
|
this.nodeRenderers = new Map();
|
|
13
|
-
this.portRenderers = new Map();
|
|
14
|
-
this.edgeRenderers = new Map();
|
|
15
13
|
}
|
|
16
14
|
registerNodeRenderer(nodeTypeId, renderer) {
|
|
17
|
-
|
|
15
|
+
if (renderer === undefined) {
|
|
16
|
+
this.nodeRenderers.delete(nodeTypeId);
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
this.nodeRenderers.set(nodeTypeId, renderer);
|
|
20
|
+
}
|
|
18
21
|
return this;
|
|
19
22
|
}
|
|
20
23
|
getNodeRenderer(nodeTypeId) {
|
|
21
24
|
return this.nodeRenderers.get(nodeTypeId);
|
|
22
25
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
return
|
|
26
|
+
getAllNodeRenderers() {
|
|
27
|
+
const result = {};
|
|
28
|
+
for (const [nodeTypeId, renderer] of this.nodeRenderers.entries()) {
|
|
29
|
+
result[nodeTypeId] = renderer;
|
|
30
|
+
}
|
|
31
|
+
return result;
|
|
29
32
|
}
|
|
30
|
-
|
|
31
|
-
this.
|
|
33
|
+
registerIconProvider(provider) {
|
|
34
|
+
this.iconProvider = provider;
|
|
32
35
|
return this;
|
|
33
36
|
}
|
|
34
|
-
|
|
35
|
-
return this.
|
|
37
|
+
getIconProvider() {
|
|
38
|
+
return this.iconProvider;
|
|
36
39
|
}
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
// React Flow renderers
|
|
41
|
+
registerConnectionLineRenderer(renderer) {
|
|
42
|
+
this.connectionLineRenderer = renderer;
|
|
39
43
|
return this;
|
|
40
44
|
}
|
|
41
|
-
|
|
42
|
-
return this.
|
|
45
|
+
getConnectionLineRenderer() {
|
|
46
|
+
return this.connectionLineRenderer;
|
|
43
47
|
}
|
|
44
|
-
|
|
45
|
-
this.
|
|
48
|
+
registerMinimapRenderer(renderer) {
|
|
49
|
+
this.minimapRenderer = renderer;
|
|
46
50
|
return this;
|
|
47
51
|
}
|
|
48
|
-
|
|
49
|
-
return this.
|
|
52
|
+
getMinimapRenderer() {
|
|
53
|
+
return this.minimapRenderer;
|
|
50
54
|
}
|
|
51
|
-
|
|
52
|
-
this.
|
|
55
|
+
registerControlsRenderer(renderer) {
|
|
56
|
+
this.controlsRenderer = renderer;
|
|
53
57
|
return this;
|
|
54
58
|
}
|
|
55
|
-
|
|
56
|
-
return this.
|
|
59
|
+
getControlsRenderer() {
|
|
60
|
+
return this.controlsRenderer;
|
|
57
61
|
}
|
|
58
|
-
|
|
59
|
-
this.
|
|
62
|
+
registerBackgroundRenderer(renderer) {
|
|
63
|
+
this.backgroundRenderer = renderer;
|
|
60
64
|
return this;
|
|
61
65
|
}
|
|
62
|
-
|
|
63
|
-
return this.
|
|
66
|
+
getBackgroundRenderer() {
|
|
67
|
+
return this.backgroundRenderer;
|
|
64
68
|
}
|
|
65
|
-
|
|
66
|
-
this.
|
|
69
|
+
registerDefaultContextMenuRenderer(renderer) {
|
|
70
|
+
this.defaultContextMenuRenderer = renderer;
|
|
67
71
|
return this;
|
|
68
72
|
}
|
|
69
|
-
|
|
70
|
-
return this.
|
|
73
|
+
getDefaultContextMenuRenderer() {
|
|
74
|
+
return this.defaultContextMenuRenderer;
|
|
71
75
|
}
|
|
72
|
-
|
|
73
|
-
this.
|
|
76
|
+
registerNodeContextMenuRenderer(renderer) {
|
|
77
|
+
this.nodeContextMenuRenderer = renderer;
|
|
74
78
|
return this;
|
|
75
79
|
}
|
|
76
|
-
|
|
77
|
-
return this.
|
|
80
|
+
getNodeContextMenuRenderer() {
|
|
81
|
+
return this.nodeContextMenuRenderer;
|
|
78
82
|
}
|
|
79
83
|
}
|
|
80
84
|
|
|
@@ -84,6 +88,7 @@ class AbstractWorkbench {
|
|
|
84
88
|
this.layout = args.layout;
|
|
85
89
|
this.storage = args.storage;
|
|
86
90
|
this.serializer = args.serializer;
|
|
91
|
+
this.genId = args.genId || generateId;
|
|
87
92
|
}
|
|
88
93
|
// Expose UI registry to adapters (React Flow, CLI) to allow overrides
|
|
89
94
|
getUI() {
|
|
@@ -162,11 +167,14 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
162
167
|
return { ok: issues.every((i) => i.level !== "error"), issues };
|
|
163
168
|
}
|
|
164
169
|
addNode(node) {
|
|
165
|
-
const id = node.nodeId ??
|
|
170
|
+
const id = node.nodeId ??
|
|
171
|
+
this.genId("n", new Set(this.def.nodes.map((n) => n.nodeId)));
|
|
166
172
|
this.def.nodes.push({
|
|
167
173
|
nodeId: id,
|
|
168
174
|
typeId: node.typeId,
|
|
169
175
|
params: node.params,
|
|
176
|
+
initialInputs: node.initialInputs,
|
|
177
|
+
resolvedHandles: node.resolvedHandles,
|
|
170
178
|
});
|
|
171
179
|
if (node.position)
|
|
172
180
|
this.positions[id] = node.position;
|
|
@@ -188,7 +196,7 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
188
196
|
this.refreshValidation();
|
|
189
197
|
}
|
|
190
198
|
connect(edge) {
|
|
191
|
-
const id = edge.id ?? this.
|
|
199
|
+
const id = edge.id ?? this.genId("e", new Set(this.def.edges.map((e) => e.id)));
|
|
192
200
|
this.def.edges.push({
|
|
193
201
|
id,
|
|
194
202
|
source: { ...edge.source },
|
|
@@ -329,9 +337,6 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
329
337
|
for (const h of Array.from(set))
|
|
330
338
|
h(payload);
|
|
331
339
|
}
|
|
332
|
-
generateId(prefix) {
|
|
333
|
-
return `${prefix}${Math.random().toString(36).slice(2, 8)}`;
|
|
334
|
-
}
|
|
335
340
|
}
|
|
336
341
|
|
|
337
342
|
class CLIWorkbench {
|
|
@@ -463,6 +468,18 @@ class AbstractGraphRunner {
|
|
|
463
468
|
}
|
|
464
469
|
}
|
|
465
470
|
}
|
|
471
|
+
getInputDefaults(def) {
|
|
472
|
+
const out = {};
|
|
473
|
+
for (const n of def.nodes) {
|
|
474
|
+
const dynDefaults = n.resolvedHandles?.inputDefaults ?? {};
|
|
475
|
+
const graphDefaults = n.initialInputs ?? {};
|
|
476
|
+
const merged = { ...dynDefaults, ...graphDefaults };
|
|
477
|
+
if (Object.keys(merged).length > 0) {
|
|
478
|
+
out[n.nodeId] = merged;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
return out;
|
|
482
|
+
}
|
|
466
483
|
on(event, handler) {
|
|
467
484
|
if (!this.listeners.has(event))
|
|
468
485
|
this.listeners.set(event, new Set());
|
|
@@ -624,16 +641,6 @@ class LocalGraphRunner extends AbstractGraphRunner {
|
|
|
624
641
|
}
|
|
625
642
|
return out;
|
|
626
643
|
}
|
|
627
|
-
getInputDefaults(def) {
|
|
628
|
-
const out = {};
|
|
629
|
-
for (const n of def.nodes) {
|
|
630
|
-
const dynDefaults = n.resolvedHandles?.inputDefaults ?? {};
|
|
631
|
-
if (Object.keys(dynDefaults).length > 0) {
|
|
632
|
-
out[n.nodeId] = dynDefaults;
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
return out;
|
|
636
|
-
}
|
|
637
644
|
async snapshotFull() {
|
|
638
645
|
const def = undefined; // UI will supply def/positions on download for local
|
|
639
646
|
const inputs = this.getInputs(this.runtime
|
|
@@ -661,11 +668,25 @@ class LocalGraphRunner extends AbstractGraphRunner {
|
|
|
661
668
|
if (payload.def)
|
|
662
669
|
this.build(payload.def);
|
|
663
670
|
this.setEnvironment?.(payload.environment || {}, { merge: false });
|
|
664
|
-
|
|
671
|
+
this.hydrateSnapshotFull(payload);
|
|
672
|
+
}
|
|
673
|
+
hydrateSnapshotFull(snapshot) {
|
|
674
|
+
// Hydrate via runtime for exact restore (this emits events on runtime emitter)
|
|
665
675
|
this.runtime?.hydrate({
|
|
666
|
-
inputs:
|
|
667
|
-
outputs:
|
|
676
|
+
inputs: snapshot.inputs || {},
|
|
677
|
+
outputs: snapshot.outputs || {},
|
|
668
678
|
});
|
|
679
|
+
// Also emit directly from runner to ensure UI gets events even if engine isn't running
|
|
680
|
+
for (const [nodeId, map] of Object.entries(snapshot.inputs || {})) {
|
|
681
|
+
for (const [handle, value] of Object.entries(map || {})) {
|
|
682
|
+
this.emit("value", { nodeId, handle, value, io: "input" });
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
for (const [nodeId, map] of Object.entries(snapshot.outputs || {})) {
|
|
686
|
+
for (const [handle, value] of Object.entries(map || {})) {
|
|
687
|
+
this.emit("value", { nodeId, handle, value, io: "output" });
|
|
688
|
+
}
|
|
689
|
+
}
|
|
669
690
|
}
|
|
670
691
|
dispose() {
|
|
671
692
|
super.dispose();
|
|
@@ -1222,16 +1243,6 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
1222
1243
|
}
|
|
1223
1244
|
return out;
|
|
1224
1245
|
}
|
|
1225
|
-
getInputDefaults(def) {
|
|
1226
|
-
const out = {};
|
|
1227
|
-
for (const n of def.nodes) {
|
|
1228
|
-
const dynDefaults = n.resolvedHandles?.inputDefaults ?? {};
|
|
1229
|
-
if (Object.keys(dynDefaults).length > 0) {
|
|
1230
|
-
out[n.nodeId] = dynDefaults;
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
1233
|
-
return out;
|
|
1234
|
-
}
|
|
1235
1246
|
dispose() {
|
|
1236
1247
|
// Idempotent: allow multiple calls safely
|
|
1237
1248
|
if (this.disposed)
|
|
@@ -1401,7 +1412,8 @@ function summarizeDeep(value) {
|
|
|
1401
1412
|
|
|
1402
1413
|
// Shared UI constants for node layout to keep mapping and rendering in sync
|
|
1403
1414
|
const NODE_HEADER_HEIGHT_PX = 24;
|
|
1404
|
-
const NODE_ROW_HEIGHT_PX =
|
|
1415
|
+
const NODE_ROW_HEIGHT_PX = 18;
|
|
1416
|
+
const HANDLE_SIZE_PX = 12;
|
|
1405
1417
|
|
|
1406
1418
|
function computeEffectiveHandles(node, registry) {
|
|
1407
1419
|
const desc = registry.nodes.get(node.typeId);
|
|
@@ -1430,6 +1442,61 @@ function estimateNodeSize(args) {
|
|
|
1430
1442
|
const height = overrides?.height ?? NODE_HEADER_HEIGHT_PX + rows * NODE_ROW_HEIGHT_PX;
|
|
1431
1443
|
return { width, height, inputsCount, outputsCount, rowCount: rows };
|
|
1432
1444
|
}
|
|
1445
|
+
/**
|
|
1446
|
+
* Calculate the Y position for handle layout (center of row).
|
|
1447
|
+
* Used for positioning handles in React Flow.
|
|
1448
|
+
*/
|
|
1449
|
+
function getHandleLayoutY(rowIndex) {
|
|
1450
|
+
return (NODE_HEADER_HEIGHT_PX +
|
|
1451
|
+
rowIndex * NODE_ROW_HEIGHT_PX +
|
|
1452
|
+
NODE_ROW_HEIGHT_PX / 2);
|
|
1453
|
+
}
|
|
1454
|
+
/**
|
|
1455
|
+
* Calculate the Y position for handle bounds (top + centering offset).
|
|
1456
|
+
* Used for hit-testing and edge routing.
|
|
1457
|
+
*/
|
|
1458
|
+
function getHandleBoundsY(rowIndex) {
|
|
1459
|
+
return (NODE_HEADER_HEIGHT_PX +
|
|
1460
|
+
rowIndex * NODE_ROW_HEIGHT_PX +
|
|
1461
|
+
(NODE_ROW_HEIGHT_PX - HANDLE_SIZE_PX) / 2 +
|
|
1462
|
+
1);
|
|
1463
|
+
}
|
|
1464
|
+
/**
|
|
1465
|
+
* Calculate the X position for handle bounds based on position and node width.
|
|
1466
|
+
*/
|
|
1467
|
+
function getHandleBoundsX(position, nodeWidth) {
|
|
1468
|
+
if (position === Position.Left) {
|
|
1469
|
+
return -HANDLE_SIZE_PX / 2 + 1;
|
|
1470
|
+
}
|
|
1471
|
+
else {
|
|
1472
|
+
return nodeWidth - HANDLE_SIZE_PX / 2 - 1;
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
/**
|
|
1476
|
+
* Create handle bounds object for hit-testing/edge routing.
|
|
1477
|
+
*/
|
|
1478
|
+
function createHandleBounds(args) {
|
|
1479
|
+
return {
|
|
1480
|
+
id: args.id,
|
|
1481
|
+
type: args.type,
|
|
1482
|
+
position: args.position,
|
|
1483
|
+
x: getHandleBoundsX(args.position, args.nodeWidth),
|
|
1484
|
+
y: getHandleBoundsY(args.rowIndex),
|
|
1485
|
+
width: HANDLE_SIZE_PX,
|
|
1486
|
+
height: HANDLE_SIZE_PX,
|
|
1487
|
+
};
|
|
1488
|
+
}
|
|
1489
|
+
/**
|
|
1490
|
+
* Create handle layout object for React Flow rendering.
|
|
1491
|
+
*/
|
|
1492
|
+
function createHandleLayout(args) {
|
|
1493
|
+
return {
|
|
1494
|
+
id: args.id,
|
|
1495
|
+
type: args.type,
|
|
1496
|
+
position: args.position,
|
|
1497
|
+
y: getHandleLayoutY(args.rowIndex),
|
|
1498
|
+
};
|
|
1499
|
+
}
|
|
1433
1500
|
function layoutNode(args) {
|
|
1434
1501
|
const { node, registry, showValues, overrides } = args;
|
|
1435
1502
|
const { inputs, outputs } = computeEffectiveHandles(node, registry);
|
|
@@ -1441,40 +1508,34 @@ function layoutNode(args) {
|
|
|
1441
1508
|
showValues,
|
|
1442
1509
|
overrides,
|
|
1443
1510
|
});
|
|
1444
|
-
const HEADER = NODE_HEADER_HEIGHT_PX;
|
|
1445
|
-
const ROW = NODE_ROW_HEIGHT_PX;
|
|
1446
1511
|
const handles = [
|
|
1447
|
-
...inputOrder.map((id, i) => ({
|
|
1512
|
+
...inputOrder.map((id, i) => createHandleBounds({
|
|
1448
1513
|
id,
|
|
1449
1514
|
type: "target",
|
|
1450
1515
|
position: Position.Left,
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
width: 1,
|
|
1454
|
-
height: ROW + 2,
|
|
1516
|
+
rowIndex: i,
|
|
1517
|
+
nodeWidth: width,
|
|
1455
1518
|
})),
|
|
1456
|
-
...outputOrder.map((id, i) => ({
|
|
1519
|
+
...outputOrder.map((id, i) => createHandleBounds({
|
|
1457
1520
|
id,
|
|
1458
1521
|
type: "source",
|
|
1459
1522
|
position: Position.Right,
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
width: 1,
|
|
1463
|
-
height: ROW + 2,
|
|
1523
|
+
rowIndex: i,
|
|
1524
|
+
nodeWidth: width,
|
|
1464
1525
|
})),
|
|
1465
1526
|
];
|
|
1466
1527
|
const handleLayout = [
|
|
1467
|
-
...inputOrder.map((id, i) => ({
|
|
1528
|
+
...inputOrder.map((id, i) => createHandleLayout({
|
|
1468
1529
|
id,
|
|
1469
1530
|
type: "target",
|
|
1470
1531
|
position: Position.Left,
|
|
1471
|
-
|
|
1532
|
+
rowIndex: i,
|
|
1472
1533
|
})),
|
|
1473
|
-
...outputOrder.map((id, i) => ({
|
|
1534
|
+
...outputOrder.map((id, i) => createHandleLayout({
|
|
1474
1535
|
id,
|
|
1475
1536
|
type: "source",
|
|
1476
1537
|
position: Position.Right,
|
|
1477
|
-
|
|
1538
|
+
rowIndex: i,
|
|
1478
1539
|
})),
|
|
1479
1540
|
];
|
|
1480
1541
|
return { width, height, inputOrder, outputOrder, handles, handleLayout };
|
|
@@ -1789,20 +1850,22 @@ function toReactFlow(def, positions, registry, opts) {
|
|
|
1789
1850
|
const baseRightCount = geom.outputOrder.length;
|
|
1790
1851
|
const extraInputs = Array.from(missingInputsByNode[n.nodeId] || []);
|
|
1791
1852
|
const extraOutputs = Array.from(missingOutputsByNode[n.nodeId] || []);
|
|
1792
|
-
const HEADER = NODE_HEADER_HEIGHT_PX;
|
|
1793
|
-
const ROW = NODE_ROW_HEIGHT_PX;
|
|
1794
1853
|
const extraHandleLayoutLeft = extraInputs.map((id, i) => ({
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1854
|
+
...createHandleLayout({
|
|
1855
|
+
id,
|
|
1856
|
+
type: "target",
|
|
1857
|
+
position: Position.Left,
|
|
1858
|
+
rowIndex: baseLeftCount + i,
|
|
1859
|
+
}),
|
|
1799
1860
|
missing: true,
|
|
1800
1861
|
}));
|
|
1801
1862
|
const extraHandleLayoutRight = extraOutputs.map((id, i) => ({
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1863
|
+
...createHandleLayout({
|
|
1864
|
+
id,
|
|
1865
|
+
type: "source",
|
|
1866
|
+
position: Position.Right,
|
|
1867
|
+
rowIndex: baseRightCount + i,
|
|
1868
|
+
}),
|
|
1806
1869
|
missing: true,
|
|
1807
1870
|
}));
|
|
1808
1871
|
const handleLayout = [
|
|
@@ -1811,23 +1874,19 @@ function toReactFlow(def, positions, registry, opts) {
|
|
|
1811
1874
|
...extraHandleLayoutRight,
|
|
1812
1875
|
];
|
|
1813
1876
|
// Precompute handle bounds (including missing) so edges can render immediately
|
|
1814
|
-
const missingBoundsLeft = extraInputs.map((id, i) => ({
|
|
1877
|
+
const missingBoundsLeft = extraInputs.map((id, i) => createHandleBounds({
|
|
1815
1878
|
id,
|
|
1816
1879
|
type: "target",
|
|
1817
1880
|
position: Position.Left,
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
width: 1,
|
|
1821
|
-
height: ROW + 2,
|
|
1881
|
+
rowIndex: baseLeftCount + i,
|
|
1882
|
+
nodeWidth: geom.width,
|
|
1822
1883
|
}));
|
|
1823
|
-
const missingBoundsRight = extraOutputs.map((id, i) => ({
|
|
1884
|
+
const missingBoundsRight = extraOutputs.map((id, i) => createHandleBounds({
|
|
1824
1885
|
id,
|
|
1825
1886
|
type: "source",
|
|
1826
1887
|
position: Position.Right,
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
width: 1,
|
|
1830
|
-
height: ROW + 2,
|
|
1888
|
+
rowIndex: baseRightCount + i,
|
|
1889
|
+
nodeWidth: geom.width,
|
|
1831
1890
|
}));
|
|
1832
1891
|
const handles = [
|
|
1833
1892
|
...geom.handles,
|
|
@@ -1838,7 +1897,7 @@ function toReactFlow(def, positions, registry, opts) {
|
|
|
1838
1897
|
const baseRows = Math.max(baseLeftCount, baseRightCount);
|
|
1839
1898
|
const newRows = Math.max(baseLeftCount + extraInputs.length, baseRightCount + extraOutputs.length);
|
|
1840
1899
|
const initialWidth = geom.width;
|
|
1841
|
-
const initialHeight = geom.height + Math.max(0, newRows - baseRows) *
|
|
1900
|
+
const initialHeight = geom.height + Math.max(0, newRows - baseRows) * NODE_ROW_HEIGHT_PX;
|
|
1842
1901
|
return {
|
|
1843
1902
|
id: n.nodeId,
|
|
1844
1903
|
data: {
|
|
@@ -1974,7 +2033,7 @@ function getHandleClassName(args) {
|
|
|
1974
2033
|
else {
|
|
1975
2034
|
borderColor = "!border-gray-500 dark:!border-gray-400";
|
|
1976
2035
|
}
|
|
1977
|
-
return cx("!w-3 !h-3 !bg-white !dark:bg-stone-900", borderColor, kind === "output" && "!rounded-none");
|
|
2036
|
+
return cx("!w-3 !h-3 !bg-white/50 !dark:bg-stone-900", borderColor, kind === "output" && "!rounded-none");
|
|
1978
2037
|
}
|
|
1979
2038
|
|
|
1980
2039
|
function generateTimestamp() {
|
|
@@ -3046,7 +3105,7 @@ function NodeHandleItem({ kind, id, type, position, y, isConnectable, className,
|
|
|
3046
3105
|
textOverflow: "ellipsis",
|
|
3047
3106
|
}, children: renderLabel({ kind, id }) }))] }));
|
|
3048
3107
|
}
|
|
3049
|
-
function NodeHandles({ data, isConnectable,
|
|
3108
|
+
function NodeHandles({ data, isConnectable, getClassName, renderLabel, labelClassName = "absolute text-[11px] text-gray-700 dark:text-gray-300 pointer-events-none", }) {
|
|
3050
3109
|
const layout = data.handleLayout ?? [];
|
|
3051
3110
|
const byId = React.useMemo(() => {
|
|
3052
3111
|
const m = new Map();
|
|
@@ -3077,28 +3136,26 @@ function NodeHandles({ data, isConnectable, inputClassName = "!w-2 !h-2 !bg-gray
|
|
|
3077
3136
|
const placed = byId.get(`target:${h.id}`) ?? byId.get(h.id);
|
|
3078
3137
|
const position = placed?.position ?? Position.Left;
|
|
3079
3138
|
const y = placed?.y;
|
|
3080
|
-
const cls = getClassName?.({ kind: "input", id: h.id, type: "target" }) ??
|
|
3081
|
-
inputClassName;
|
|
3139
|
+
const cls = getClassName?.({ kind: "input", id: h.id, type: "target" }) ?? "";
|
|
3082
3140
|
return (jsx(NodeHandleItem, { kind: "input", id: h.id, type: "target", position: position, y: y, isConnectable: isConnectable, className: cls, labelClassName: labelClassName, renderLabel: renderLabel }, h.id));
|
|
3083
3141
|
}), missingInputs.map((h) => {
|
|
3084
3142
|
const key = `missing-input:${h.id}`;
|
|
3085
3143
|
const position = h.position ?? Position.Left;
|
|
3086
3144
|
const y = h.y;
|
|
3087
3145
|
const cls = "!w-3 !h-3 !bg-amber-400 !border-amber-500";
|
|
3088
|
-
return (jsx(NodeHandleItem, { kind: "input", id: h.id, type: "target", position: position, y: y, isConnectable: false, className: cls
|
|
3146
|
+
return (jsx(NodeHandleItem, { kind: "input", id: h.id, type: "target", position: position, y: y, isConnectable: false, className: `${cls} wb-nodrag wb-nowheel`, labelClassName: labelClassName, renderLabel: renderLabel }, key));
|
|
3089
3147
|
}), (data.outputHandles ?? []).map((h) => {
|
|
3090
3148
|
const placed = byId.get(`source:${h.id}`) ?? byId.get(h.id);
|
|
3091
3149
|
const position = placed?.position ?? Position.Right;
|
|
3092
3150
|
const y = placed?.y;
|
|
3093
|
-
const cls = getClassName?.({ kind: "output", id: h.id, type: "source" }) ??
|
|
3094
|
-
|
|
3095
|
-
return (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));
|
|
3151
|
+
const cls = getClassName?.({ kind: "output", id: h.id, type: "source" }) ?? "";
|
|
3152
|
+
return (jsx(NodeHandleItem, { kind: "output", id: h.id, type: "source", position: position, y: y, isConnectable: isConnectable, className: cls, labelClassName: labelClassName, renderLabel: renderLabel }, h.id));
|
|
3096
3153
|
}), missingOutputs.map((h) => {
|
|
3097
3154
|
const key = `missing-output:${h.id}`;
|
|
3098
3155
|
const position = h.position ?? Position.Right;
|
|
3099
3156
|
const y = h.y;
|
|
3100
3157
|
const cls = "!w-3 !h-3 !bg-amber-400 !border-amber-500 !rounded-none wb-nodrag wb-nowheel";
|
|
3101
|
-
return (jsx(NodeHandleItem, { kind: "output", id: h.id, type: "source", position: position, y: y, isConnectable: false, className: cls
|
|
3158
|
+
return (jsx(NodeHandleItem, { kind: "output", id: h.id, type: "source", position: position, y: y, isConnectable: false, className: `${cls} wb-nodrag wb-nowheel`, labelClassName: labelClassName, renderLabel: renderLabel }, key));
|
|
3102
3159
|
})] }));
|
|
3103
3160
|
}
|
|
3104
3161
|
|
|
@@ -3127,7 +3184,7 @@ const DefaultNode = React.memo(function DefaultNode({ id, data, selected, isConn
|
|
|
3127
3184
|
status,
|
|
3128
3185
|
validation,
|
|
3129
3186
|
});
|
|
3130
|
-
return (jsxs("div", { className: cx("rounded-lg bg-white/
|
|
3187
|
+
return (jsxs("div", { className: cx("rounded-lg bg-white/50 !dark:bg-stone-900", containerBorder), style: {
|
|
3131
3188
|
position: "relative",
|
|
3132
3189
|
minWidth: typeof data.renderWidth === "number" ? data.renderWidth : undefined,
|
|
3133
3190
|
minHeight: typeof data.renderHeight === "number" ? data.renderHeight : undefined,
|
|
@@ -3212,15 +3269,13 @@ function DefaultNodeContent({ data, isConnectable, }) {
|
|
|
3212
3269
|
} })] }));
|
|
3213
3270
|
}
|
|
3214
3271
|
|
|
3215
|
-
function DefaultContextMenu({ open, clientPos,
|
|
3216
|
-
const { registry } = useWorkbenchContext();
|
|
3272
|
+
function DefaultContextMenu({ open, clientPos, handlers, registry, nodeIds, }) {
|
|
3217
3273
|
const rf = useReactFlow();
|
|
3218
|
-
const ids = Array.from(registry.nodes.keys());
|
|
3219
3274
|
const [query, setQuery] = useState("");
|
|
3220
3275
|
const q = query.trim().toLowerCase();
|
|
3221
3276
|
const filteredIds = q
|
|
3222
|
-
?
|
|
3223
|
-
:
|
|
3277
|
+
? nodeIds.filter((id) => id.toLowerCase().includes(q))
|
|
3278
|
+
: nodeIds;
|
|
3224
3279
|
const root = { __children: {} };
|
|
3225
3280
|
for (const id of filteredIds) {
|
|
3226
3281
|
const parts = id.split(".");
|
|
@@ -3244,11 +3299,11 @@ function DefaultContextMenu({ open, clientPos, onAdd, onClose, }) {
|
|
|
3244
3299
|
if (!ref.current)
|
|
3245
3300
|
return;
|
|
3246
3301
|
if (!ref.current.contains(e.target))
|
|
3247
|
-
onClose();
|
|
3302
|
+
handlers.onClose();
|
|
3248
3303
|
};
|
|
3249
3304
|
const onKey = (e) => {
|
|
3250
3305
|
if (e.key === "Escape")
|
|
3251
|
-
onClose();
|
|
3306
|
+
handlers.onClose();
|
|
3252
3307
|
};
|
|
3253
3308
|
window.addEventListener("mousedown", onDown, true);
|
|
3254
3309
|
window.addEventListener("keydown", onKey);
|
|
@@ -3256,7 +3311,7 @@ function DefaultContextMenu({ open, clientPos, onAdd, onClose, }) {
|
|
|
3256
3311
|
window.removeEventListener("mousedown", onDown, true);
|
|
3257
3312
|
window.removeEventListener("keydown", onKey);
|
|
3258
3313
|
};
|
|
3259
|
-
}, [open,
|
|
3314
|
+
}, [open, handlers]);
|
|
3260
3315
|
// Focus search input when menu opens
|
|
3261
3316
|
const inputRef = useRef(null);
|
|
3262
3317
|
useEffect(() => {
|
|
@@ -3275,8 +3330,8 @@ function DefaultContextMenu({ open, clientPos, onAdd, onClose, }) {
|
|
|
3275
3330
|
const handleClick = (typeId) => {
|
|
3276
3331
|
// project() is deprecated; use screenToFlowPosition for screen coordinates
|
|
3277
3332
|
const p = rf.screenToFlowPosition({ x: clientPos.x, y: clientPos.y });
|
|
3278
|
-
|
|
3279
|
-
onClose();
|
|
3333
|
+
handlers.onAddNode(typeId, { position: p });
|
|
3334
|
+
handlers.onClose();
|
|
3280
3335
|
};
|
|
3281
3336
|
const renderTree = (tree, path = []) => {
|
|
3282
3337
|
const entries = Object.entries(tree?.__children ?? {}).sort((a, b) => a[0].localeCompare(b[0]));
|
|
@@ -3298,8 +3353,7 @@ function DefaultContextMenu({ open, clientPos, onAdd, onClose, }) {
|
|
|
3298
3353
|
}, children: [jsxs("div", { className: "px-2 py-1 font-semibold text-gray-700", children: ["Add Node", " ", jsxs("span", { className: "text-gray-500 font-normal", children: ["(", totalCount, ")"] })] }), jsx("div", { className: "px-2 pb-1", children: jsx("input", { ref: inputRef, type: "text", value: query, onChange: (e) => setQuery(e.target.value), placeholder: "Filter nodes...", className: "w-full border border-gray-300 px-2 py-1 text-sm outline-none focus:border-gray-400", onClick: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), onWheel: (e) => e.stopPropagation() }) }), jsx("div", { className: "max-h-60 overflow-auto", children: totalCount > 0 ? (renderTree(root)) : (jsx("div", { className: "px-3 py-2 text-gray-400", children: "No matches" })) })] }));
|
|
3299
3354
|
}
|
|
3300
3355
|
|
|
3301
|
-
function NodeContextMenu({ open, clientPos, nodeId,
|
|
3302
|
-
const { wb, runner, engineKind, registry, outputsMap, outputTypesMap } = useWorkbenchContext();
|
|
3356
|
+
function NodeContextMenu({ open, clientPos, nodeId, handlers, canRunPull, bakeableOutputs, }) {
|
|
3303
3357
|
const ref = useRef(null);
|
|
3304
3358
|
// outside click + ESC
|
|
3305
3359
|
useEffect(() => {
|
|
@@ -3309,11 +3363,11 @@ function NodeContextMenu({ open, clientPos, nodeId, onClose, }) {
|
|
|
3309
3363
|
if (!ref.current)
|
|
3310
3364
|
return;
|
|
3311
3365
|
if (!ref.current.contains(e.target))
|
|
3312
|
-
onClose();
|
|
3366
|
+
handlers.onClose();
|
|
3313
3367
|
};
|
|
3314
3368
|
const onKey = (e) => {
|
|
3315
3369
|
if (e.key === "Escape")
|
|
3316
|
-
onClose();
|
|
3370
|
+
handlers.onClose();
|
|
3317
3371
|
};
|
|
3318
3372
|
window.addEventListener("mousedown", onDown, true);
|
|
3319
3373
|
window.addEventListener("keydown", onKey);
|
|
@@ -3321,48 +3375,26 @@ function NodeContextMenu({ open, clientPos, nodeId, onClose, }) {
|
|
|
3321
3375
|
window.removeEventListener("mousedown", onDown, true);
|
|
3322
3376
|
window.removeEventListener("keydown", onKey);
|
|
3323
3377
|
};
|
|
3324
|
-
}, [open,
|
|
3378
|
+
}, [open, handlers]);
|
|
3325
3379
|
useEffect(() => {
|
|
3326
3380
|
if (open)
|
|
3327
3381
|
ref.current?.focus();
|
|
3328
3382
|
}, [open]);
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
const base = tId.slice(0, -2);
|
|
3345
|
-
const tArr = registry.types.get(tId);
|
|
3346
|
-
const tElem = registry.types.get(base);
|
|
3347
|
-
const arrT = tArr?.bakeTarget;
|
|
3348
|
-
const elemT = tElem?.bakeTarget;
|
|
3349
|
-
if ((arrT && registry.nodes.has(arrT.nodeTypeId)) ||
|
|
3350
|
-
(elemT && registry.nodes.has(elemT.nodeTypeId)))
|
|
3351
|
-
out.push(h);
|
|
3352
|
-
}
|
|
3353
|
-
else {
|
|
3354
|
-
const t = registry.types.get(tId);
|
|
3355
|
-
const bt = t?.bakeTarget;
|
|
3356
|
-
if (bt && registry.nodes.has(bt.nodeTypeId))
|
|
3357
|
-
out.push(h);
|
|
3358
|
-
}
|
|
3359
|
-
}
|
|
3360
|
-
return out;
|
|
3361
|
-
}
|
|
3362
|
-
catch {
|
|
3363
|
-
return [];
|
|
3364
|
-
}
|
|
3365
|
-
};
|
|
3383
|
+
if (!open || !clientPos || !nodeId)
|
|
3384
|
+
return null;
|
|
3385
|
+
// clamp
|
|
3386
|
+
const MENU_MIN_WIDTH = 180;
|
|
3387
|
+
const PADDING = 16;
|
|
3388
|
+
const x = Math.min(clientPos.x, (typeof window !== "undefined" ? window.innerWidth : 0) -
|
|
3389
|
+
(MENU_MIN_WIDTH + PADDING));
|
|
3390
|
+
const y = Math.min(clientPos.y, (typeof window !== "undefined" ? window.innerHeight : 0) - 240);
|
|
3391
|
+
return (jsxs("div", { ref: ref, tabIndex: -1, className: "fixed z-[1000] bg-white border border-gray-300 rounded-lg shadow-lg p-1 min-w-[180px] text-sm text-gray-700", style: { left: x, top: y }, onClick: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), onWheel: (e) => e.stopPropagation(), onContextMenu: (e) => {
|
|
3392
|
+
e.preventDefault();
|
|
3393
|
+
e.stopPropagation();
|
|
3394
|
+
}, children: [jsxs("div", { className: "px-2 py-1 font-semibold text-gray-700", children: ["Node (", nodeId, ")"] }), jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onDelete, children: "Delete" }), jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onDuplicate, children: "Duplicate" }), canRunPull && (jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onRunPull, children: "Run (pull)" })), jsx("div", { className: "h-px bg-gray-200 my-1" }), bakeableOutputs.length > 0 && (jsxs(Fragment, { children: [jsx("div", { className: "px-2 py-1 font-semibold text-gray-700", children: "Bake" }), bakeableOutputs.map((h) => (jsxs("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: () => handlers.onBake(h), children: ["Bake: ", h] }, h))), jsx("div", { className: "h-px bg-gray-200 my-1" })] })), jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onCopyId, children: "Copy Node ID" })] }));
|
|
3395
|
+
}
|
|
3396
|
+
|
|
3397
|
+
function createNodeContextMenuHandlers(nodeId, wb, runner, registry, outputsMap, outputTypesMap, onClose) {
|
|
3366
3398
|
const doBake = async (handleId) => {
|
|
3367
3399
|
try {
|
|
3368
3400
|
const typeId = outputTypesMap?.[nodeId]?.[handleId];
|
|
@@ -3395,7 +3427,6 @@ function NodeContextMenu({ open, clientPos, nodeId, onClose, }) {
|
|
|
3395
3427
|
const newId = wb.addNode({
|
|
3396
3428
|
typeId: singleTarget.nodeTypeId,
|
|
3397
3429
|
position: { x: pos.x + 180, y: pos.y },
|
|
3398
|
-
params: {},
|
|
3399
3430
|
});
|
|
3400
3431
|
runner.update(wb.export());
|
|
3401
3432
|
await runner.whenIdle();
|
|
@@ -3406,12 +3437,9 @@ function NodeContextMenu({ open, clientPos, nodeId, onClose, }) {
|
|
|
3406
3437
|
const nodeDesc = registry.nodes.get(arrTarget.nodeTypeId);
|
|
3407
3438
|
const inType = getInputTypeId(nodeDesc?.inputs, arrTarget.inputHandle);
|
|
3408
3439
|
const coerced = await coerceIfNeeded(typeId, inType, unwrap(raw));
|
|
3409
|
-
const newId =
|
|
3410
|
-
wb.addNode({
|
|
3411
|
-
nodeId: newId,
|
|
3440
|
+
const newId = wb.addNode({
|
|
3412
3441
|
typeId: arrTarget.nodeTypeId,
|
|
3413
3442
|
position: { x: pos.x + 180, y: pos.y },
|
|
3414
|
-
params: {},
|
|
3415
3443
|
});
|
|
3416
3444
|
runner.update(wb.export());
|
|
3417
3445
|
await runner.whenIdle();
|
|
@@ -3429,14 +3457,11 @@ function NodeContextMenu({ open, clientPos, nodeId, onClose, }) {
|
|
|
3429
3457
|
const DY = 160;
|
|
3430
3458
|
const nodeIds = [];
|
|
3431
3459
|
for (let idx = 0; idx < coercedItems.length; idx++) {
|
|
3432
|
-
const cv = coercedItems[idx];
|
|
3433
3460
|
const col = idx % COLS;
|
|
3434
3461
|
const row = Math.floor(idx / COLS);
|
|
3435
3462
|
const newId = wb.addNode({
|
|
3436
3463
|
typeId: elemTarget.nodeTypeId,
|
|
3437
3464
|
position: { x: pos.x + (col + 1) * DX, y: pos.y + row * DY },
|
|
3438
|
-
params: {},
|
|
3439
|
-
initialInputs: { [elemTarget.inputHandle]: structuredClone(cv) },
|
|
3440
3465
|
});
|
|
3441
3466
|
nodeIds.push(newId);
|
|
3442
3467
|
}
|
|
@@ -3454,65 +3479,91 @@ function NodeContextMenu({ open, clientPos, nodeId, onClose, }) {
|
|
|
3454
3479
|
}
|
|
3455
3480
|
catch { }
|
|
3456
3481
|
};
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3482
|
+
return {
|
|
3483
|
+
onDelete: () => {
|
|
3484
|
+
wb.removeNode(nodeId);
|
|
3485
|
+
onClose();
|
|
3486
|
+
},
|
|
3487
|
+
onDuplicate: async () => {
|
|
3488
|
+
const def = wb.export();
|
|
3489
|
+
const n = def.nodes.find((n) => n.nodeId === nodeId);
|
|
3490
|
+
if (!n)
|
|
3491
|
+
return onClose();
|
|
3492
|
+
const pos = wb.getPositions?.()[nodeId] || { x: 0, y: 0 };
|
|
3493
|
+
const newId = wb.addNode({
|
|
3494
|
+
typeId: n.typeId,
|
|
3495
|
+
params: n.params,
|
|
3496
|
+
position: { x: pos.x + 24, y: pos.y + 24 },
|
|
3497
|
+
initialInputs: n.initialInputs,
|
|
3498
|
+
resolvedHandles: n.resolvedHandles,
|
|
3499
|
+
});
|
|
3500
|
+
await runner.whenIdle();
|
|
3501
|
+
runner.setInputs(newId, { ...runner.getInputs(def)[nodeId] });
|
|
3502
|
+
onClose();
|
|
3503
|
+
},
|
|
3504
|
+
onRunPull: async () => {
|
|
3505
|
+
try {
|
|
3506
|
+
await runner.computeNode(nodeId);
|
|
3507
|
+
}
|
|
3508
|
+
catch { }
|
|
3509
|
+
onClose();
|
|
3510
|
+
},
|
|
3511
|
+
onBake: async (handleId) => {
|
|
3512
|
+
await doBake(handleId);
|
|
3513
|
+
onClose();
|
|
3514
|
+
},
|
|
3515
|
+
onCopyId: async () => {
|
|
3516
|
+
try {
|
|
3517
|
+
await navigator.clipboard.writeText(nodeId);
|
|
3518
|
+
}
|
|
3519
|
+
catch { }
|
|
3520
|
+
onClose();
|
|
3521
|
+
},
|
|
3522
|
+
onClose,
|
|
3523
|
+
};
|
|
3524
|
+
}
|
|
3525
|
+
function getBakeableOutputs(nodeId, wb, registry, outputTypesMap) {
|
|
3526
|
+
try {
|
|
3463
3527
|
const def = wb.export();
|
|
3464
|
-
const
|
|
3465
|
-
if (!
|
|
3466
|
-
return
|
|
3467
|
-
const
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3528
|
+
const node = def.nodes.find((n) => n.nodeId === nodeId);
|
|
3529
|
+
if (!node)
|
|
3530
|
+
return [];
|
|
3531
|
+
const desc = registry.nodes.get(node.typeId);
|
|
3532
|
+
const handles = Object.keys(desc?.outputs || {});
|
|
3533
|
+
const out = [];
|
|
3534
|
+
for (const h of handles) {
|
|
3535
|
+
const tId = outputTypesMap?.[nodeId]?.[h];
|
|
3536
|
+
if (!tId)
|
|
3537
|
+
continue;
|
|
3538
|
+
if (tId.endsWith("[]")) {
|
|
3539
|
+
const base = tId.slice(0, -2);
|
|
3540
|
+
const tArr = registry.types.get(tId);
|
|
3541
|
+
const tElem = registry.types.get(base);
|
|
3542
|
+
const arrT = tArr?.bakeTarget;
|
|
3543
|
+
const elemT = tElem?.bakeTarget;
|
|
3544
|
+
if ((arrT && registry.nodes.has(arrT.nodeTypeId)) ||
|
|
3545
|
+
(elemT && registry.nodes.has(elemT.nodeTypeId)))
|
|
3546
|
+
out.push(h);
|
|
3547
|
+
}
|
|
3548
|
+
else {
|
|
3549
|
+
const t = registry.types.get(tId);
|
|
3550
|
+
const bt = t?.bakeTarget;
|
|
3551
|
+
if (bt && registry.nodes.has(bt.nodeTypeId))
|
|
3552
|
+
out.push(h);
|
|
3553
|
+
}
|
|
3489
3554
|
}
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
// clamp
|
|
3496
|
-
const MENU_MIN_WIDTH = 180;
|
|
3497
|
-
const PADDING = 16;
|
|
3498
|
-
const x = Math.min(clientPos.x, (typeof window !== "undefined" ? window.innerWidth : 0) -
|
|
3499
|
-
(MENU_MIN_WIDTH + PADDING));
|
|
3500
|
-
const y = Math.min(clientPos.y, (typeof window !== "undefined" ? window.innerHeight : 0) - 240);
|
|
3501
|
-
const canRunPull = engineKind()?.toString() === "pull";
|
|
3502
|
-
const outs = getBakeableOutputs();
|
|
3503
|
-
return (jsxs("div", { ref: ref, tabIndex: -1, className: "fixed z-[1000] bg-white border border-gray-300 rounded-lg shadow-lg p-1 min-w-[180px] text-sm text-gray-700", style: { left: x, top: y }, onClick: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), onWheel: (e) => e.stopPropagation(), onContextMenu: (e) => {
|
|
3504
|
-
e.preventDefault();
|
|
3505
|
-
e.stopPropagation();
|
|
3506
|
-
}, children: [jsxs("div", { className: "px-2 py-1 font-semibold text-gray-700", children: ["Node (", nodeId, ")"] }), jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handleDelete, children: "Delete" }), jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handleDuplicate, children: "Duplicate" }), canRunPull && (jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handleRunPull, children: "Run (pull)" })), jsx("div", { className: "h-px bg-gray-200 my-1" }), outs.length > 0 && (jsxs(Fragment, { children: [jsx("div", { className: "px-2 py-1 font-semibold text-gray-700", children: "Bake" }), outs.map((h) => (jsxs("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: async () => {
|
|
3507
|
-
await doBake(h);
|
|
3508
|
-
onClose();
|
|
3509
|
-
}, children: ["Bake: ", h] }, h))), jsx("div", { className: "h-px bg-gray-200 my-1" })] })), jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handleCopyId, children: "Copy Node ID" })] }));
|
|
3555
|
+
return out;
|
|
3556
|
+
}
|
|
3557
|
+
catch {
|
|
3558
|
+
return [];
|
|
3559
|
+
}
|
|
3510
3560
|
}
|
|
3511
3561
|
|
|
3512
3562
|
const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, getDefaultNodeSize }, ref) => {
|
|
3513
|
-
const { wb, registry, inputsMap, inputDefaultsMap, outputsMap, valuesTick, nodeStatus, edgeStatus, validationByNode, validationByEdge, uiVersion, } = useWorkbenchContext();
|
|
3563
|
+
const { wb, registry, inputsMap, inputDefaultsMap, outputsMap, outputTypesMap, valuesTick, nodeStatus, edgeStatus, validationByNode, validationByEdge, uiVersion, runner, engineKind, } = useWorkbenchContext();
|
|
3514
3564
|
const nodeValidation = validationByNode;
|
|
3515
3565
|
const edgeValidation = validationByEdge.errors;
|
|
3566
|
+
const [registryVersion, setRegistryVersion] = useState(0);
|
|
3516
3567
|
// Keep stable references for nodes/edges to avoid unnecessary updates
|
|
3517
3568
|
const prevNodesRef = useRef([]);
|
|
3518
3569
|
const prevEdgesRef = useRef([]);
|
|
@@ -3577,9 +3628,9 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
3577
3628
|
},
|
|
3578
3629
|
}));
|
|
3579
3630
|
const { onConnect, onNodesChange, onEdgesChange, onEdgesDelete, onNodesDelete, } = useWorkbenchBridge(wb);
|
|
3631
|
+
const ui = wb.getUI();
|
|
3580
3632
|
const { nodeTypes, resolveNodeType } = useMemo(() => {
|
|
3581
3633
|
// Build nodeTypes map using UI extension registry
|
|
3582
|
-
const ui = wb.getUI();
|
|
3583
3634
|
const custom = new Map(); // Include all types present in registry AND current graph to avoid timing issues
|
|
3584
3635
|
const def = wb.export();
|
|
3585
3636
|
const ids = new Set([
|
|
@@ -3601,7 +3652,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
3601
3652
|
const resolver = (nodeTypeId) => custom.has(nodeTypeId) ? `spark-${nodeTypeId}` : "spark-default";
|
|
3602
3653
|
return { nodeTypes: types, resolveNodeType: resolver };
|
|
3603
3654
|
// Include uiVersion to recompute when custom renderers are registered
|
|
3604
|
-
}, [wb, registry, uiVersion]);
|
|
3655
|
+
}, [wb, registry, uiVersion, ui]);
|
|
3605
3656
|
const { nodes, edges } = useMemo(() => {
|
|
3606
3657
|
const def = wb.export();
|
|
3607
3658
|
const sel = wb.getSelection();
|
|
@@ -3765,15 +3816,65 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
3765
3816
|
setNodeMenuOpen(false);
|
|
3766
3817
|
}
|
|
3767
3818
|
};
|
|
3768
|
-
const addNodeAt = useCallback((typeId,
|
|
3769
|
-
wb.addNode({
|
|
3770
|
-
|
|
3819
|
+
const addNodeAt = useCallback(async (typeId, opts) => {
|
|
3820
|
+
const nodeId = wb.addNode({
|
|
3821
|
+
typeId,
|
|
3822
|
+
initialInputs: opts.initialInputs,
|
|
3823
|
+
position: opts.position,
|
|
3824
|
+
});
|
|
3825
|
+
if (opts.inputs) {
|
|
3826
|
+
runner.update(wb.export());
|
|
3827
|
+
await runner.whenIdle();
|
|
3828
|
+
runner.setInputs(nodeId, opts.inputs);
|
|
3829
|
+
}
|
|
3830
|
+
}, [wb, runner]);
|
|
3771
3831
|
const onCloseMenu = useCallback(() => {
|
|
3772
3832
|
setMenuOpen(false);
|
|
3773
3833
|
}, []);
|
|
3774
3834
|
const onCloseNodeMenu = useCallback(() => {
|
|
3775
3835
|
setNodeMenuOpen(false);
|
|
3776
3836
|
}, []);
|
|
3837
|
+
useEffect(() => {
|
|
3838
|
+
const off = runner.on("registry", () => {
|
|
3839
|
+
setRegistryVersion((v) => v + 1);
|
|
3840
|
+
});
|
|
3841
|
+
return () => off();
|
|
3842
|
+
}, [runner]);
|
|
3843
|
+
const nodeIds = useMemo(() => Array.from(registry.nodes.keys()), [registry, registryVersion]);
|
|
3844
|
+
const defaultContextMenuHandlers = useMemo(() => ({
|
|
3845
|
+
onAddNode: addNodeAt,
|
|
3846
|
+
onClose: onCloseMenu,
|
|
3847
|
+
}), [addNodeAt, onCloseMenu]);
|
|
3848
|
+
const nodeContextMenuHandlers = useMemo(() => {
|
|
3849
|
+
if (!nodeAtMenu)
|
|
3850
|
+
return null;
|
|
3851
|
+
return createNodeContextMenuHandlers(nodeAtMenu, wb, runner, registry, outputsMap, outputTypesMap, onCloseNodeMenu);
|
|
3852
|
+
}, [
|
|
3853
|
+
nodeAtMenu,
|
|
3854
|
+
wb,
|
|
3855
|
+
runner,
|
|
3856
|
+
registry,
|
|
3857
|
+
outputsMap,
|
|
3858
|
+
outputTypesMap,
|
|
3859
|
+
onCloseNodeMenu,
|
|
3860
|
+
]);
|
|
3861
|
+
const canRunPull = useMemo(() => engineKind()?.toString() === "pull", [engineKind]);
|
|
3862
|
+
const bakeableOutputs = useMemo(() => {
|
|
3863
|
+
if (!nodeAtMenu)
|
|
3864
|
+
return [];
|
|
3865
|
+
return getBakeableOutputs(nodeAtMenu, wb, registry, outputTypesMap);
|
|
3866
|
+
}, [nodeAtMenu, wb, registry, outputTypesMap]);
|
|
3867
|
+
// Get custom renderers from UI extension registry (reactive to uiVersion changes)
|
|
3868
|
+
const { BackgroundRenderer, MinimapRenderer, ControlsRenderer, DefaultContextMenuRenderer, NodeContextMenuRenderer, connectionLineRenderer, } = useMemo(() => {
|
|
3869
|
+
return {
|
|
3870
|
+
BackgroundRenderer: ui.getBackgroundRenderer(),
|
|
3871
|
+
MinimapRenderer: ui.getMinimapRenderer(),
|
|
3872
|
+
ControlsRenderer: ui.getControlsRenderer(),
|
|
3873
|
+
DefaultContextMenuRenderer: ui.getDefaultContextMenuRenderer(),
|
|
3874
|
+
NodeContextMenuRenderer: ui.getNodeContextMenuRenderer(),
|
|
3875
|
+
connectionLineRenderer: ui.getConnectionLineRenderer(),
|
|
3876
|
+
};
|
|
3877
|
+
}, [ui, uiVersion]);
|
|
3777
3878
|
const onMoveEnd = useCallback(() => {
|
|
3778
3879
|
if (rfInstanceRef.current) {
|
|
3779
3880
|
const viewport = rfInstanceRef.current.getViewport();
|
|
@@ -3798,7 +3899,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
3798
3899
|
});
|
|
3799
3900
|
}
|
|
3800
3901
|
});
|
|
3801
|
-
return (jsx("div", { className: "w-full h-full", onContextMenu: onContextMenu, children: jsx(ReactFlowProvider, { children: jsxs(ReactFlow, { nodes: throttled.nodes, edges: throttled.edges, nodeTypes: nodeTypes, selectionOnDrag: true, onInit: (inst) => {
|
|
3902
|
+
return (jsx("div", { className: "w-full h-full", onContextMenu: onContextMenu, children: jsx(ReactFlowProvider, { children: jsxs(ReactFlow, { nodes: throttled.nodes, edges: throttled.edges, nodeTypes: nodeTypes, connectionLineComponent: connectionLineRenderer, selectionOnDrag: true, onInit: (inst) => {
|
|
3802
3903
|
rfInstanceRef.current = inst;
|
|
3803
3904
|
const savedViewport = wb.getViewport();
|
|
3804
3905
|
if (savedViewport) {
|
|
@@ -3809,7 +3910,9 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
3809
3910
|
zoom: savedViewport.zoom,
|
|
3810
3911
|
});
|
|
3811
3912
|
}
|
|
3812
|
-
}, onConnect: onConnect, onEdgesChange: onEdgesChange, onEdgesDelete: onEdgesDelete, onNodesDelete: onNodesDelete, onNodesChange: onNodesChange, onMoveEnd: onMoveEnd, deleteKeyCode: ["Backspace", "Delete"], proOptions: { hideAttribution: true }, noDragClassName: "wb-nodrag", noWheelClassName: "wb-nowheel", noPanClassName: "wb-nopan", fitView: true, children: [jsx(Background, { id: "workbench-canvas-background", variant: BackgroundVariant.Dots, gap: 12, size: 1 }), jsx(MiniMap, {}), jsx(Controls, {}), jsx(
|
|
3913
|
+
}, onConnect: onConnect, onEdgesChange: onEdgesChange, onEdgesDelete: onEdgesDelete, onNodesDelete: onNodesDelete, onNodesChange: onNodesChange, onMoveEnd: onMoveEnd, deleteKeyCode: ["Backspace", "Delete"], proOptions: { hideAttribution: true }, noDragClassName: "wb-nodrag", noWheelClassName: "wb-nowheel", noPanClassName: "wb-nopan", fitView: true, children: [BackgroundRenderer ? (jsx(BackgroundRenderer, {})) : (jsx(Background, { id: "workbench-canvas-background", variant: BackgroundVariant.Dots, gap: 12, size: 1 })), MinimapRenderer ? jsx(MinimapRenderer, {}) : jsx(MiniMap, {}), ControlsRenderer ? jsx(ControlsRenderer, {}) : jsx(Controls, {}), DefaultContextMenuRenderer ? (jsx(DefaultContextMenuRenderer, { open: menuOpen, clientPos: menuPos, handlers: defaultContextMenuHandlers, registry: registry, nodeIds: nodeIds })) : (jsx(DefaultContextMenu, { open: menuOpen, clientPos: menuPos, handlers: defaultContextMenuHandlers, registry: registry, nodeIds: nodeIds })), !!nodeAtMenu &&
|
|
3914
|
+
nodeContextMenuHandlers &&
|
|
3915
|
+
(NodeContextMenuRenderer ? (jsx(NodeContextMenuRenderer, { open: nodeMenuOpen, clientPos: nodeMenuPos, nodeId: nodeAtMenu, handlers: nodeContextMenuHandlers, canRunPull: canRunPull, bakeableOutputs: bakeableOutputs })) : (jsx(NodeContextMenu, { open: nodeMenuOpen, clientPos: nodeMenuPos, nodeId: nodeAtMenu, handlers: nodeContextMenuHandlers, canRunPull: canRunPull, bakeableOutputs: bakeableOutputs })))] }) }) }));
|
|
3813
3916
|
});
|
|
3814
3917
|
|
|
3815
3918
|
function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, example, onExampleChange, engine, onEngineChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, overrides, onInit, onChange, }) {
|
|
@@ -4340,5 +4443,5 @@ function WorkbenchStudio({ engine, onEngineChange, example, onExampleChange, bac
|
|
|
4340
4443
|
return (jsx(WorkbenchProvider, { wb: wb, runner: runner, registry: registry, setRegistry: setRegistry, overrides: overrides, uiVersion: uiVersion, children: jsx(WorkbenchStudioCanvas, { setRegistry: setRegistry, autoScroll: autoScroll, onAutoScrollChange: onAutoScrollChange, example: example, onExampleChange: onExampleChange, engine: engine, onEngineChange: onEngineChange, backendKind: backendKind, onBackendKindChange: onBackendKindChangeWithDispose, httpBaseUrl: httpBaseUrl, onHttpBaseUrlChange: onHttpBaseUrlChange, wsUrl: wsUrl, onWsUrlChange: onWsUrlChange, debug: debug, onDebugChange: onDebugChange, showValues: showValues, onShowValuesChange: onShowValuesChange, hideWorkbench: hideWorkbench, onHideWorkbenchChange: onHideWorkbenchChange, overrides: overrides, onInit: onInit, onChange: onChange }) }));
|
|
4341
4444
|
}
|
|
4342
4445
|
|
|
4343
|
-
export { AbstractWorkbench, CLIWorkbench, DefaultNode, DefaultNodeContent, DefaultNodeHeader, DefaultUIExtensionRegistry, InMemoryWorkbench, Inspector, LocalGraphRunner, NodeHandles, RemoteGraphRunner, WorkbenchCanvas, WorkbenchContext, WorkbenchProvider, WorkbenchStudio, computeEffectiveHandles, countVisibleHandles, download, estimateNodeSize, formatDataUrlAsLabel, formatDeclaredTypeSignature, getHandleClassName, getNodeBorderClassNames, layoutNode, preformatValueForDisplay, prettyHandle, resolveOutputDisplay, summarizeDeep, toReactFlow, upload, useQueryParamBoolean, useQueryParamString, useThrottledValue, useWorkbenchBridge, useWorkbenchContext, useWorkbenchGraphTick, useWorkbenchGraphUiTick, useWorkbenchVersionTick };
|
|
4446
|
+
export { AbstractWorkbench, CLIWorkbench, DefaultNode, DefaultNodeContent, DefaultNodeHeader, DefaultUIExtensionRegistry, InMemoryWorkbench, Inspector, LocalGraphRunner, NodeHandles, RemoteGraphRunner, WorkbenchCanvas, WorkbenchContext, WorkbenchProvider, WorkbenchStudio, computeEffectiveHandles, countVisibleHandles, createHandleBounds, createHandleLayout, download, estimateNodeSize, formatDataUrlAsLabel, formatDeclaredTypeSignature, getHandleBoundsX, getHandleBoundsY, getHandleClassName, getHandleLayoutY, getNodeBorderClassNames, layoutNode, preformatValueForDisplay, prettyHandle, resolveOutputDisplay, summarizeDeep, toReactFlow, upload, useQueryParamBoolean, useQueryParamString, useThrottledValue, useWorkbenchBridge, useWorkbenchContext, useWorkbenchGraphTick, useWorkbenchGraphUiTick, useWorkbenchVersionTick };
|
|
4344
4447
|
//# sourceMappingURL=index.js.map
|