@bian-womp/spark-workbench 0.2.78 → 0.2.79
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 +346 -283
- package/lib/cjs/index.cjs.map +1 -1
- package/lib/cjs/src/adapters/cli/index.d.ts +1 -1
- package/lib/cjs/src/adapters/cli/index.d.ts.map +1 -1
- package/lib/cjs/src/core/AbstractWorkbench.d.ts +1 -1
- package/lib/cjs/src/core/AbstractWorkbench.d.ts.map +1 -1
- package/lib/cjs/src/core/InMemoryWorkbench.d.ts +45 -2
- package/lib/cjs/src/core/InMemoryWorkbench.d.ts.map +1 -1
- package/lib/cjs/src/core/contracts.d.ts +4 -2
- package/lib/cjs/src/core/contracts.d.ts.map +1 -1
- package/lib/cjs/src/misc/Inspector.d.ts.map +1 -1
- package/lib/cjs/src/misc/WorkbenchCanvas.d.ts.map +1 -1
- package/lib/cjs/src/misc/WorkbenchStudio.d.ts.map +1 -1
- package/lib/cjs/src/misc/context/WorkbenchContext.d.ts +0 -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/context-menu/ContextMenuHelpers.d.ts.map +1 -1
- package/lib/cjs/src/misc/hooks.d.ts.map +1 -1
- package/lib/cjs/src/misc/load.d.ts.map +1 -1
- package/lib/esm/index.js +347 -284
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/src/adapters/cli/index.d.ts +1 -1
- package/lib/esm/src/adapters/cli/index.d.ts.map +1 -1
- package/lib/esm/src/core/AbstractWorkbench.d.ts +1 -1
- package/lib/esm/src/core/AbstractWorkbench.d.ts.map +1 -1
- package/lib/esm/src/core/InMemoryWorkbench.d.ts +45 -2
- package/lib/esm/src/core/InMemoryWorkbench.d.ts.map +1 -1
- package/lib/esm/src/core/contracts.d.ts +4 -2
- package/lib/esm/src/core/contracts.d.ts.map +1 -1
- package/lib/esm/src/misc/Inspector.d.ts.map +1 -1
- package/lib/esm/src/misc/WorkbenchCanvas.d.ts.map +1 -1
- package/lib/esm/src/misc/WorkbenchStudio.d.ts.map +1 -1
- package/lib/esm/src/misc/context/WorkbenchContext.d.ts +0 -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/context-menu/ContextMenuHelpers.d.ts.map +1 -1
- package/lib/esm/src/misc/hooks.d.ts.map +1 -1
- package/lib/esm/src/misc/load.d.ts.map +1 -1
- package/package.json +4 -4
package/lib/esm/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { generateId, GraphBuilder, createEngine, StepEngine, PullEngine, BatchedEngine,
|
|
1
|
+
import { generateId, GraphBuilder, getTypedOutputValue, isTypedOutput, getInputTypeId, createEngine, StepEngine, PullEngine, BatchedEngine, getTypedOutputTypeId, isInputPrivate, createSimpleGraphRegistry, createSimpleGraphDef, createAsyncGraphDef, createAsyncGraphRegistry, createProgressGraphDef, createProgressGraphRegistry, createValidationGraphDef, createValidationGraphRegistry } from '@bian-womp/spark-graph';
|
|
2
2
|
import lod from 'lodash';
|
|
3
3
|
import { RuntimeApiClient } from '@bian-womp/spark-remote';
|
|
4
4
|
import { Position, Handle, useUpdateNodeInternals, useReactFlow, ReactFlowProvider, ReactFlow, Background, BackgroundVariant, MiniMap, Controls } from '@xyflow/react';
|
|
@@ -121,7 +121,7 @@ class AbstractWorkbench {
|
|
|
121
121
|
class InMemoryWorkbench extends AbstractWorkbench {
|
|
122
122
|
constructor() {
|
|
123
123
|
super(...arguments);
|
|
124
|
-
this.
|
|
124
|
+
this._def = { nodes: [], edges: [] };
|
|
125
125
|
this.listeners = new Map();
|
|
126
126
|
this.positions = {};
|
|
127
127
|
this.selection = {
|
|
@@ -133,40 +133,40 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
133
133
|
this.historyState = undefined;
|
|
134
134
|
this.copiedData = null;
|
|
135
135
|
}
|
|
136
|
+
get def() {
|
|
137
|
+
return this._def;
|
|
138
|
+
}
|
|
136
139
|
setRegistry(registry) {
|
|
137
140
|
this.registry = registry;
|
|
138
141
|
}
|
|
139
142
|
async load(def) {
|
|
140
|
-
this.
|
|
143
|
+
this._def = { nodes: [...def.nodes], edges: [...def.edges] };
|
|
141
144
|
if (this.layout) {
|
|
142
|
-
const { positions } = await this.layout.layout(this.
|
|
145
|
+
const { positions } = await this.layout.layout(this._def);
|
|
143
146
|
this.positions = positions;
|
|
144
147
|
}
|
|
145
|
-
const defNodeIds = new Set(this.
|
|
146
|
-
const defEdgeIds = new Set(this.
|
|
148
|
+
const defNodeIds = new Set(this._def.nodes.map((n) => n.nodeId));
|
|
149
|
+
const defEdgeIds = new Set(this._def.edges.map((e) => e.id));
|
|
147
150
|
const filteredPositions = Object.fromEntries(Object.entries(this.positions).filter(([id]) => defNodeIds.has(id)));
|
|
148
151
|
const filteredNodes = this.selection.nodes.filter((id) => defNodeIds.has(id));
|
|
149
152
|
const filteredEdges = this.selection.edges.filter((id) => defEdgeIds.has(id));
|
|
150
153
|
this.positions = filteredPositions;
|
|
151
154
|
this.selection = { nodes: filteredNodes, edges: filteredEdges };
|
|
152
|
-
this.emit("graphChanged", { def: this.
|
|
155
|
+
this.emit("graphChanged", { def: this._def });
|
|
153
156
|
this.refreshValidation();
|
|
154
157
|
}
|
|
155
|
-
export() {
|
|
156
|
-
return this.def;
|
|
157
|
-
}
|
|
158
158
|
refreshValidation() {
|
|
159
159
|
this.emit("validationChanged", this.validate());
|
|
160
160
|
}
|
|
161
161
|
validate() {
|
|
162
162
|
if (this.registry) {
|
|
163
163
|
const builder = new GraphBuilder(this.registry);
|
|
164
|
-
const report = builder.validate(this.
|
|
164
|
+
const report = builder.validate(this._def);
|
|
165
165
|
return report;
|
|
166
166
|
}
|
|
167
167
|
const issues = [];
|
|
168
168
|
const nodeIds = new Set();
|
|
169
|
-
for (const n of this.
|
|
169
|
+
for (const n of this._def.nodes) {
|
|
170
170
|
if (nodeIds.has(n.nodeId)) {
|
|
171
171
|
issues.push({
|
|
172
172
|
level: "error",
|
|
@@ -178,7 +178,7 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
178
178
|
nodeIds.add(n.nodeId);
|
|
179
179
|
}
|
|
180
180
|
const edgeIds = new Set();
|
|
181
|
-
for (const e of this.
|
|
181
|
+
for (const e of this._def.edges) {
|
|
182
182
|
if (edgeIds.has(e.id)) {
|
|
183
183
|
issues.push({
|
|
184
184
|
level: "error",
|
|
@@ -193,15 +193,15 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
193
193
|
}
|
|
194
194
|
setInputs(nodeId, inputs, options) {
|
|
195
195
|
this.emit("graphChanged", {
|
|
196
|
-
def: this.
|
|
196
|
+
def: this._def,
|
|
197
197
|
change: { type: "setInputs", nodeId, inputs },
|
|
198
198
|
...options,
|
|
199
199
|
});
|
|
200
200
|
}
|
|
201
201
|
addNode(node, options) {
|
|
202
202
|
const id = node.nodeId ??
|
|
203
|
-
this.genId("n", new Set(this.
|
|
204
|
-
this.
|
|
203
|
+
this.genId("n", new Set(this._def.nodes.map((n) => n.nodeId)));
|
|
204
|
+
this._def.nodes.push({
|
|
205
205
|
nodeId: id,
|
|
206
206
|
typeId: node.typeId,
|
|
207
207
|
params: node.params,
|
|
@@ -210,7 +210,7 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
210
210
|
if (node.position)
|
|
211
211
|
this.positions[id] = node.position;
|
|
212
212
|
this.emit("graphChanged", {
|
|
213
|
-
def: this.
|
|
213
|
+
def: this._def,
|
|
214
214
|
change: {
|
|
215
215
|
type: "addNode",
|
|
216
216
|
nodeId: id,
|
|
@@ -223,26 +223,26 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
223
223
|
return id;
|
|
224
224
|
}
|
|
225
225
|
removeNode(nodeId, options) {
|
|
226
|
-
this.
|
|
227
|
-
this.
|
|
226
|
+
this._def.nodes = this._def.nodes.filter((n) => n.nodeId !== nodeId);
|
|
227
|
+
this._def.edges = this._def.edges.filter((e) => e.source.nodeId !== nodeId && e.target.nodeId !== nodeId);
|
|
228
228
|
delete this.positions[nodeId];
|
|
229
229
|
this.emit("graphChanged", {
|
|
230
|
-
def: this.
|
|
230
|
+
def: this._def,
|
|
231
231
|
change: { type: "removeNode", nodeId },
|
|
232
232
|
...options,
|
|
233
233
|
});
|
|
234
234
|
this.refreshValidation();
|
|
235
235
|
}
|
|
236
236
|
connect(edge, options) {
|
|
237
|
-
const id = edge.id ?? this.genId("e", new Set(this.
|
|
238
|
-
this.
|
|
237
|
+
const id = edge.id ?? this.genId("e", new Set(this._def.edges.map((e) => e.id)));
|
|
238
|
+
this._def.edges.push({
|
|
239
239
|
id,
|
|
240
240
|
source: { ...edge.source },
|
|
241
241
|
target: { ...edge.target },
|
|
242
242
|
typeId: edge.typeId,
|
|
243
243
|
});
|
|
244
244
|
this.emit("graphChanged", {
|
|
245
|
-
def: this.
|
|
245
|
+
def: this._def,
|
|
246
246
|
change: { type: "connect", edgeId: id },
|
|
247
247
|
...options,
|
|
248
248
|
});
|
|
@@ -250,16 +250,16 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
250
250
|
return id;
|
|
251
251
|
}
|
|
252
252
|
disconnect(edgeId, options) {
|
|
253
|
-
this.
|
|
253
|
+
this._def.edges = this._def.edges.filter((e) => e.id !== edgeId);
|
|
254
254
|
this.emit("graphChanged", {
|
|
255
|
-
def: this.
|
|
255
|
+
def: this._def,
|
|
256
256
|
change: { type: "disconnect", edgeId },
|
|
257
257
|
...options,
|
|
258
258
|
});
|
|
259
259
|
this.emit("validationChanged", this.validate());
|
|
260
260
|
}
|
|
261
261
|
updateEdgeType(edgeId, typeId) {
|
|
262
|
-
const e = this.
|
|
262
|
+
const e = this._def.edges.find((x) => x.id === edgeId);
|
|
263
263
|
if (!e)
|
|
264
264
|
return;
|
|
265
265
|
if (!typeId)
|
|
@@ -267,18 +267,18 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
267
267
|
else
|
|
268
268
|
e.typeId = typeId;
|
|
269
269
|
this.emit("graphChanged", {
|
|
270
|
-
def: this.
|
|
270
|
+
def: this._def,
|
|
271
271
|
change: { type: "updateEdgeType", edgeId, typeId },
|
|
272
272
|
});
|
|
273
273
|
this.refreshValidation();
|
|
274
274
|
}
|
|
275
275
|
updateParams(nodeId, params) {
|
|
276
|
-
const n = this.
|
|
276
|
+
const n = this._def.nodes.find((n) => n.nodeId === nodeId);
|
|
277
277
|
if (!n)
|
|
278
278
|
return;
|
|
279
279
|
n.params = { ...(n.params ?? {}), ...params };
|
|
280
280
|
this.emit("graphChanged", {
|
|
281
|
-
def: this.
|
|
281
|
+
def: this._def,
|
|
282
282
|
change: { type: "updateParams", nodeId },
|
|
283
283
|
});
|
|
284
284
|
}
|
|
@@ -286,7 +286,7 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
286
286
|
setPositions(map, options) {
|
|
287
287
|
this.positions = { ...this.positions, ...map };
|
|
288
288
|
this.emit("graphUiChanged", {
|
|
289
|
-
def: this.
|
|
289
|
+
def: this._def,
|
|
290
290
|
change: { type: "moveNodes" },
|
|
291
291
|
...options,
|
|
292
292
|
});
|
|
@@ -294,17 +294,20 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
294
294
|
getPositions() {
|
|
295
295
|
return { ...this.positions };
|
|
296
296
|
}
|
|
297
|
-
|
|
297
|
+
setSelectionInternal(sel, options) {
|
|
298
298
|
if (lod.isEqual(this.selection, sel))
|
|
299
299
|
return;
|
|
300
300
|
this.selection = { nodes: [...sel.nodes], edges: [...sel.edges] };
|
|
301
301
|
this.emit("selectionChanged", this.selection);
|
|
302
302
|
this.emit("graphUiChanged", {
|
|
303
|
-
def: this.
|
|
303
|
+
def: this._def,
|
|
304
304
|
change: { type: "selection" },
|
|
305
305
|
...options,
|
|
306
306
|
});
|
|
307
307
|
}
|
|
308
|
+
setSelection(sel, options) {
|
|
309
|
+
this.setSelectionInternal(sel, options);
|
|
310
|
+
}
|
|
308
311
|
getSelection() {
|
|
309
312
|
return {
|
|
310
313
|
nodes: [...this.selection.nodes],
|
|
@@ -325,14 +328,14 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
325
328
|
this.disconnect(edgeId);
|
|
326
329
|
}
|
|
327
330
|
// Clear selection
|
|
328
|
-
this.
|
|
331
|
+
this.setSelectionInternal({ nodes: [], edges: [] }, options);
|
|
329
332
|
}
|
|
330
333
|
setViewport(viewport) {
|
|
331
334
|
if (lod.isEqual(this.viewport, viewport))
|
|
332
335
|
return;
|
|
333
336
|
this.viewport = { ...viewport };
|
|
334
337
|
this.emit("graphUiChanged", {
|
|
335
|
-
def: this.
|
|
338
|
+
def: this._def,
|
|
336
339
|
change: { type: "viewport" },
|
|
337
340
|
});
|
|
338
341
|
}
|
|
@@ -340,8 +343,8 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
340
343
|
return this.viewport ? { ...this.viewport } : null;
|
|
341
344
|
}
|
|
342
345
|
getUIState() {
|
|
343
|
-
const defNodeIds = new Set(this.
|
|
344
|
-
const defEdgeIds = new Set(this.
|
|
346
|
+
const defNodeIds = new Set(this._def.nodes.map((n) => n.nodeId));
|
|
347
|
+
const defEdgeIds = new Set(this._def.edges.map((e) => e.id));
|
|
345
348
|
const filteredPositions = Object.fromEntries(Object.entries(this.positions).filter(([id]) => defNodeIds.has(id)));
|
|
346
349
|
const filteredNodes = this.selection.nodes.filter((id) => defNodeIds.has(id));
|
|
347
350
|
const filteredEdges = this.selection.edges.filter((id) => defEdgeIds.has(id));
|
|
@@ -380,6 +383,7 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
380
383
|
}
|
|
381
384
|
setRuntimeState(runtime) {
|
|
382
385
|
this.runtimeState = runtime ? { ...runtime } : null;
|
|
386
|
+
this.emit("runtimeMetadataChanged", {});
|
|
383
387
|
}
|
|
384
388
|
getHistory() {
|
|
385
389
|
return this.historyState;
|
|
@@ -396,6 +400,7 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
396
400
|
const nodeMeta = current.nodes[nodeId] ?? {};
|
|
397
401
|
const updated = updater({ ...nodeMeta });
|
|
398
402
|
this.runtimeState = { nodes: { ...current.nodes, [nodeId]: updated } };
|
|
403
|
+
this.emit("runtimeMetadataChanged", { nodeId });
|
|
399
404
|
}
|
|
400
405
|
on(event, handler) {
|
|
401
406
|
if (!this.listeners.has(event))
|
|
@@ -420,11 +425,10 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
420
425
|
const selection = this.getSelection();
|
|
421
426
|
if (selection.nodes.length === 0)
|
|
422
427
|
return null;
|
|
423
|
-
const def = this.export();
|
|
424
428
|
const positions = this.getPositions();
|
|
425
429
|
const selectedNodeSet = new Set(selection.nodes);
|
|
426
430
|
// Collect nodes to copy
|
|
427
|
-
const nodesToCopy = def.nodes.filter((n) => selectedNodeSet.has(n.nodeId));
|
|
431
|
+
const nodesToCopy = this.def.nodes.filter((n) => selectedNodeSet.has(n.nodeId));
|
|
428
432
|
if (nodesToCopy.length === 0)
|
|
429
433
|
return null;
|
|
430
434
|
// Calculate bounds
|
|
@@ -448,12 +452,12 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
448
452
|
const centerY = (bounds.minY + bounds.maxY) / 2;
|
|
449
453
|
// Get inputs for each node
|
|
450
454
|
// Include values from inbound edges if those edges are selected
|
|
451
|
-
const allInputs = runner.getInputs(def);
|
|
455
|
+
const allInputs = runner.getInputs(this.def);
|
|
452
456
|
const selectedEdgeSet = new Set(selection.edges);
|
|
453
457
|
const copiedNodes = nodesToCopy.map((node) => {
|
|
454
458
|
const pos = positions[node.nodeId] || { x: 0, y: 0 };
|
|
455
459
|
// Get all inbound edges for this node
|
|
456
|
-
const inboundEdges = def.edges.filter((e) => e.target.nodeId === node.nodeId);
|
|
460
|
+
const inboundEdges = this.def.edges.filter((e) => e.target.nodeId === node.nodeId);
|
|
457
461
|
// Build set of handles that have inbound edges
|
|
458
462
|
// But only exclude handles whose edges are NOT selected
|
|
459
463
|
const inboundHandlesToExclude = new Set(inboundEdges
|
|
@@ -477,7 +481,7 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
477
481
|
};
|
|
478
482
|
});
|
|
479
483
|
// Collect edges between copied nodes
|
|
480
|
-
const copiedEdges = def.edges
|
|
484
|
+
const copiedEdges = this.def.edges
|
|
481
485
|
.filter((edge) => {
|
|
482
486
|
return (selectedNodeSet.has(edge.source.nodeId) &&
|
|
483
487
|
selectedNodeSet.has(edge.target.nodeId));
|
|
@@ -495,6 +499,193 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
495
499
|
bounds,
|
|
496
500
|
};
|
|
497
501
|
}
|
|
502
|
+
/**
|
|
503
|
+
* Duplicate all selected nodes.
|
|
504
|
+
* Returns the list of newly created node IDs.
|
|
505
|
+
* Each duplicated node is offset by 24px in both x and y directions.
|
|
506
|
+
* Copies inputs without bindings and uses copyOutputsFrom to copy outputs.
|
|
507
|
+
*/
|
|
508
|
+
duplicateSelection(runner, options) {
|
|
509
|
+
const selection = this.getSelection();
|
|
510
|
+
if (selection.nodes.length === 0)
|
|
511
|
+
return [];
|
|
512
|
+
const positions = this.getPositions();
|
|
513
|
+
const newNodes = [];
|
|
514
|
+
// Get inputs without bindings (literal values only)
|
|
515
|
+
const allInputs = runner.getInputs(this.def) || {};
|
|
516
|
+
// Duplicate each selected node
|
|
517
|
+
for (const nodeId of selection.nodes) {
|
|
518
|
+
const n = this.def.nodes.find((n) => n.nodeId === nodeId);
|
|
519
|
+
if (!n)
|
|
520
|
+
continue;
|
|
521
|
+
const pos = positions[nodeId] || { x: 0, y: 0 };
|
|
522
|
+
const inboundHandles = new Set(this.def.edges
|
|
523
|
+
.filter((e) => e.target.nodeId === nodeId)
|
|
524
|
+
.map((e) => e.target.handle));
|
|
525
|
+
const inputsWithoutBindings = Object.fromEntries(Object.entries(allInputs).filter(([handle]) => !inboundHandles.has(handle)));
|
|
526
|
+
const newNodeId = this.addNode({
|
|
527
|
+
typeId: n.typeId,
|
|
528
|
+
params: n.params,
|
|
529
|
+
position: { x: pos.x + 24, y: pos.y + 24 },
|
|
530
|
+
resolvedHandles: n.resolvedHandles,
|
|
531
|
+
}, {
|
|
532
|
+
inputs: inputsWithoutBindings,
|
|
533
|
+
copyOutputsFrom: nodeId,
|
|
534
|
+
dry: true,
|
|
535
|
+
});
|
|
536
|
+
newNodes.push(newNodeId);
|
|
537
|
+
}
|
|
538
|
+
// Select all newly duplicated nodes
|
|
539
|
+
if (newNodes.length > 0) {
|
|
540
|
+
this.setSelectionInternal({ nodes: newNodes, edges: [] }, options || { commit: true, reason: "duplicate-selection" });
|
|
541
|
+
}
|
|
542
|
+
return newNodes;
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Bake an output value from a node into a new node.
|
|
546
|
+
* Creates a new node based on the output type's bakeTarget configuration.
|
|
547
|
+
* Returns the ID of the last created node (or undefined if none created).
|
|
548
|
+
*/
|
|
549
|
+
async bake(registry, runner, outputValue, outputTypeId, nodePosition, options) {
|
|
550
|
+
try {
|
|
551
|
+
if (!outputTypeId || outputValue === undefined)
|
|
552
|
+
return undefined;
|
|
553
|
+
const unwrap = (v) => isTypedOutput(v) ? getTypedOutputValue(v) : v;
|
|
554
|
+
const coerceIfNeeded = async (fromType, toType, value) => {
|
|
555
|
+
if (!toType || toType === fromType || !runner?.coerce)
|
|
556
|
+
return value;
|
|
557
|
+
try {
|
|
558
|
+
return await runner.coerce(fromType, toType, value);
|
|
559
|
+
}
|
|
560
|
+
catch {
|
|
561
|
+
return value;
|
|
562
|
+
}
|
|
563
|
+
};
|
|
564
|
+
const pos = nodePosition;
|
|
565
|
+
const isArray = outputTypeId.endsWith("[]");
|
|
566
|
+
const baseTypeId = isArray ? outputTypeId.slice(0, -2) : outputTypeId;
|
|
567
|
+
const tArr = isArray ? registry.types.get(outputTypeId) : undefined;
|
|
568
|
+
const tElem = registry.types.get(baseTypeId);
|
|
569
|
+
const singleTarget = !isArray ? tElem?.bakeTarget : undefined;
|
|
570
|
+
const arrTarget = isArray ? tArr?.bakeTarget : undefined;
|
|
571
|
+
const elemTarget = isArray ? tElem?.bakeTarget : undefined;
|
|
572
|
+
let newNodeId;
|
|
573
|
+
if (singleTarget) {
|
|
574
|
+
const nodeDesc = registry.nodes.get(singleTarget.nodeTypeId);
|
|
575
|
+
const inType = getInputTypeId(nodeDesc?.inputs, singleTarget.inputHandle);
|
|
576
|
+
const coerced = await coerceIfNeeded(outputTypeId, inType, unwrap(outputValue));
|
|
577
|
+
newNodeId = this.addNode({
|
|
578
|
+
typeId: singleTarget.nodeTypeId,
|
|
579
|
+
position: { x: pos.x + 180, y: pos.y },
|
|
580
|
+
}, { inputs: { [singleTarget.inputHandle]: coerced } });
|
|
581
|
+
}
|
|
582
|
+
else if (isArray && arrTarget) {
|
|
583
|
+
const nodeDesc = registry.nodes.get(arrTarget.nodeTypeId);
|
|
584
|
+
const inType = getInputTypeId(nodeDesc?.inputs, arrTarget.inputHandle);
|
|
585
|
+
const coerced = await coerceIfNeeded(outputTypeId, inType, unwrap(outputValue));
|
|
586
|
+
newNodeId = this.addNode({
|
|
587
|
+
typeId: arrTarget.nodeTypeId,
|
|
588
|
+
position: { x: pos.x + 180, y: pos.y },
|
|
589
|
+
}, { inputs: { [arrTarget.inputHandle]: coerced } });
|
|
590
|
+
}
|
|
591
|
+
else if (isArray && elemTarget) {
|
|
592
|
+
const nodeDesc = registry.nodes.get(elemTarget.nodeTypeId);
|
|
593
|
+
const inType = getInputTypeId(nodeDesc?.inputs, elemTarget.inputHandle);
|
|
594
|
+
const src = unwrap(outputValue);
|
|
595
|
+
const items = Array.isArray(src) ? src : [src];
|
|
596
|
+
const coercedItems = await Promise.all(items.map((v) => coerceIfNeeded(baseTypeId, inType, v)));
|
|
597
|
+
const COLS = 4;
|
|
598
|
+
const DX = 180;
|
|
599
|
+
const DY = 160;
|
|
600
|
+
for (let idx = 0; idx < coercedItems.length; idx++) {
|
|
601
|
+
const col = idx % COLS;
|
|
602
|
+
const row = Math.floor(idx / COLS);
|
|
603
|
+
newNodeId = this.addNode({
|
|
604
|
+
typeId: elemTarget.nodeTypeId,
|
|
605
|
+
position: { x: pos.x + (col + 1) * DX, y: pos.y + row * DY },
|
|
606
|
+
}, { inputs: { [elemTarget.inputHandle]: coercedItems[idx] } });
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
if (newNodeId) {
|
|
610
|
+
this.setSelectionInternal({ nodes: [newNodeId], edges: [] }, options || { commit: true, reason: "bake" });
|
|
611
|
+
}
|
|
612
|
+
return newNodeId;
|
|
613
|
+
}
|
|
614
|
+
catch {
|
|
615
|
+
return undefined;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Duplicate a single node.
|
|
620
|
+
* Returns the ID of the newly created node.
|
|
621
|
+
* The duplicated node is offset by 24px in both x and y directions.
|
|
622
|
+
* Copies inputs without bindings and uses copyOutputsFrom to copy outputs.
|
|
623
|
+
*/
|
|
624
|
+
duplicateNode(nodeId, runner, options) {
|
|
625
|
+
const n = this.def.nodes.find((n) => n.nodeId === nodeId);
|
|
626
|
+
if (!n)
|
|
627
|
+
return undefined;
|
|
628
|
+
const pos = this.getPositions()[nodeId] || { x: 0, y: 0 };
|
|
629
|
+
// Get inputs without bindings (literal values only)
|
|
630
|
+
const allInputs = runner.getInputs(this.def)[nodeId] || {};
|
|
631
|
+
const inboundHandles = new Set(this.def.edges
|
|
632
|
+
.filter((e) => e.target.nodeId === nodeId)
|
|
633
|
+
.map((e) => e.target.handle));
|
|
634
|
+
const inputsWithoutBindings = Object.fromEntries(Object.entries(allInputs).filter(([handle]) => !inboundHandles.has(handle)));
|
|
635
|
+
const newNodeId = this.addNode({
|
|
636
|
+
typeId: n.typeId,
|
|
637
|
+
params: n.params,
|
|
638
|
+
position: { x: pos.x + 24, y: pos.y + 24 },
|
|
639
|
+
resolvedHandles: n.resolvedHandles,
|
|
640
|
+
}, {
|
|
641
|
+
inputs: inputsWithoutBindings,
|
|
642
|
+
copyOutputsFrom: nodeId,
|
|
643
|
+
dry: true,
|
|
644
|
+
});
|
|
645
|
+
// Select the newly duplicated node
|
|
646
|
+
this.setSelectionInternal({ nodes: [newNodeId], edges: [] }, options || { commit: true, reason: "duplicate-node" });
|
|
647
|
+
return newNodeId;
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Duplicate a node and all its incoming edges.
|
|
651
|
+
* Returns the ID of the newly created node.
|
|
652
|
+
* The duplicated node is offset by 24px in both x and y directions.
|
|
653
|
+
* All incoming edges are duplicated to point to the new node.
|
|
654
|
+
*/
|
|
655
|
+
duplicateNodeWithEdges(nodeId, runner, options) {
|
|
656
|
+
const n = this.def.nodes.find((n) => n.nodeId === nodeId);
|
|
657
|
+
if (!n)
|
|
658
|
+
return undefined;
|
|
659
|
+
const pos = this.getPositions()[nodeId] || { x: 0, y: 0 };
|
|
660
|
+
// Get all inputs (including those with bindings, since edges will be duplicated)
|
|
661
|
+
const inputs = runner.getInputs(this.def)[nodeId] || {};
|
|
662
|
+
// Add the duplicated node
|
|
663
|
+
const newNodeId = this.addNode({
|
|
664
|
+
typeId: n.typeId,
|
|
665
|
+
params: n.params,
|
|
666
|
+
position: { x: pos.x + 24, y: pos.y + 24 },
|
|
667
|
+
resolvedHandles: n.resolvedHandles,
|
|
668
|
+
}, {
|
|
669
|
+
inputs,
|
|
670
|
+
copyOutputsFrom: nodeId,
|
|
671
|
+
dry: true,
|
|
672
|
+
});
|
|
673
|
+
// Find all incoming edges (edges where target is the original node)
|
|
674
|
+
const incomingEdges = this.def.edges.filter((e) => e.target.nodeId === nodeId);
|
|
675
|
+
// Duplicate each incoming edge to point to the new node
|
|
676
|
+
for (const edge of incomingEdges) {
|
|
677
|
+
this.connect({
|
|
678
|
+
source: edge.source, // Keep the same source
|
|
679
|
+
target: { nodeId: newNodeId, handle: edge.target.handle }, // Point to new node
|
|
680
|
+
typeId: edge.typeId,
|
|
681
|
+
}, { dry: true });
|
|
682
|
+
}
|
|
683
|
+
// Select the newly duplicated node
|
|
684
|
+
if (newNodeId) {
|
|
685
|
+
this.setSelectionInternal({ nodes: [newNodeId], edges: [] }, options || { commit: true, reason: "duplicate-node-with-edges" });
|
|
686
|
+
}
|
|
687
|
+
return newNodeId;
|
|
688
|
+
}
|
|
498
689
|
/**
|
|
499
690
|
* Paste copied graph data at the specified center position.
|
|
500
691
|
* Returns the mapping from original node IDs to new node IDs.
|
|
@@ -540,7 +731,7 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
540
731
|
}
|
|
541
732
|
}
|
|
542
733
|
// Select the newly pasted nodes
|
|
543
|
-
this.
|
|
734
|
+
this.setSelectionInternal({ nodes: Array.from(nodeIdMap.values()), edges: edgeIds }, options);
|
|
544
735
|
return { nodeIdMap, edgeIds };
|
|
545
736
|
}
|
|
546
737
|
/**
|
|
@@ -565,8 +756,8 @@ class CLIWorkbench {
|
|
|
565
756
|
async load(def) {
|
|
566
757
|
await this.wb.load(def);
|
|
567
758
|
}
|
|
568
|
-
print(
|
|
569
|
-
const d =
|
|
759
|
+
print(options) {
|
|
760
|
+
const d = this.wb.def;
|
|
570
761
|
const detail = !!options?.detail;
|
|
571
762
|
const lines = [];
|
|
572
763
|
lines.push(`Nodes (${d.nodes.length})`);
|
|
@@ -1935,8 +2126,7 @@ function useWorkbenchBridge(wb) {
|
|
|
1935
2126
|
if (!params.sourceHandle || !params.targetHandle)
|
|
1936
2127
|
return;
|
|
1937
2128
|
// Prevent duplicate edges between the same endpoints
|
|
1938
|
-
const
|
|
1939
|
-
const exists = def.edges.some((e) => e.source.nodeId === params.source &&
|
|
2129
|
+
const exists = wb.def.edges.some((e) => e.source.nodeId === params.source &&
|
|
1940
2130
|
e.source.handle === params.sourceHandle &&
|
|
1941
2131
|
e.target.nodeId === params.target &&
|
|
1942
2132
|
e.target.handle === params.targetHandle);
|
|
@@ -1986,7 +2176,10 @@ function useWorkbenchBridge(wb) {
|
|
|
1986
2176
|
}
|
|
1987
2177
|
}
|
|
1988
2178
|
if (selectionChanged) {
|
|
1989
|
-
wb.setSelection({
|
|
2179
|
+
wb.setSelection({
|
|
2180
|
+
nodes: Array.from(nextNodeIds),
|
|
2181
|
+
edges: current.edges,
|
|
2182
|
+
});
|
|
1990
2183
|
}
|
|
1991
2184
|
if (Object.keys(positions).length > 0) {
|
|
1992
2185
|
wb.setPositions(positions, { commit });
|
|
@@ -2021,7 +2214,10 @@ function useWorkbenchBridge(wb) {
|
|
|
2021
2214
|
}
|
|
2022
2215
|
}
|
|
2023
2216
|
if (selectionChanged) {
|
|
2024
|
-
wb.setSelection({
|
|
2217
|
+
wb.setSelection({
|
|
2218
|
+
nodes: current.nodes,
|
|
2219
|
+
edges: Array.from(nextEdgeIds),
|
|
2220
|
+
});
|
|
2025
2221
|
}
|
|
2026
2222
|
}, [wb]);
|
|
2027
2223
|
const onNodesDelete = useCallback((nodes) => {
|
|
@@ -2467,7 +2663,6 @@ function isSnapshotPayload(parsed) {
|
|
|
2467
2663
|
}
|
|
2468
2664
|
async function download(wb, runner) {
|
|
2469
2665
|
try {
|
|
2470
|
-
const def = wb.export();
|
|
2471
2666
|
const fullUiState = wb.getUIState();
|
|
2472
2667
|
const uiState = excludeViewportFromUIState(fullUiState);
|
|
2473
2668
|
const runtimeState = wb.getRuntimeState();
|
|
@@ -2476,7 +2671,7 @@ async function download(wb, runner) {
|
|
|
2476
2671
|
const fullSnapshot = await runner.snapshotFull();
|
|
2477
2672
|
snapshot = {
|
|
2478
2673
|
...fullSnapshot,
|
|
2479
|
-
def,
|
|
2674
|
+
def: wb.def,
|
|
2480
2675
|
extData: {
|
|
2481
2676
|
...(fullSnapshot.extData || {}),
|
|
2482
2677
|
ui: Object.keys(uiState || {}).length > 0 ? uiState : undefined,
|
|
@@ -2485,9 +2680,9 @@ async function download(wb, runner) {
|
|
|
2485
2680
|
};
|
|
2486
2681
|
}
|
|
2487
2682
|
else {
|
|
2488
|
-
const inputs = runner.getInputs(def);
|
|
2683
|
+
const inputs = runner.getInputs(wb.def);
|
|
2489
2684
|
snapshot = {
|
|
2490
|
-
def,
|
|
2685
|
+
def: wb.def,
|
|
2491
2686
|
inputs,
|
|
2492
2687
|
outputs: {},
|
|
2493
2688
|
environment: {},
|
|
@@ -2526,7 +2721,7 @@ async function upload(parsed, wb, runner) {
|
|
|
2526
2721
|
}
|
|
2527
2722
|
if (runner.isRunning()) {
|
|
2528
2723
|
await runner.applySnapshotFull({
|
|
2529
|
-
def,
|
|
2724
|
+
def: wb.def,
|
|
2530
2725
|
environment,
|
|
2531
2726
|
inputs,
|
|
2532
2727
|
outputs,
|
|
@@ -2534,7 +2729,7 @@ async function upload(parsed, wb, runner) {
|
|
|
2534
2729
|
});
|
|
2535
2730
|
}
|
|
2536
2731
|
else {
|
|
2537
|
-
runner.build(wb.
|
|
2732
|
+
runner.build(wb.def);
|
|
2538
2733
|
if (inputs && typeof inputs === "object") {
|
|
2539
2734
|
for (const [nodeId, map] of Object.entries(inputs)) {
|
|
2540
2735
|
runner.setInputs(nodeId, map, { dry: true });
|
|
@@ -2630,14 +2825,13 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
2630
2825
|
const versionTick = useWorkbenchVersionTick(runner);
|
|
2631
2826
|
const valuesTick = versionTick + graphTick + graphUiTick;
|
|
2632
2827
|
// Def and IO values
|
|
2633
|
-
const
|
|
2634
|
-
const
|
|
2635
|
-
const
|
|
2636
|
-
const outputsMap = useMemo(() => runner.getOutputs(def), [runner, def, valuesTick]);
|
|
2828
|
+
const inputsMap = useMemo(() => runner.getInputs(wb.def), [runner, wb, wb.def, valuesTick]);
|
|
2829
|
+
const inputDefaultsMap = useMemo(() => runner.getInputDefaults(wb.def), [runner, wb, wb.def, valuesTick]);
|
|
2830
|
+
const outputsMap = useMemo(() => runner.getOutputs(wb.def), [runner, wb, wb.def, valuesTick]);
|
|
2637
2831
|
const outputTypesMap = useMemo(() => {
|
|
2638
2832
|
const out = {};
|
|
2639
2833
|
// Local: runtimeTypeId is not stored; derive from typed wrapper in outputsMap
|
|
2640
|
-
for (const n of def.nodes) {
|
|
2834
|
+
for (const n of wb.def.nodes) {
|
|
2641
2835
|
const effectiveHandles = computeEffectiveHandles(n, registry);
|
|
2642
2836
|
const outputsDecl = effectiveHandles.outputs;
|
|
2643
2837
|
const handles = Object.keys(outputsDecl);
|
|
@@ -2652,14 +2846,14 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
2652
2846
|
out[n.nodeId] = cur;
|
|
2653
2847
|
}
|
|
2654
2848
|
return out;
|
|
2655
|
-
}, [def, outputsMap, registry]);
|
|
2849
|
+
}, [wb, wb.def, outputsMap, registry]);
|
|
2656
2850
|
// Initialize nodes and derive invalidated status from persisted metadata
|
|
2657
2851
|
useEffect(() => {
|
|
2658
2852
|
const workbenchRuntimeState = wb.getRuntimeState() ?? { nodes: {} };
|
|
2659
2853
|
setNodeStatus((prev) => {
|
|
2660
2854
|
const next = { ...prev };
|
|
2661
2855
|
const metadata = workbenchRuntimeState;
|
|
2662
|
-
for (const n of def.nodes) {
|
|
2856
|
+
for (const n of wb.def.nodes) {
|
|
2663
2857
|
const cur = next[n.nodeId] ?? (next[n.nodeId] = {});
|
|
2664
2858
|
const nodeMeta = metadata.nodes[n.nodeId];
|
|
2665
2859
|
const updates = {};
|
|
@@ -2681,18 +2875,17 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
2681
2875
|
}
|
|
2682
2876
|
return next;
|
|
2683
2877
|
});
|
|
2684
|
-
}, [def, wb]);
|
|
2878
|
+
}, [wb.def, wb]);
|
|
2685
2879
|
// Auto layout (simple layered layout)
|
|
2686
2880
|
const runAutoLayout = useCallback(() => {
|
|
2687
|
-
const cur = wb.export();
|
|
2688
2881
|
// Build DAG layers by indegree
|
|
2689
2882
|
const indegree = {};
|
|
2690
2883
|
const adj = {};
|
|
2691
|
-
for (const n of
|
|
2884
|
+
for (const n of wb.def.nodes) {
|
|
2692
2885
|
indegree[n.nodeId] = 0;
|
|
2693
2886
|
adj[n.nodeId] = [];
|
|
2694
2887
|
}
|
|
2695
|
-
for (const e of
|
|
2888
|
+
for (const e of wb.def.edges) {
|
|
2696
2889
|
indegree[e.target.nodeId] = (indegree[e.target.nodeId] ?? 0) + 1;
|
|
2697
2890
|
adj[e.source.nodeId].push(e.target.nodeId);
|
|
2698
2891
|
}
|
|
@@ -2723,7 +2916,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
2723
2916
|
let maxWidth = 0;
|
|
2724
2917
|
const heights = {};
|
|
2725
2918
|
for (const id of layer) {
|
|
2726
|
-
const node =
|
|
2919
|
+
const node = wb.def.nodes.find((n) => n.nodeId === id);
|
|
2727
2920
|
if (!node)
|
|
2728
2921
|
continue;
|
|
2729
2922
|
// Prefer showValues sizing similar to node rendering
|
|
@@ -2749,26 +2942,26 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
2749
2942
|
curX += maxWidth + H_GAP;
|
|
2750
2943
|
}
|
|
2751
2944
|
wb.setPositions(pos, { commit: true, reason: "auto-layout" });
|
|
2752
|
-
}, [wb, registry, overrides?.getDefaultNodeSize]);
|
|
2945
|
+
}, [wb, wb.def, registry, overrides?.getDefaultNodeSize]);
|
|
2753
2946
|
const updateEdgeType = useCallback((edgeId, typeId) => wb.updateEdgeType(edgeId, typeId), [wb]);
|
|
2754
2947
|
const triggerExternal = useCallback((nodeId, event) => runner.triggerExternal(nodeId, event), [runner]);
|
|
2755
2948
|
// Helper to save runtime metadata and UI state to extData
|
|
2756
|
-
const saveUiRuntimeMetadata = useCallback(async () => {
|
|
2949
|
+
const saveUiRuntimeMetadata = useCallback(async (workbench, graphRunner) => {
|
|
2757
2950
|
try {
|
|
2758
|
-
const current =
|
|
2951
|
+
const current = workbench.getRuntimeState() ?? { nodes: {} };
|
|
2759
2952
|
const metadata = { nodes: { ...current.nodes } };
|
|
2760
2953
|
// Clean up metadata for nodes that no longer exist
|
|
2761
|
-
const nodeIds = new Set(def.nodes.map((n) => n.nodeId));
|
|
2954
|
+
const nodeIds = new Set(workbench.def.nodes.map((n) => n.nodeId));
|
|
2762
2955
|
for (const nodeId of Object.keys(metadata.nodes)) {
|
|
2763
2956
|
if (!nodeIds.has(nodeId)) {
|
|
2764
2957
|
delete metadata.nodes[nodeId];
|
|
2765
2958
|
}
|
|
2766
2959
|
}
|
|
2767
2960
|
// Save cleaned metadata to workbench state
|
|
2768
|
-
|
|
2769
|
-
const fullUiState =
|
|
2961
|
+
workbench.setRuntimeState(metadata);
|
|
2962
|
+
const fullUiState = workbench.getUIState();
|
|
2770
2963
|
const uiWithoutViewport = excludeViewportFromUIState(fullUiState);
|
|
2771
|
-
await
|
|
2964
|
+
await graphRunner.setExtData?.({
|
|
2772
2965
|
...(Object.keys(uiWithoutViewport || {}).length > 0
|
|
2773
2966
|
? { ui: uiWithoutViewport }
|
|
2774
2967
|
: {}),
|
|
@@ -2778,7 +2971,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
2778
2971
|
catch (err) {
|
|
2779
2972
|
console.warn("[WorkbenchContext] Failed to save runtime metadata:", err);
|
|
2780
2973
|
}
|
|
2781
|
-
}, [
|
|
2974
|
+
}, []);
|
|
2782
2975
|
// Subscribe to runner/workbench events
|
|
2783
2976
|
useEffect(() => {
|
|
2784
2977
|
const add = (source, type) => (payload) => setEvents((prev) => {
|
|
@@ -2804,9 +2997,8 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
2804
2997
|
});
|
|
2805
2998
|
// Helper to apply resolved handles from event payload to workbench
|
|
2806
2999
|
const applyResolvedHandles = (resolvedHandles) => {
|
|
2807
|
-
const cur = wb.export();
|
|
2808
3000
|
let changed = false;
|
|
2809
|
-
for (const n of
|
|
3001
|
+
for (const n of wb.def.nodes) {
|
|
2810
3002
|
const updated = resolvedHandles[n.nodeId];
|
|
2811
3003
|
if (updated) {
|
|
2812
3004
|
const before = JSON.stringify(n.resolvedHandles || {});
|
|
@@ -3095,6 +3287,9 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
3095
3287
|
else if (changeType === "updateEdgeType") {
|
|
3096
3288
|
reason = "update-edge-type";
|
|
3097
3289
|
}
|
|
3290
|
+
else if (changeType === "setInputs") {
|
|
3291
|
+
reason = "set-inputs";
|
|
3292
|
+
}
|
|
3098
3293
|
}
|
|
3099
3294
|
if (event.change?.type === "setInputs") {
|
|
3100
3295
|
const { nodeId, inputs } = event.change;
|
|
@@ -3102,7 +3297,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
3102
3297
|
}
|
|
3103
3298
|
if (!runner.isRunning()) {
|
|
3104
3299
|
if (event.commit) {
|
|
3105
|
-
await saveUiRuntimeMetadata();
|
|
3300
|
+
await saveUiRuntimeMetadata(wb, runner);
|
|
3106
3301
|
const history = await runner.commit(reason).catch((err) => {
|
|
3107
3302
|
console.error("[WorkbenchContext] Error committing:", err);
|
|
3108
3303
|
return undefined;
|
|
@@ -3135,7 +3330,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
3135
3330
|
await runner.update(event.def, { dry: event.dry });
|
|
3136
3331
|
}
|
|
3137
3332
|
if (event.commit) {
|
|
3138
|
-
await saveUiRuntimeMetadata();
|
|
3333
|
+
await saveUiRuntimeMetadata(wb, runner);
|
|
3139
3334
|
const history = await runner
|
|
3140
3335
|
.commit(event.reason ?? reason)
|
|
3141
3336
|
.catch((err) => {
|
|
@@ -3156,7 +3351,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
3156
3351
|
setSelectedNodeId(sel.nodes?.[0]);
|
|
3157
3352
|
setSelectedEdgeId(sel.edges?.[0]);
|
|
3158
3353
|
if (sel.commit) {
|
|
3159
|
-
await saveUiRuntimeMetadata();
|
|
3354
|
+
await saveUiRuntimeMetadata(wb, runner);
|
|
3160
3355
|
const history = await runner
|
|
3161
3356
|
.commit(sel.reason ?? "selection")
|
|
3162
3357
|
.catch((err) => {
|
|
@@ -3195,7 +3390,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
3195
3390
|
reason = "selection";
|
|
3196
3391
|
}
|
|
3197
3392
|
}
|
|
3198
|
-
await saveUiRuntimeMetadata();
|
|
3393
|
+
await saveUiRuntimeMetadata(wb, runner);
|
|
3199
3394
|
const history = await runner
|
|
3200
3395
|
.commit(event.reason ?? reason)
|
|
3201
3396
|
.catch((err) => {
|
|
@@ -3215,7 +3410,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
3215
3410
|
wb.setRegistry(newReg);
|
|
3216
3411
|
// Trigger a graph update so the UI revalidates with new types/enums/nodes
|
|
3217
3412
|
try {
|
|
3218
|
-
await runner.update(wb.
|
|
3413
|
+
await runner.update(wb.def);
|
|
3219
3414
|
}
|
|
3220
3415
|
catch {
|
|
3221
3416
|
console.error("Failed to update graph definition after registry changed");
|
|
@@ -3238,7 +3433,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
3238
3433
|
setNodeStatus(() => {
|
|
3239
3434
|
const next = {};
|
|
3240
3435
|
const metadata = wb.getRuntimeState() ?? { nodes: {} };
|
|
3241
|
-
for (const n of def.nodes) {
|
|
3436
|
+
for (const n of wb.def.nodes) {
|
|
3242
3437
|
const nodeMeta = metadata.nodes[n.nodeId];
|
|
3243
3438
|
next[n.nodeId] = {
|
|
3244
3439
|
activeRuns: 0,
|
|
@@ -3253,6 +3448,30 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
3253
3448
|
errorRunsRef.current = {};
|
|
3254
3449
|
}
|
|
3255
3450
|
});
|
|
3451
|
+
const offWbRuntimeMetadataChanged = wb.on("runtimeMetadataChanged", (event) => {
|
|
3452
|
+
const workbenchRuntimeState = wb.getRuntimeState() ?? { nodes: {} };
|
|
3453
|
+
setNodeStatus((prev) => {
|
|
3454
|
+
const next = { ...prev };
|
|
3455
|
+
const metadata = workbenchRuntimeState;
|
|
3456
|
+
let changed = false;
|
|
3457
|
+
// If nodeId is specified, only update that node; otherwise update all nodes
|
|
3458
|
+
const nodesToUpdate = event.nodeId
|
|
3459
|
+
? wb.def.nodes.filter((n) => n.nodeId === event.nodeId)
|
|
3460
|
+
: wb.def.nodes;
|
|
3461
|
+
for (const n of nodesToUpdate) {
|
|
3462
|
+
const cur = next[n.nodeId];
|
|
3463
|
+
if (!cur)
|
|
3464
|
+
continue;
|
|
3465
|
+
const nodeMeta = metadata.nodes[n.nodeId];
|
|
3466
|
+
const newInvalidated = computeInvalidatedFromMetadata(nodeMeta);
|
|
3467
|
+
if (cur.invalidated !== newInvalidated) {
|
|
3468
|
+
next[n.nodeId] = { ...cur, invalidated: newInvalidated };
|
|
3469
|
+
changed = true;
|
|
3470
|
+
}
|
|
3471
|
+
}
|
|
3472
|
+
return changed ? next : prev;
|
|
3473
|
+
});
|
|
3474
|
+
});
|
|
3256
3475
|
wb.refreshValidation();
|
|
3257
3476
|
return () => {
|
|
3258
3477
|
offRunnerValue();
|
|
@@ -3270,13 +3489,14 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
3270
3489
|
offRunnerRegistry();
|
|
3271
3490
|
offRunnerTransport();
|
|
3272
3491
|
offFlowViewport();
|
|
3492
|
+
offWbRuntimeMetadataChanged();
|
|
3273
3493
|
};
|
|
3274
3494
|
}, [runner, wb, setRegistry]);
|
|
3275
3495
|
const isRunning = useCallback(() => runner.isRunning(), [runner]);
|
|
3276
3496
|
const engineKind = useCallback(() => runner.getRunningEngine(), [runner]);
|
|
3277
3497
|
const start = useCallback((engine) => {
|
|
3278
3498
|
try {
|
|
3279
|
-
runner.launch(wb.
|
|
3499
|
+
runner.launch(wb.def, { engine });
|
|
3280
3500
|
}
|
|
3281
3501
|
catch { }
|
|
3282
3502
|
}, [runner, wb]);
|
|
@@ -3351,7 +3571,6 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
3351
3571
|
runner,
|
|
3352
3572
|
registry,
|
|
3353
3573
|
setRegistry,
|
|
3354
|
-
def,
|
|
3355
3574
|
selectedNodeId,
|
|
3356
3575
|
selectedEdgeId,
|
|
3357
3576
|
setSelection,
|
|
@@ -3392,7 +3611,6 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
3392
3611
|
runner,
|
|
3393
3612
|
registry,
|
|
3394
3613
|
setRegistry,
|
|
3395
|
-
def,
|
|
3396
3614
|
selectedNodeId,
|
|
3397
3615
|
selectedEdgeId,
|
|
3398
3616
|
setSelection,
|
|
@@ -3432,137 +3650,23 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
3432
3650
|
}
|
|
3433
3651
|
|
|
3434
3652
|
function createNodeContextMenuHandlers(nodeId, wb, runner, registry, outputsMap, outputTypesMap, onClose, getDefaultNodeSize, onCopyResult) {
|
|
3435
|
-
const doBake = async (handleId) => {
|
|
3436
|
-
try {
|
|
3437
|
-
const typeId = outputTypesMap?.[nodeId]?.[handleId];
|
|
3438
|
-
const raw = outputsMap?.[nodeId]?.[handleId];
|
|
3439
|
-
let newNodeId;
|
|
3440
|
-
if (!typeId || raw === undefined)
|
|
3441
|
-
return;
|
|
3442
|
-
const unwrap = (v) => isTypedOutput(v) ? getTypedOutputValue(v) : v;
|
|
3443
|
-
const coerceIfNeeded = async (fromType, toType, value) => {
|
|
3444
|
-
if (!toType || toType === fromType || !runner?.coerce)
|
|
3445
|
-
return value;
|
|
3446
|
-
try {
|
|
3447
|
-
return await runner.coerce(fromType, toType, value);
|
|
3448
|
-
}
|
|
3449
|
-
catch {
|
|
3450
|
-
return value;
|
|
3451
|
-
}
|
|
3452
|
-
};
|
|
3453
|
-
const pos = wb.getPositions()[nodeId] || { x: 0, y: 0 };
|
|
3454
|
-
const isArray = typeId.endsWith("[]");
|
|
3455
|
-
const baseTypeId = isArray ? typeId.slice(0, -2) : typeId;
|
|
3456
|
-
const tArr = isArray ? registry.types.get(typeId) : undefined;
|
|
3457
|
-
const tElem = registry.types.get(baseTypeId);
|
|
3458
|
-
const singleTarget = !isArray ? tElem?.bakeTarget : undefined;
|
|
3459
|
-
const arrTarget = isArray ? tArr?.bakeTarget : undefined;
|
|
3460
|
-
const elemTarget = isArray ? tElem?.bakeTarget : undefined;
|
|
3461
|
-
if (singleTarget) {
|
|
3462
|
-
const nodeDesc = registry.nodes.get(singleTarget.nodeTypeId);
|
|
3463
|
-
const inType = getInputTypeId(nodeDesc?.inputs, singleTarget.inputHandle);
|
|
3464
|
-
const coerced = await coerceIfNeeded(typeId, inType, unwrap(raw));
|
|
3465
|
-
newNodeId = wb.addNode({
|
|
3466
|
-
typeId: singleTarget.nodeTypeId,
|
|
3467
|
-
position: { x: pos.x + 180, y: pos.y },
|
|
3468
|
-
}, { inputs: { [singleTarget.inputHandle]: coerced } });
|
|
3469
|
-
}
|
|
3470
|
-
else if (isArray && arrTarget) {
|
|
3471
|
-
const nodeDesc = registry.nodes.get(arrTarget.nodeTypeId);
|
|
3472
|
-
const inType = getInputTypeId(nodeDesc?.inputs, arrTarget.inputHandle);
|
|
3473
|
-
const coerced = await coerceIfNeeded(typeId, inType, unwrap(raw));
|
|
3474
|
-
newNodeId = wb.addNode({
|
|
3475
|
-
typeId: arrTarget.nodeTypeId,
|
|
3476
|
-
position: { x: pos.x + 180, y: pos.y },
|
|
3477
|
-
}, { inputs: { [arrTarget.inputHandle]: coerced } });
|
|
3478
|
-
}
|
|
3479
|
-
else if (isArray && elemTarget) {
|
|
3480
|
-
const nodeDesc = registry.nodes.get(elemTarget.nodeTypeId);
|
|
3481
|
-
const inType = getInputTypeId(nodeDesc?.inputs, elemTarget.inputHandle);
|
|
3482
|
-
const src = unwrap(raw);
|
|
3483
|
-
const items = Array.isArray(src) ? src : [src];
|
|
3484
|
-
const coercedItems = await Promise.all(items.map((v) => coerceIfNeeded(baseTypeId, inType, v)));
|
|
3485
|
-
const COLS = 4;
|
|
3486
|
-
const DX = 180;
|
|
3487
|
-
const DY = 160;
|
|
3488
|
-
for (let idx = 0; idx < coercedItems.length; idx++) {
|
|
3489
|
-
const col = idx % COLS;
|
|
3490
|
-
const row = Math.floor(idx / COLS);
|
|
3491
|
-
newNodeId = wb.addNode({
|
|
3492
|
-
typeId: elemTarget.nodeTypeId,
|
|
3493
|
-
position: { x: pos.x + (col + 1) * DX, y: pos.y + row * DY },
|
|
3494
|
-
}, { inputs: { [elemTarget.inputHandle]: coercedItems[idx] } });
|
|
3495
|
-
}
|
|
3496
|
-
}
|
|
3497
|
-
if (newNodeId) {
|
|
3498
|
-
wb.setSelection({ nodes: [newNodeId], edges: [] }, { commit: true, reason: "bake" });
|
|
3499
|
-
}
|
|
3500
|
-
}
|
|
3501
|
-
catch { }
|
|
3502
|
-
};
|
|
3503
3653
|
return {
|
|
3504
3654
|
onDelete: () => {
|
|
3505
3655
|
wb.removeNode(nodeId, { commit: true });
|
|
3506
3656
|
onClose();
|
|
3507
3657
|
},
|
|
3508
3658
|
onDuplicate: async () => {
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
return onClose();
|
|
3513
|
-
const pos = wb.getPositions()[nodeId] || { x: 0, y: 0 };
|
|
3514
|
-
const inboundHandles = new Set(def.edges
|
|
3515
|
-
.filter((e) => e.target.nodeId === nodeId)
|
|
3516
|
-
.map((e) => e.target.handle));
|
|
3517
|
-
const allInputs = runner.getInputs(def)[nodeId] || {};
|
|
3518
|
-
const inputsWithoutBindings = Object.fromEntries(Object.entries(allInputs).filter(([handle]) => !inboundHandles.has(handle)));
|
|
3519
|
-
const newNodeId = wb.addNode({
|
|
3520
|
-
typeId: n.typeId,
|
|
3521
|
-
params: n.params,
|
|
3522
|
-
position: { x: pos.x + 24, y: pos.y + 24 },
|
|
3523
|
-
resolvedHandles: n.resolvedHandles,
|
|
3524
|
-
}, {
|
|
3525
|
-
inputs: inputsWithoutBindings,
|
|
3526
|
-
copyOutputsFrom: nodeId,
|
|
3527
|
-
dry: true,
|
|
3659
|
+
wb.duplicateNode(nodeId, runner, {
|
|
3660
|
+
commit: true,
|
|
3661
|
+
reason: "duplicate-node",
|
|
3528
3662
|
});
|
|
3529
|
-
// Select the newly duplicated node
|
|
3530
|
-
wb.setSelection({ nodes: [newNodeId], edges: [] }, { commit: true, reason: "duplicate" });
|
|
3531
3663
|
onClose();
|
|
3532
3664
|
},
|
|
3533
3665
|
onDuplicateWithEdges: async () => {
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
return onClose();
|
|
3538
|
-
const pos = wb.getPositions()[nodeId] || { x: 0, y: 0 };
|
|
3539
|
-
// Get inputs without bindings (literal values only)
|
|
3540
|
-
const inputs = runner.getInputs(def)[nodeId] || {};
|
|
3541
|
-
// Add the duplicated node
|
|
3542
|
-
const newNodeId = wb.addNode({
|
|
3543
|
-
typeId: n.typeId,
|
|
3544
|
-
params: n.params,
|
|
3545
|
-
position: { x: pos.x + 24, y: pos.y + 24 },
|
|
3546
|
-
resolvedHandles: n.resolvedHandles,
|
|
3547
|
-
}, {
|
|
3548
|
-
inputs,
|
|
3549
|
-
copyOutputsFrom: nodeId,
|
|
3550
|
-
dry: true,
|
|
3666
|
+
wb.duplicateNodeWithEdges(nodeId, runner, {
|
|
3667
|
+
commit: true,
|
|
3668
|
+
reason: "duplicate-node-with-edges",
|
|
3551
3669
|
});
|
|
3552
|
-
// Find all incoming edges (edges where target is the original node)
|
|
3553
|
-
const incomingEdges = def.edges.filter((e) => e.target.nodeId === nodeId);
|
|
3554
|
-
// Duplicate each incoming edge to point to the new node
|
|
3555
|
-
for (const edge of incomingEdges) {
|
|
3556
|
-
wb.connect({
|
|
3557
|
-
source: edge.source, // Keep the same source
|
|
3558
|
-
target: { nodeId: newNodeId, handle: edge.target.handle }, // Point to new node
|
|
3559
|
-
typeId: edge.typeId,
|
|
3560
|
-
}, { dry: true });
|
|
3561
|
-
}
|
|
3562
|
-
// Select the newly duplicated node and edges
|
|
3563
|
-
if (newNodeId) {
|
|
3564
|
-
wb.setSelection({ nodes: [newNodeId], edges: [] }, { commit: true, reason: "duplicate-with-edges" });
|
|
3565
|
-
}
|
|
3566
3670
|
onClose();
|
|
3567
3671
|
},
|
|
3568
3672
|
onRunPull: async () => {
|
|
@@ -3573,7 +3677,13 @@ function createNodeContextMenuHandlers(nodeId, wb, runner, registry, outputsMap,
|
|
|
3573
3677
|
onClose();
|
|
3574
3678
|
},
|
|
3575
3679
|
onBake: async (handleId) => {
|
|
3576
|
-
|
|
3680
|
+
const nodePosition = wb.getPositions()[nodeId] || { x: 0, y: 0 };
|
|
3681
|
+
const typeId = outputTypesMap?.[nodeId]?.[handleId];
|
|
3682
|
+
const raw = outputsMap?.[nodeId]?.[handleId];
|
|
3683
|
+
await wb.bake(registry, runner, raw, typeId || "", nodePosition, {
|
|
3684
|
+
commit: true,
|
|
3685
|
+
reason: "bake",
|
|
3686
|
+
});
|
|
3577
3687
|
onClose();
|
|
3578
3688
|
},
|
|
3579
3689
|
onCopy: () => {
|
|
@@ -3630,42 +3740,10 @@ function createNodeCopyHandler(wb, runner, nodeId, getDefaultNodeSize, onCopyRes
|
|
|
3630
3740
|
function createSelectionContextMenuHandlers(wb, onClose, getDefaultNodeSize, onCopyResult, runner) {
|
|
3631
3741
|
const onDuplicate = runner
|
|
3632
3742
|
? () => {
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
}
|
|
3638
|
-
const def = wb.export();
|
|
3639
|
-
const positions = wb.getPositions();
|
|
3640
|
-
const newNodes = [];
|
|
3641
|
-
// Duplicate each selected node
|
|
3642
|
-
for (const nodeId of selection.nodes) {
|
|
3643
|
-
const n = def.nodes.find((n) => n.nodeId === nodeId);
|
|
3644
|
-
if (!n)
|
|
3645
|
-
continue;
|
|
3646
|
-
const pos = positions[nodeId] || { x: 0, y: 0 };
|
|
3647
|
-
// Get inputs without bindings (literal values only)
|
|
3648
|
-
const allInputs = runner.getInputs(def)[nodeId] || {};
|
|
3649
|
-
const inboundHandles = new Set(def.edges
|
|
3650
|
-
.filter((e) => e.target.nodeId === nodeId)
|
|
3651
|
-
.map((e) => e.target.handle));
|
|
3652
|
-
const inputsWithoutBindings = Object.fromEntries(Object.entries(allInputs).filter(([handle]) => !inboundHandles.has(handle)));
|
|
3653
|
-
const newNodeId = wb.addNode({
|
|
3654
|
-
typeId: n.typeId,
|
|
3655
|
-
params: n.params,
|
|
3656
|
-
position: { x: pos.x + 24, y: pos.y + 24 },
|
|
3657
|
-
resolvedHandles: n.resolvedHandles,
|
|
3658
|
-
}, {
|
|
3659
|
-
inputs: inputsWithoutBindings,
|
|
3660
|
-
copyOutputsFrom: nodeId,
|
|
3661
|
-
dry: true,
|
|
3662
|
-
});
|
|
3663
|
-
newNodes.push(newNodeId);
|
|
3664
|
-
}
|
|
3665
|
-
// Select all newly duplicated nodes
|
|
3666
|
-
if (newNodes.length > 0) {
|
|
3667
|
-
wb.setSelection({ nodes: newNodes, edges: [] }, { commit: true, reason: "duplicate-selection" });
|
|
3668
|
-
}
|
|
3743
|
+
wb.duplicateSelection(runner, {
|
|
3744
|
+
commit: true,
|
|
3745
|
+
reason: "duplicate-selection",
|
|
3746
|
+
});
|
|
3669
3747
|
onClose();
|
|
3670
3748
|
}
|
|
3671
3749
|
: undefined;
|
|
@@ -3700,9 +3778,8 @@ function createDefaultContextMenuHandlers(onAddNode, onClose, onPaste, runner, g
|
|
|
3700
3778
|
const canRedo = history ? history.redoCount > 0 : undefined;
|
|
3701
3779
|
const onSelectAll = wb
|
|
3702
3780
|
? () => {
|
|
3703
|
-
const
|
|
3704
|
-
const
|
|
3705
|
-
const allEdgeIds = def.edges.map((e) => e.id);
|
|
3781
|
+
const allNodeIds = wb.def.nodes.map((n) => n.nodeId);
|
|
3782
|
+
const allEdgeIds = wb.def.edges.map((e) => e.id);
|
|
3706
3783
|
wb.setSelection({ nodes: allNodeIds, edges: allEdgeIds }, { commit: true, reason: "select-all" });
|
|
3707
3784
|
onClose();
|
|
3708
3785
|
}
|
|
@@ -3721,8 +3798,7 @@ function createDefaultContextMenuHandlers(onAddNode, onClose, onPaste, runner, g
|
|
|
3721
3798
|
}
|
|
3722
3799
|
function getBakeableOutputs(nodeId, wb, registry, outputTypesMap) {
|
|
3723
3800
|
try {
|
|
3724
|
-
const
|
|
3725
|
-
const node = def.nodes.find((n) => n.nodeId === nodeId);
|
|
3801
|
+
const node = wb.def.nodes.find((n) => n.nodeId === nodeId);
|
|
3726
3802
|
if (!node)
|
|
3727
3803
|
return [];
|
|
3728
3804
|
const desc = registry.nodes.get(node.typeId);
|
|
@@ -3821,13 +3897,13 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
|
|
|
3821
3897
|
return String(value ?? "");
|
|
3822
3898
|
}
|
|
3823
3899
|
};
|
|
3824
|
-
const {
|
|
3900
|
+
const { wb, registry, selectedNodeId, selectedEdgeId, inputsMap, inputDefaultsMap, outputsMap, outputTypesMap, nodeStatus, edgeStatus, validationByNode, validationByEdge, validationGlobal, valuesTick, updateEdgeType, systemErrors, registryErrors, inputValidationErrors, clearSystemErrors, clearRegistryErrors, clearInputValidationErrors, removeSystemError, removeRegistryError, removeInputValidationError, } = useWorkbenchContext();
|
|
3825
3901
|
const nodeValidationIssues = validationByNode.issues;
|
|
3826
3902
|
const edgeValidationIssues = validationByEdge.issues;
|
|
3827
3903
|
const nodeValidationHandles = validationByNode;
|
|
3828
3904
|
const globalValidationIssues = validationGlobal;
|
|
3829
|
-
const selectedNode = def.nodes.find((n) => n.nodeId === selectedNodeId);
|
|
3830
|
-
const selectedEdge = def.edges.find((e) => e.id === selectedEdgeId);
|
|
3905
|
+
const selectedNode = wb.def.nodes.find((n) => n.nodeId === selectedNodeId);
|
|
3906
|
+
const selectedEdge = wb.def.edges.find((e) => e.id === selectedEdgeId);
|
|
3831
3907
|
// Use computeEffectiveHandles to merge registry defaults with dynamically resolved handles
|
|
3832
3908
|
const effectiveHandles = selectedNode
|
|
3833
3909
|
? computeEffectiveHandles(selectedNode, registry)
|
|
@@ -3959,7 +4035,6 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
|
|
|
3959
4035
|
setOriginals(nextOriginals);
|
|
3960
4036
|
}, [selectedNodeId, selectedNode, registry, valuesTick]);
|
|
3961
4037
|
const widthClass = debug ? "w-[480px]" : "w-[320px]";
|
|
3962
|
-
const { wb } = useWorkbenchContext();
|
|
3963
4038
|
const deleteEdgeById = (edgeId) => {
|
|
3964
4039
|
if (!edgeId)
|
|
3965
4040
|
return;
|
|
@@ -3986,9 +4061,9 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
|
|
|
3986
4061
|
selectedNodeStatus.activeRunIds.length > 0 ? (jsxs("div", { className: "mt-1", children: [jsx("div", { className: "text-[10px] text-blue-600", children: "RunIds:" }), jsx("div", { className: "flex flex-wrap gap-1 mt-1", children: selectedNodeStatus.activeRunIds.map((runId, idx) => (jsx("span", { className: "text-[10px] px-1.5 py-0.5 bg-blue-100 border border-blue-300 rounded font-mono", children: runId }, idx))) })] })) : (jsx("div", { className: "text-[10px] text-blue-600 mt-1", children: "RunIds not available (some runs may have started without runId)" }))] })), !!selectedNodeStatus?.lastError && (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 ??
|
|
3987
4062
|
selectedNodeStatus.lastError) }))] })), jsxs("div", { className: "mb-2", children: [jsx("div", { className: "font-semibold mb-1", children: "Inputs" }), inputHandles.length === 0 ? (jsx("div", { className: "text-gray-500", children: "No inputs" })) : (inputHandles.map((h) => {
|
|
3988
4063
|
const typeId = getInputTypeId(effectiveHandles.inputs, h);
|
|
3989
|
-
const isLinked = def.edges.some((e) => e.target.nodeId === selectedNodeId &&
|
|
4064
|
+
const isLinked = wb.def.edges.some((e) => e.target.nodeId === selectedNodeId &&
|
|
3990
4065
|
e.target.handle === h);
|
|
3991
|
-
const inbound = new Set(def.edges
|
|
4066
|
+
const inbound = new Set(wb.def.edges
|
|
3992
4067
|
.filter((e) => e.target.nodeId === selectedNodeId &&
|
|
3993
4068
|
e.target.handle === h)
|
|
3994
4069
|
.map((e) => e.target.handle));
|
|
@@ -4564,10 +4639,9 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
4564
4639
|
const { nodeTypes, resolveNodeType } = useMemo(() => {
|
|
4565
4640
|
// Build nodeTypes map using UI extension registry
|
|
4566
4641
|
const custom = new Map(); // Include all types present in registry AND current graph to avoid timing issues
|
|
4567
|
-
const def = wb.export();
|
|
4568
4642
|
const ids = new Set([
|
|
4569
4643
|
...Array.from(registry.nodes.keys()),
|
|
4570
|
-
...def.nodes.map((n) => n.typeId),
|
|
4644
|
+
...wb.def.nodes.map((n) => n.typeId),
|
|
4571
4645
|
]);
|
|
4572
4646
|
for (const typeId of ids) {
|
|
4573
4647
|
const renderer = ui.getNodeRenderer(typeId);
|
|
@@ -4586,14 +4660,13 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
4586
4660
|
// Include uiVersion to recompute when custom renderers are registered
|
|
4587
4661
|
}, [wb, registry, uiVersion, ui]);
|
|
4588
4662
|
const { nodes, edges } = useMemo(() => {
|
|
4589
|
-
const def = wb.export();
|
|
4590
4663
|
const sel = wb.getSelection();
|
|
4591
4664
|
// Merge defaults with inputs for node display (defaults shown in lighter gray)
|
|
4592
4665
|
const inputsWithDefaults = {};
|
|
4593
|
-
for (const n of def.nodes) {
|
|
4666
|
+
for (const n of wb.def.nodes) {
|
|
4594
4667
|
const nodeInputs = inputsMap[n.nodeId] ?? {};
|
|
4595
4668
|
const nodeDefaults = inputDefaultsMap[n.nodeId] ?? {};
|
|
4596
|
-
const inbound = new Set(def.edges
|
|
4669
|
+
const inbound = new Set(wb.def.edges
|
|
4597
4670
|
.filter((e) => e.target.nodeId === n.nodeId)
|
|
4598
4671
|
.map((e) => e.target.handle));
|
|
4599
4672
|
const merged = { ...nodeInputs };
|
|
@@ -4606,7 +4679,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
4606
4679
|
inputsWithDefaults[n.nodeId] = merged;
|
|
4607
4680
|
}
|
|
4608
4681
|
}
|
|
4609
|
-
const out = toReactFlow(def, wb.getPositions(), registry, {
|
|
4682
|
+
const out = toReactFlow(wb.def, wb.getPositions(), registry, {
|
|
4610
4683
|
showValues,
|
|
4611
4684
|
inputs: inputsWithDefaults,
|
|
4612
4685
|
inputDefaults: inputDefaultsMap,
|
|
@@ -5130,11 +5203,11 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
5130
5203
|
});
|
|
5131
5204
|
|
|
5132
5205
|
function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, example, onExampleChange, engine, onEngineChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, overrides, onInit, onChange, }) {
|
|
5133
|
-
const { wb, runner, registry,
|
|
5206
|
+
const { wb, runner, registry, selectedNodeId, runAutoLayout } = useWorkbenchContext();
|
|
5134
5207
|
const [transportStatus, setTransportStatus] = useState({
|
|
5135
5208
|
state: "local",
|
|
5136
5209
|
});
|
|
5137
|
-
const selectedNode = def.nodes.find((n) => n.nodeId === selectedNodeId);
|
|
5210
|
+
const selectedNode = wb.def.nodes.find((n) => n.nodeId === selectedNodeId);
|
|
5138
5211
|
const effectiveHandles = selectedNode
|
|
5139
5212
|
? computeEffectiveHandles(selectedNode, registry)
|
|
5140
5213
|
: { inputs: {}, outputs: {}, inputDefaults: {} };
|
|
@@ -5162,7 +5235,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
5162
5235
|
if (evt.shiftKey && !confirm("Invalidate and re-run graph?"))
|
|
5163
5236
|
return;
|
|
5164
5237
|
try {
|
|
5165
|
-
runner.launch(wb.
|
|
5238
|
+
runner.launch(wb.def, {
|
|
5166
5239
|
engine: kind,
|
|
5167
5240
|
invalidate: evt.shiftKey,
|
|
5168
5241
|
});
|
|
@@ -5230,7 +5303,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
5230
5303
|
const setInitialGraph = async (d, inputs) => {
|
|
5231
5304
|
await wb.load(d);
|
|
5232
5305
|
try {
|
|
5233
|
-
runner.build(wb.
|
|
5306
|
+
runner.build(wb.def);
|
|
5234
5307
|
}
|
|
5235
5308
|
catch { }
|
|
5236
5309
|
if (inputs) {
|
|
@@ -5245,36 +5318,27 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
5245
5318
|
useEffect(() => {
|
|
5246
5319
|
if (!onChange)
|
|
5247
5320
|
return;
|
|
5248
|
-
const
|
|
5249
|
-
try {
|
|
5250
|
-
const cur = wb.export();
|
|
5251
|
-
const inputs = runner.getInputs(cur);
|
|
5252
|
-
onChange({ def: cur, inputs });
|
|
5253
|
-
}
|
|
5254
|
-
catch { }
|
|
5255
|
-
});
|
|
5256
|
-
const off2 = runner.on("value", () => {
|
|
5321
|
+
const offGraphChanged = wb.on("graphChanged", () => {
|
|
5257
5322
|
try {
|
|
5258
|
-
const cur = wb.
|
|
5323
|
+
const cur = wb.def;
|
|
5259
5324
|
const inputs = runner.getInputs(cur);
|
|
5260
5325
|
onChange({ def: cur, inputs });
|
|
5261
5326
|
}
|
|
5262
5327
|
catch { }
|
|
5263
5328
|
});
|
|
5264
|
-
const
|
|
5329
|
+
const offGraphUiChanged = wb.on("graphUiChanged", (evt) => {
|
|
5265
5330
|
if (!evt.commit)
|
|
5266
5331
|
return;
|
|
5267
5332
|
try {
|
|
5268
|
-
const cur = wb.
|
|
5333
|
+
const cur = wb.def;
|
|
5269
5334
|
const inputs = runner.getInputs(cur);
|
|
5270
5335
|
onChange({ def: cur, inputs });
|
|
5271
5336
|
}
|
|
5272
5337
|
catch { }
|
|
5273
5338
|
});
|
|
5274
5339
|
return () => {
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
off3();
|
|
5340
|
+
offGraphChanged();
|
|
5341
|
+
offGraphUiChanged();
|
|
5278
5342
|
};
|
|
5279
5343
|
}, [wb, runner, onChange]);
|
|
5280
5344
|
const applyExample = useCallback(async (key) => {
|
|
@@ -5297,7 +5361,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
5297
5361
|
}
|
|
5298
5362
|
await wb.load(def);
|
|
5299
5363
|
// Build a local runtime so seeded defaults are visible pre-run
|
|
5300
|
-
runner.build(wb.
|
|
5364
|
+
runner.build(wb.def);
|
|
5301
5365
|
// Set initial inputs if provided
|
|
5302
5366
|
if (inputs) {
|
|
5303
5367
|
for (const [nodeId, map] of Object.entries(inputs)) {
|
|
@@ -5382,11 +5446,10 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
5382
5446
|
// Only auto-launch for local backend; require explicit Start for remote
|
|
5383
5447
|
if (backendKind !== "local")
|
|
5384
5448
|
return;
|
|
5385
|
-
|
|
5386
|
-
if (!d.nodes || d.nodes.length === 0)
|
|
5449
|
+
if (!wb.def.nodes || wb.def.nodes.length === 0)
|
|
5387
5450
|
return;
|
|
5388
5451
|
try {
|
|
5389
|
-
runner.launch(
|
|
5452
|
+
runner.launch(wb.def, { engine: engine });
|
|
5390
5453
|
}
|
|
5391
5454
|
catch {
|
|
5392
5455
|
// ignore
|
|
@@ -5396,7 +5459,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
5396
5459
|
if (!selectedNodeId)
|
|
5397
5460
|
return;
|
|
5398
5461
|
// If selected input is wired (has inbound edge), ignore user input to respect runtime value
|
|
5399
|
-
const isLinked = def.edges.some((e) => e.target.nodeId === selectedNodeId && e.target.handle === handle);
|
|
5462
|
+
const isLinked = wb.def.edges.some((e) => e.target.nodeId === selectedNodeId && e.target.handle === handle);
|
|
5400
5463
|
if (isLinked)
|
|
5401
5464
|
return;
|
|
5402
5465
|
// If raw is undefined, pass it through to delete the input value
|
|
@@ -5481,7 +5544,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
5481
5544
|
}
|
|
5482
5545
|
}
|
|
5483
5546
|
wb.setInputs(selectedNodeId, { [handle]: value }, { commit: true });
|
|
5484
|
-
}, [selectedNodeId, def.edges, effectiveHandles, wb]);
|
|
5547
|
+
}, [selectedNodeId, wb.def.edges, effectiveHandles, wb]);
|
|
5485
5548
|
const setInput = useMemo(() => {
|
|
5486
5549
|
if (overrides?.setInput) {
|
|
5487
5550
|
return overrides.setInput(baseSetInput, {
|