@bian-womp/spark-workbench 0.2.77 → 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 +363 -330
- 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 +50 -2
- package/lib/cjs/src/core/InMemoryWorkbench.d.ts.map +1 -1
- package/lib/cjs/src/core/contracts.d.ts +8 -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 +364 -331
- 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 +50 -2
- package/lib/esm/src/core/InMemoryWorkbench.d.ts.map +1 -1
- package/lib/esm/src/core/contracts.d.ts +8 -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",
|
|
@@ -191,10 +191,17 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
191
191
|
}
|
|
192
192
|
return { ok: issues.every((i) => i.level !== "error"), issues };
|
|
193
193
|
}
|
|
194
|
+
setInputs(nodeId, inputs, options) {
|
|
195
|
+
this.emit("graphChanged", {
|
|
196
|
+
def: this._def,
|
|
197
|
+
change: { type: "setInputs", nodeId, inputs },
|
|
198
|
+
...options,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
194
201
|
addNode(node, options) {
|
|
195
202
|
const id = node.nodeId ??
|
|
196
|
-
this.genId("n", new Set(this.
|
|
197
|
-
this.
|
|
203
|
+
this.genId("n", new Set(this._def.nodes.map((n) => n.nodeId)));
|
|
204
|
+
this._def.nodes.push({
|
|
198
205
|
nodeId: id,
|
|
199
206
|
typeId: node.typeId,
|
|
200
207
|
params: node.params,
|
|
@@ -203,7 +210,7 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
203
210
|
if (node.position)
|
|
204
211
|
this.positions[id] = node.position;
|
|
205
212
|
this.emit("graphChanged", {
|
|
206
|
-
def: this.
|
|
213
|
+
def: this._def,
|
|
207
214
|
change: {
|
|
208
215
|
type: "addNode",
|
|
209
216
|
nodeId: id,
|
|
@@ -216,26 +223,26 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
216
223
|
return id;
|
|
217
224
|
}
|
|
218
225
|
removeNode(nodeId, options) {
|
|
219
|
-
this.
|
|
220
|
-
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);
|
|
221
228
|
delete this.positions[nodeId];
|
|
222
229
|
this.emit("graphChanged", {
|
|
223
|
-
def: this.
|
|
230
|
+
def: this._def,
|
|
224
231
|
change: { type: "removeNode", nodeId },
|
|
225
232
|
...options,
|
|
226
233
|
});
|
|
227
234
|
this.refreshValidation();
|
|
228
235
|
}
|
|
229
236
|
connect(edge, options) {
|
|
230
|
-
const id = edge.id ?? this.genId("e", new Set(this.
|
|
231
|
-
this.
|
|
237
|
+
const id = edge.id ?? this.genId("e", new Set(this._def.edges.map((e) => e.id)));
|
|
238
|
+
this._def.edges.push({
|
|
232
239
|
id,
|
|
233
240
|
source: { ...edge.source },
|
|
234
241
|
target: { ...edge.target },
|
|
235
242
|
typeId: edge.typeId,
|
|
236
243
|
});
|
|
237
244
|
this.emit("graphChanged", {
|
|
238
|
-
def: this.
|
|
245
|
+
def: this._def,
|
|
239
246
|
change: { type: "connect", edgeId: id },
|
|
240
247
|
...options,
|
|
241
248
|
});
|
|
@@ -243,16 +250,16 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
243
250
|
return id;
|
|
244
251
|
}
|
|
245
252
|
disconnect(edgeId, options) {
|
|
246
|
-
this.
|
|
253
|
+
this._def.edges = this._def.edges.filter((e) => e.id !== edgeId);
|
|
247
254
|
this.emit("graphChanged", {
|
|
248
|
-
def: this.
|
|
255
|
+
def: this._def,
|
|
249
256
|
change: { type: "disconnect", edgeId },
|
|
250
257
|
...options,
|
|
251
258
|
});
|
|
252
259
|
this.emit("validationChanged", this.validate());
|
|
253
260
|
}
|
|
254
261
|
updateEdgeType(edgeId, typeId) {
|
|
255
|
-
const e = this.
|
|
262
|
+
const e = this._def.edges.find((x) => x.id === edgeId);
|
|
256
263
|
if (!e)
|
|
257
264
|
return;
|
|
258
265
|
if (!typeId)
|
|
@@ -260,18 +267,18 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
260
267
|
else
|
|
261
268
|
e.typeId = typeId;
|
|
262
269
|
this.emit("graphChanged", {
|
|
263
|
-
def: this.
|
|
270
|
+
def: this._def,
|
|
264
271
|
change: { type: "updateEdgeType", edgeId, typeId },
|
|
265
272
|
});
|
|
266
273
|
this.refreshValidation();
|
|
267
274
|
}
|
|
268
275
|
updateParams(nodeId, params) {
|
|
269
|
-
const n = this.
|
|
276
|
+
const n = this._def.nodes.find((n) => n.nodeId === nodeId);
|
|
270
277
|
if (!n)
|
|
271
278
|
return;
|
|
272
279
|
n.params = { ...(n.params ?? {}), ...params };
|
|
273
280
|
this.emit("graphChanged", {
|
|
274
|
-
def: this.
|
|
281
|
+
def: this._def,
|
|
275
282
|
change: { type: "updateParams", nodeId },
|
|
276
283
|
});
|
|
277
284
|
}
|
|
@@ -279,7 +286,7 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
279
286
|
setPositions(map, options) {
|
|
280
287
|
this.positions = { ...this.positions, ...map };
|
|
281
288
|
this.emit("graphUiChanged", {
|
|
282
|
-
def: this.
|
|
289
|
+
def: this._def,
|
|
283
290
|
change: { type: "moveNodes" },
|
|
284
291
|
...options,
|
|
285
292
|
});
|
|
@@ -287,17 +294,20 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
287
294
|
getPositions() {
|
|
288
295
|
return { ...this.positions };
|
|
289
296
|
}
|
|
290
|
-
|
|
297
|
+
setSelectionInternal(sel, options) {
|
|
291
298
|
if (lod.isEqual(this.selection, sel))
|
|
292
299
|
return;
|
|
293
300
|
this.selection = { nodes: [...sel.nodes], edges: [...sel.edges] };
|
|
294
301
|
this.emit("selectionChanged", this.selection);
|
|
295
302
|
this.emit("graphUiChanged", {
|
|
296
|
-
def: this.
|
|
303
|
+
def: this._def,
|
|
297
304
|
change: { type: "selection" },
|
|
298
305
|
...options,
|
|
299
306
|
});
|
|
300
307
|
}
|
|
308
|
+
setSelection(sel, options) {
|
|
309
|
+
this.setSelectionInternal(sel, options);
|
|
310
|
+
}
|
|
301
311
|
getSelection() {
|
|
302
312
|
return {
|
|
303
313
|
nodes: [...this.selection.nodes],
|
|
@@ -318,14 +328,14 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
318
328
|
this.disconnect(edgeId);
|
|
319
329
|
}
|
|
320
330
|
// Clear selection
|
|
321
|
-
this.
|
|
331
|
+
this.setSelectionInternal({ nodes: [], edges: [] }, options);
|
|
322
332
|
}
|
|
323
333
|
setViewport(viewport) {
|
|
324
334
|
if (lod.isEqual(this.viewport, viewport))
|
|
325
335
|
return;
|
|
326
336
|
this.viewport = { ...viewport };
|
|
327
337
|
this.emit("graphUiChanged", {
|
|
328
|
-
def: this.
|
|
338
|
+
def: this._def,
|
|
329
339
|
change: { type: "viewport" },
|
|
330
340
|
});
|
|
331
341
|
}
|
|
@@ -333,8 +343,8 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
333
343
|
return this.viewport ? { ...this.viewport } : null;
|
|
334
344
|
}
|
|
335
345
|
getUIState() {
|
|
336
|
-
const defNodeIds = new Set(this.
|
|
337
|
-
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));
|
|
338
348
|
const filteredPositions = Object.fromEntries(Object.entries(this.positions).filter(([id]) => defNodeIds.has(id)));
|
|
339
349
|
const filteredNodes = this.selection.nodes.filter((id) => defNodeIds.has(id));
|
|
340
350
|
const filteredEdges = this.selection.edges.filter((id) => defEdgeIds.has(id));
|
|
@@ -373,6 +383,7 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
373
383
|
}
|
|
374
384
|
setRuntimeState(runtime) {
|
|
375
385
|
this.runtimeState = runtime ? { ...runtime } : null;
|
|
386
|
+
this.emit("runtimeMetadataChanged", {});
|
|
376
387
|
}
|
|
377
388
|
getHistory() {
|
|
378
389
|
return this.historyState;
|
|
@@ -389,6 +400,7 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
389
400
|
const nodeMeta = current.nodes[nodeId] ?? {};
|
|
390
401
|
const updated = updater({ ...nodeMeta });
|
|
391
402
|
this.runtimeState = { nodes: { ...current.nodes, [nodeId]: updated } };
|
|
403
|
+
this.emit("runtimeMetadataChanged", { nodeId });
|
|
392
404
|
}
|
|
393
405
|
on(event, handler) {
|
|
394
406
|
if (!this.listeners.has(event))
|
|
@@ -413,11 +425,10 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
413
425
|
const selection = this.getSelection();
|
|
414
426
|
if (selection.nodes.length === 0)
|
|
415
427
|
return null;
|
|
416
|
-
const def = this.export();
|
|
417
428
|
const positions = this.getPositions();
|
|
418
429
|
const selectedNodeSet = new Set(selection.nodes);
|
|
419
430
|
// Collect nodes to copy
|
|
420
|
-
const nodesToCopy = def.nodes.filter((n) => selectedNodeSet.has(n.nodeId));
|
|
431
|
+
const nodesToCopy = this.def.nodes.filter((n) => selectedNodeSet.has(n.nodeId));
|
|
421
432
|
if (nodesToCopy.length === 0)
|
|
422
433
|
return null;
|
|
423
434
|
// Calculate bounds
|
|
@@ -441,12 +452,12 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
441
452
|
const centerY = (bounds.minY + bounds.maxY) / 2;
|
|
442
453
|
// Get inputs for each node
|
|
443
454
|
// Include values from inbound edges if those edges are selected
|
|
444
|
-
const allInputs = runner.getInputs(def);
|
|
455
|
+
const allInputs = runner.getInputs(this.def);
|
|
445
456
|
const selectedEdgeSet = new Set(selection.edges);
|
|
446
457
|
const copiedNodes = nodesToCopy.map((node) => {
|
|
447
458
|
const pos = positions[node.nodeId] || { x: 0, y: 0 };
|
|
448
459
|
// Get all inbound edges for this node
|
|
449
|
-
const inboundEdges = def.edges.filter((e) => e.target.nodeId === node.nodeId);
|
|
460
|
+
const inboundEdges = this.def.edges.filter((e) => e.target.nodeId === node.nodeId);
|
|
450
461
|
// Build set of handles that have inbound edges
|
|
451
462
|
// But only exclude handles whose edges are NOT selected
|
|
452
463
|
const inboundHandlesToExclude = new Set(inboundEdges
|
|
@@ -470,7 +481,7 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
470
481
|
};
|
|
471
482
|
});
|
|
472
483
|
// Collect edges between copied nodes
|
|
473
|
-
const copiedEdges = def.edges
|
|
484
|
+
const copiedEdges = this.def.edges
|
|
474
485
|
.filter((edge) => {
|
|
475
486
|
return (selectedNodeSet.has(edge.source.nodeId) &&
|
|
476
487
|
selectedNodeSet.has(edge.target.nodeId));
|
|
@@ -488,6 +499,193 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
488
499
|
bounds,
|
|
489
500
|
};
|
|
490
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
|
+
}
|
|
491
689
|
/**
|
|
492
690
|
* Paste copied graph data at the specified center position.
|
|
493
691
|
* Returns the mapping from original node IDs to new node IDs.
|
|
@@ -533,7 +731,7 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
533
731
|
}
|
|
534
732
|
}
|
|
535
733
|
// Select the newly pasted nodes
|
|
536
|
-
this.
|
|
734
|
+
this.setSelectionInternal({ nodes: Array.from(nodeIdMap.values()), edges: edgeIds }, options);
|
|
537
735
|
return { nodeIdMap, edgeIds };
|
|
538
736
|
}
|
|
539
737
|
/**
|
|
@@ -558,8 +756,8 @@ class CLIWorkbench {
|
|
|
558
756
|
async load(def) {
|
|
559
757
|
await this.wb.load(def);
|
|
560
758
|
}
|
|
561
|
-
print(
|
|
562
|
-
const d =
|
|
759
|
+
print(options) {
|
|
760
|
+
const d = this.wb.def;
|
|
563
761
|
const detail = !!options?.detail;
|
|
564
762
|
const lines = [];
|
|
565
763
|
lines.push(`Nodes (${d.nodes.length})`);
|
|
@@ -1928,8 +2126,7 @@ function useWorkbenchBridge(wb) {
|
|
|
1928
2126
|
if (!params.sourceHandle || !params.targetHandle)
|
|
1929
2127
|
return;
|
|
1930
2128
|
// Prevent duplicate edges between the same endpoints
|
|
1931
|
-
const
|
|
1932
|
-
const exists = def.edges.some((e) => e.source.nodeId === params.source &&
|
|
2129
|
+
const exists = wb.def.edges.some((e) => e.source.nodeId === params.source &&
|
|
1933
2130
|
e.source.handle === params.sourceHandle &&
|
|
1934
2131
|
e.target.nodeId === params.target &&
|
|
1935
2132
|
e.target.handle === params.targetHandle);
|
|
@@ -1979,7 +2176,10 @@ function useWorkbenchBridge(wb) {
|
|
|
1979
2176
|
}
|
|
1980
2177
|
}
|
|
1981
2178
|
if (selectionChanged) {
|
|
1982
|
-
wb.setSelection({
|
|
2179
|
+
wb.setSelection({
|
|
2180
|
+
nodes: Array.from(nextNodeIds),
|
|
2181
|
+
edges: current.edges,
|
|
2182
|
+
});
|
|
1983
2183
|
}
|
|
1984
2184
|
if (Object.keys(positions).length > 0) {
|
|
1985
2185
|
wb.setPositions(positions, { commit });
|
|
@@ -2014,7 +2214,10 @@ function useWorkbenchBridge(wb) {
|
|
|
2014
2214
|
}
|
|
2015
2215
|
}
|
|
2016
2216
|
if (selectionChanged) {
|
|
2017
|
-
wb.setSelection({
|
|
2217
|
+
wb.setSelection({
|
|
2218
|
+
nodes: current.nodes,
|
|
2219
|
+
edges: Array.from(nextEdgeIds),
|
|
2220
|
+
});
|
|
2018
2221
|
}
|
|
2019
2222
|
}, [wb]);
|
|
2020
2223
|
const onNodesDelete = useCallback((nodes) => {
|
|
@@ -2460,7 +2663,6 @@ function isSnapshotPayload(parsed) {
|
|
|
2460
2663
|
}
|
|
2461
2664
|
async function download(wb, runner) {
|
|
2462
2665
|
try {
|
|
2463
|
-
const def = wb.export();
|
|
2464
2666
|
const fullUiState = wb.getUIState();
|
|
2465
2667
|
const uiState = excludeViewportFromUIState(fullUiState);
|
|
2466
2668
|
const runtimeState = wb.getRuntimeState();
|
|
@@ -2469,7 +2671,7 @@ async function download(wb, runner) {
|
|
|
2469
2671
|
const fullSnapshot = await runner.snapshotFull();
|
|
2470
2672
|
snapshot = {
|
|
2471
2673
|
...fullSnapshot,
|
|
2472
|
-
def,
|
|
2674
|
+
def: wb.def,
|
|
2473
2675
|
extData: {
|
|
2474
2676
|
...(fullSnapshot.extData || {}),
|
|
2475
2677
|
ui: Object.keys(uiState || {}).length > 0 ? uiState : undefined,
|
|
@@ -2478,9 +2680,9 @@ async function download(wb, runner) {
|
|
|
2478
2680
|
};
|
|
2479
2681
|
}
|
|
2480
2682
|
else {
|
|
2481
|
-
const inputs = runner.getInputs(def);
|
|
2683
|
+
const inputs = runner.getInputs(wb.def);
|
|
2482
2684
|
snapshot = {
|
|
2483
|
-
def,
|
|
2685
|
+
def: wb.def,
|
|
2484
2686
|
inputs,
|
|
2485
2687
|
outputs: {},
|
|
2486
2688
|
environment: {},
|
|
@@ -2519,7 +2721,7 @@ async function upload(parsed, wb, runner) {
|
|
|
2519
2721
|
}
|
|
2520
2722
|
if (runner.isRunning()) {
|
|
2521
2723
|
await runner.applySnapshotFull({
|
|
2522
|
-
def,
|
|
2724
|
+
def: wb.def,
|
|
2523
2725
|
environment,
|
|
2524
2726
|
inputs,
|
|
2525
2727
|
outputs,
|
|
@@ -2527,10 +2729,10 @@ async function upload(parsed, wb, runner) {
|
|
|
2527
2729
|
});
|
|
2528
2730
|
}
|
|
2529
2731
|
else {
|
|
2530
|
-
runner.build(wb.
|
|
2732
|
+
runner.build(wb.def);
|
|
2531
2733
|
if (inputs && typeof inputs === "object") {
|
|
2532
2734
|
for (const [nodeId, map] of Object.entries(inputs)) {
|
|
2533
|
-
runner.setInputs(nodeId, map);
|
|
2735
|
+
runner.setInputs(nodeId, map, { dry: true });
|
|
2534
2736
|
}
|
|
2535
2737
|
}
|
|
2536
2738
|
}
|
|
@@ -2623,14 +2825,13 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
2623
2825
|
const versionTick = useWorkbenchVersionTick(runner);
|
|
2624
2826
|
const valuesTick = versionTick + graphTick + graphUiTick;
|
|
2625
2827
|
// Def and IO values
|
|
2626
|
-
const
|
|
2627
|
-
const
|
|
2628
|
-
const
|
|
2629
|
-
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]);
|
|
2630
2831
|
const outputTypesMap = useMemo(() => {
|
|
2631
2832
|
const out = {};
|
|
2632
2833
|
// Local: runtimeTypeId is not stored; derive from typed wrapper in outputsMap
|
|
2633
|
-
for (const n of def.nodes) {
|
|
2834
|
+
for (const n of wb.def.nodes) {
|
|
2634
2835
|
const effectiveHandles = computeEffectiveHandles(n, registry);
|
|
2635
2836
|
const outputsDecl = effectiveHandles.outputs;
|
|
2636
2837
|
const handles = Object.keys(outputsDecl);
|
|
@@ -2645,14 +2846,14 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
2645
2846
|
out[n.nodeId] = cur;
|
|
2646
2847
|
}
|
|
2647
2848
|
return out;
|
|
2648
|
-
}, [def, outputsMap, registry]);
|
|
2849
|
+
}, [wb, wb.def, outputsMap, registry]);
|
|
2649
2850
|
// Initialize nodes and derive invalidated status from persisted metadata
|
|
2650
2851
|
useEffect(() => {
|
|
2651
2852
|
const workbenchRuntimeState = wb.getRuntimeState() ?? { nodes: {} };
|
|
2652
2853
|
setNodeStatus((prev) => {
|
|
2653
2854
|
const next = { ...prev };
|
|
2654
2855
|
const metadata = workbenchRuntimeState;
|
|
2655
|
-
for (const n of def.nodes) {
|
|
2856
|
+
for (const n of wb.def.nodes) {
|
|
2656
2857
|
const cur = next[n.nodeId] ?? (next[n.nodeId] = {});
|
|
2657
2858
|
const nodeMeta = metadata.nodes[n.nodeId];
|
|
2658
2859
|
const updates = {};
|
|
@@ -2674,18 +2875,17 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
2674
2875
|
}
|
|
2675
2876
|
return next;
|
|
2676
2877
|
});
|
|
2677
|
-
}, [def, wb]);
|
|
2878
|
+
}, [wb.def, wb]);
|
|
2678
2879
|
// Auto layout (simple layered layout)
|
|
2679
2880
|
const runAutoLayout = useCallback(() => {
|
|
2680
|
-
const cur = wb.export();
|
|
2681
2881
|
// Build DAG layers by indegree
|
|
2682
2882
|
const indegree = {};
|
|
2683
2883
|
const adj = {};
|
|
2684
|
-
for (const n of
|
|
2884
|
+
for (const n of wb.def.nodes) {
|
|
2685
2885
|
indegree[n.nodeId] = 0;
|
|
2686
2886
|
adj[n.nodeId] = [];
|
|
2687
2887
|
}
|
|
2688
|
-
for (const e of
|
|
2888
|
+
for (const e of wb.def.edges) {
|
|
2689
2889
|
indegree[e.target.nodeId] = (indegree[e.target.nodeId] ?? 0) + 1;
|
|
2690
2890
|
adj[e.source.nodeId].push(e.target.nodeId);
|
|
2691
2891
|
}
|
|
@@ -2716,7 +2916,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
2716
2916
|
let maxWidth = 0;
|
|
2717
2917
|
const heights = {};
|
|
2718
2918
|
for (const id of layer) {
|
|
2719
|
-
const node =
|
|
2919
|
+
const node = wb.def.nodes.find((n) => n.nodeId === id);
|
|
2720
2920
|
if (!node)
|
|
2721
2921
|
continue;
|
|
2722
2922
|
// Prefer showValues sizing similar to node rendering
|
|
@@ -2742,26 +2942,26 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
2742
2942
|
curX += maxWidth + H_GAP;
|
|
2743
2943
|
}
|
|
2744
2944
|
wb.setPositions(pos, { commit: true, reason: "auto-layout" });
|
|
2745
|
-
}, [wb, registry, overrides?.getDefaultNodeSize]);
|
|
2945
|
+
}, [wb, wb.def, registry, overrides?.getDefaultNodeSize]);
|
|
2746
2946
|
const updateEdgeType = useCallback((edgeId, typeId) => wb.updateEdgeType(edgeId, typeId), [wb]);
|
|
2747
2947
|
const triggerExternal = useCallback((nodeId, event) => runner.triggerExternal(nodeId, event), [runner]);
|
|
2748
2948
|
// Helper to save runtime metadata and UI state to extData
|
|
2749
|
-
const saveUiRuntimeMetadata = useCallback(async () => {
|
|
2949
|
+
const saveUiRuntimeMetadata = useCallback(async (workbench, graphRunner) => {
|
|
2750
2950
|
try {
|
|
2751
|
-
const current =
|
|
2951
|
+
const current = workbench.getRuntimeState() ?? { nodes: {} };
|
|
2752
2952
|
const metadata = { nodes: { ...current.nodes } };
|
|
2753
2953
|
// Clean up metadata for nodes that no longer exist
|
|
2754
|
-
const nodeIds = new Set(def.nodes.map((n) => n.nodeId));
|
|
2954
|
+
const nodeIds = new Set(workbench.def.nodes.map((n) => n.nodeId));
|
|
2755
2955
|
for (const nodeId of Object.keys(metadata.nodes)) {
|
|
2756
2956
|
if (!nodeIds.has(nodeId)) {
|
|
2757
2957
|
delete metadata.nodes[nodeId];
|
|
2758
2958
|
}
|
|
2759
2959
|
}
|
|
2760
2960
|
// Save cleaned metadata to workbench state
|
|
2761
|
-
|
|
2762
|
-
const fullUiState =
|
|
2961
|
+
workbench.setRuntimeState(metadata);
|
|
2962
|
+
const fullUiState = workbench.getUIState();
|
|
2763
2963
|
const uiWithoutViewport = excludeViewportFromUIState(fullUiState);
|
|
2764
|
-
await
|
|
2964
|
+
await graphRunner.setExtData?.({
|
|
2765
2965
|
...(Object.keys(uiWithoutViewport || {}).length > 0
|
|
2766
2966
|
? { ui: uiWithoutViewport }
|
|
2767
2967
|
: {}),
|
|
@@ -2771,7 +2971,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
2771
2971
|
catch (err) {
|
|
2772
2972
|
console.warn("[WorkbenchContext] Failed to save runtime metadata:", err);
|
|
2773
2973
|
}
|
|
2774
|
-
}, [
|
|
2974
|
+
}, []);
|
|
2775
2975
|
// Subscribe to runner/workbench events
|
|
2776
2976
|
useEffect(() => {
|
|
2777
2977
|
const add = (source, type) => (payload) => setEvents((prev) => {
|
|
@@ -2797,9 +2997,8 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
2797
2997
|
});
|
|
2798
2998
|
// Helper to apply resolved handles from event payload to workbench
|
|
2799
2999
|
const applyResolvedHandles = (resolvedHandles) => {
|
|
2800
|
-
const cur = wb.export();
|
|
2801
3000
|
let changed = false;
|
|
2802
|
-
for (const n of
|
|
3001
|
+
for (const n of wb.def.nodes) {
|
|
2803
3002
|
const updated = resolvedHandles[n.nodeId];
|
|
2804
3003
|
if (updated) {
|
|
2805
3004
|
const before = JSON.stringify(n.resolvedHandles || {});
|
|
@@ -2826,10 +3025,6 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
2826
3025
|
[handle]: now,
|
|
2827
3026
|
},
|
|
2828
3027
|
}));
|
|
2829
|
-
setNodeStatus((s) => ({
|
|
2830
|
-
...s,
|
|
2831
|
-
[nodeId]: { ...s[nodeId], invalidated: true },
|
|
2832
|
-
}));
|
|
2833
3028
|
// Clear validation errors for this input when a valid value is set
|
|
2834
3029
|
setInputValidationErrors((prev) => prev.filter((err) => !(err.nodeId === nodeId && err.handle === handle)));
|
|
2835
3030
|
}
|
|
@@ -2935,30 +3130,6 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
2935
3130
|
// If resolvedHandles are included in the event, use them directly (more efficient)
|
|
2936
3131
|
if (e?.resolvedHandles && Object.keys(e.resolvedHandles).length > 0) {
|
|
2937
3132
|
applyResolvedHandles(e.resolvedHandles);
|
|
2938
|
-
// Mark nodes whose handles changed as invalid
|
|
2939
|
-
const affectedNodeIds = Object.keys(e.resolvedHandles);
|
|
2940
|
-
if (affectedNodeIds.length > 0) {
|
|
2941
|
-
setNodeStatus((prev) => {
|
|
2942
|
-
const next = { ...prev };
|
|
2943
|
-
for (const id of affectedNodeIds) {
|
|
2944
|
-
const cur = next[id] ?? (next[id] = { activeRuns: 0, activeRunIds: [] });
|
|
2945
|
-
next[id] = { ...cur, invalidated: true };
|
|
2946
|
-
}
|
|
2947
|
-
return next;
|
|
2948
|
-
});
|
|
2949
|
-
}
|
|
2950
|
-
}
|
|
2951
|
-
// For broader invalidations (e.g. registry-changed, graph-updated), mark all nodes invalid
|
|
2952
|
-
if (e?.reason === "registry-changed" || e?.reason === "graph-updated") {
|
|
2953
|
-
setNodeStatus((prev) => {
|
|
2954
|
-
const next = { ...prev };
|
|
2955
|
-
for (const n of def.nodes) {
|
|
2956
|
-
const cur = next[n.nodeId] ??
|
|
2957
|
-
(next[n.nodeId] = { activeRuns: 0, activeRunIds: [] });
|
|
2958
|
-
next[n.nodeId] = { ...cur, invalidated: true };
|
|
2959
|
-
}
|
|
2960
|
-
return next;
|
|
2961
|
-
});
|
|
2962
3133
|
}
|
|
2963
3134
|
return add("runner", "invalidate")(e);
|
|
2964
3135
|
});
|
|
@@ -2991,7 +3162,6 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
2991
3162
|
? [...currentRunIds, runId]
|
|
2992
3163
|
: currentRunIds,
|
|
2993
3164
|
progress: 0,
|
|
2994
|
-
invalidated: false,
|
|
2995
3165
|
},
|
|
2996
3166
|
};
|
|
2997
3167
|
});
|
|
@@ -3094,16 +3264,6 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
3094
3264
|
const offWbGraphUiChangedForLog = wb.on("graphUiChanged", add("workbench", "graphUiChanged"));
|
|
3095
3265
|
const offWbValidationChanged = wb.on("validationChanged", add("workbench", "validationChanged"));
|
|
3096
3266
|
// Ensure newly added nodes start as invalidated until first evaluation
|
|
3097
|
-
const offWbAddNode = wb.on("graphChanged", (e) => {
|
|
3098
|
-
const change = e.change;
|
|
3099
|
-
if (change?.type === "addNode" && typeof change.nodeId === "string") {
|
|
3100
|
-
const id = change.nodeId;
|
|
3101
|
-
setNodeStatus((s) => ({
|
|
3102
|
-
...s,
|
|
3103
|
-
[id]: { ...s[id], invalidated: true },
|
|
3104
|
-
}));
|
|
3105
|
-
}
|
|
3106
|
-
});
|
|
3107
3267
|
const offWbGraphChangedForUpdate = wb.on("graphChanged", async (event) => {
|
|
3108
3268
|
// Build detailed reason from change type
|
|
3109
3269
|
let reason = "graph-changed";
|
|
@@ -3127,17 +3287,23 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
3127
3287
|
else if (changeType === "updateEdgeType") {
|
|
3128
3288
|
reason = "update-edge-type";
|
|
3129
3289
|
}
|
|
3290
|
+
else if (changeType === "setInputs") {
|
|
3291
|
+
reason = "set-inputs";
|
|
3292
|
+
}
|
|
3293
|
+
}
|
|
3294
|
+
if (event.change?.type === "setInputs") {
|
|
3295
|
+
const { nodeId, inputs } = event.change;
|
|
3296
|
+
await runner.setInputs(nodeId, inputs, { dry: event.dry });
|
|
3130
3297
|
}
|
|
3131
3298
|
if (!runner.isRunning()) {
|
|
3132
3299
|
if (event.commit) {
|
|
3133
|
-
await saveUiRuntimeMetadata();
|
|
3300
|
+
await saveUiRuntimeMetadata(wb, runner);
|
|
3134
3301
|
const history = await runner.commit(reason).catch((err) => {
|
|
3135
3302
|
console.error("[WorkbenchContext] Error committing:", err);
|
|
3136
3303
|
return undefined;
|
|
3137
3304
|
});
|
|
3138
|
-
if (history)
|
|
3305
|
+
if (history)
|
|
3139
3306
|
wb.setHistory(history);
|
|
3140
|
-
}
|
|
3141
3307
|
}
|
|
3142
3308
|
return;
|
|
3143
3309
|
}
|
|
@@ -3160,11 +3326,11 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
3160
3326
|
}
|
|
3161
3327
|
}
|
|
3162
3328
|
}
|
|
3163
|
-
else {
|
|
3329
|
+
else if (event.change?.type !== "setInputs") {
|
|
3164
3330
|
await runner.update(event.def, { dry: event.dry });
|
|
3165
3331
|
}
|
|
3166
3332
|
if (event.commit) {
|
|
3167
|
-
await saveUiRuntimeMetadata();
|
|
3333
|
+
await saveUiRuntimeMetadata(wb, runner);
|
|
3168
3334
|
const history = await runner
|
|
3169
3335
|
.commit(event.reason ?? reason)
|
|
3170
3336
|
.catch((err) => {
|
|
@@ -3185,7 +3351,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
3185
3351
|
setSelectedNodeId(sel.nodes?.[0]);
|
|
3186
3352
|
setSelectedEdgeId(sel.edges?.[0]);
|
|
3187
3353
|
if (sel.commit) {
|
|
3188
|
-
await saveUiRuntimeMetadata();
|
|
3354
|
+
await saveUiRuntimeMetadata(wb, runner);
|
|
3189
3355
|
const history = await runner
|
|
3190
3356
|
.commit(sel.reason ?? "selection")
|
|
3191
3357
|
.catch((err) => {
|
|
@@ -3224,7 +3390,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
3224
3390
|
reason = "selection";
|
|
3225
3391
|
}
|
|
3226
3392
|
}
|
|
3227
|
-
await saveUiRuntimeMetadata();
|
|
3393
|
+
await saveUiRuntimeMetadata(wb, runner);
|
|
3228
3394
|
const history = await runner
|
|
3229
3395
|
.commit(event.reason ?? reason)
|
|
3230
3396
|
.catch((err) => {
|
|
@@ -3244,7 +3410,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
3244
3410
|
wb.setRegistry(newReg);
|
|
3245
3411
|
// Trigger a graph update so the UI revalidates with new types/enums/nodes
|
|
3246
3412
|
try {
|
|
3247
|
-
await runner.update(wb.
|
|
3413
|
+
await runner.update(wb.def);
|
|
3248
3414
|
}
|
|
3249
3415
|
catch {
|
|
3250
3416
|
console.error("Failed to update graph definition after registry changed");
|
|
@@ -3267,7 +3433,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
3267
3433
|
setNodeStatus(() => {
|
|
3268
3434
|
const next = {};
|
|
3269
3435
|
const metadata = wb.getRuntimeState() ?? { nodes: {} };
|
|
3270
|
-
for (const n of def.nodes) {
|
|
3436
|
+
for (const n of wb.def.nodes) {
|
|
3271
3437
|
const nodeMeta = metadata.nodes[n.nodeId];
|
|
3272
3438
|
next[n.nodeId] = {
|
|
3273
3439
|
activeRuns: 0,
|
|
@@ -3282,6 +3448,30 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
3282
3448
|
errorRunsRef.current = {};
|
|
3283
3449
|
}
|
|
3284
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
|
+
});
|
|
3285
3475
|
wb.refreshValidation();
|
|
3286
3476
|
return () => {
|
|
3287
3477
|
offRunnerValue();
|
|
@@ -3293,20 +3483,20 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
3293
3483
|
offWbGraphUiChanged();
|
|
3294
3484
|
offWbValidationChanged();
|
|
3295
3485
|
offWbError();
|
|
3296
|
-
offWbAddNode();
|
|
3297
3486
|
offWbGraphChangedForUpdate();
|
|
3298
3487
|
offWbdSetValidation();
|
|
3299
3488
|
offWbSelectionChanged();
|
|
3300
3489
|
offRunnerRegistry();
|
|
3301
3490
|
offRunnerTransport();
|
|
3302
3491
|
offFlowViewport();
|
|
3492
|
+
offWbRuntimeMetadataChanged();
|
|
3303
3493
|
};
|
|
3304
3494
|
}, [runner, wb, setRegistry]);
|
|
3305
3495
|
const isRunning = useCallback(() => runner.isRunning(), [runner]);
|
|
3306
3496
|
const engineKind = useCallback(() => runner.getRunningEngine(), [runner]);
|
|
3307
3497
|
const start = useCallback((engine) => {
|
|
3308
3498
|
try {
|
|
3309
|
-
runner.launch(wb.
|
|
3499
|
+
runner.launch(wb.def, { engine });
|
|
3310
3500
|
}
|
|
3311
3501
|
catch { }
|
|
3312
3502
|
}, [runner, wb]);
|
|
@@ -3381,7 +3571,6 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
3381
3571
|
runner,
|
|
3382
3572
|
registry,
|
|
3383
3573
|
setRegistry,
|
|
3384
|
-
def,
|
|
3385
3574
|
selectedNodeId,
|
|
3386
3575
|
selectedEdgeId,
|
|
3387
3576
|
setSelection,
|
|
@@ -3422,7 +3611,6 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
3422
3611
|
runner,
|
|
3423
3612
|
registry,
|
|
3424
3613
|
setRegistry,
|
|
3425
|
-
def,
|
|
3426
3614
|
selectedNodeId,
|
|
3427
3615
|
selectedEdgeId,
|
|
3428
3616
|
setSelection,
|
|
@@ -3462,137 +3650,23 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
3462
3650
|
}
|
|
3463
3651
|
|
|
3464
3652
|
function createNodeContextMenuHandlers(nodeId, wb, runner, registry, outputsMap, outputTypesMap, onClose, getDefaultNodeSize, onCopyResult) {
|
|
3465
|
-
const doBake = async (handleId) => {
|
|
3466
|
-
try {
|
|
3467
|
-
const typeId = outputTypesMap?.[nodeId]?.[handleId];
|
|
3468
|
-
const raw = outputsMap?.[nodeId]?.[handleId];
|
|
3469
|
-
let newNodeId;
|
|
3470
|
-
if (!typeId || raw === undefined)
|
|
3471
|
-
return;
|
|
3472
|
-
const unwrap = (v) => isTypedOutput(v) ? getTypedOutputValue(v) : v;
|
|
3473
|
-
const coerceIfNeeded = async (fromType, toType, value) => {
|
|
3474
|
-
if (!toType || toType === fromType || !runner?.coerce)
|
|
3475
|
-
return value;
|
|
3476
|
-
try {
|
|
3477
|
-
return await runner.coerce(fromType, toType, value);
|
|
3478
|
-
}
|
|
3479
|
-
catch {
|
|
3480
|
-
return value;
|
|
3481
|
-
}
|
|
3482
|
-
};
|
|
3483
|
-
const pos = wb.getPositions()[nodeId] || { x: 0, y: 0 };
|
|
3484
|
-
const isArray = typeId.endsWith("[]");
|
|
3485
|
-
const baseTypeId = isArray ? typeId.slice(0, -2) : typeId;
|
|
3486
|
-
const tArr = isArray ? registry.types.get(typeId) : undefined;
|
|
3487
|
-
const tElem = registry.types.get(baseTypeId);
|
|
3488
|
-
const singleTarget = !isArray ? tElem?.bakeTarget : undefined;
|
|
3489
|
-
const arrTarget = isArray ? tArr?.bakeTarget : undefined;
|
|
3490
|
-
const elemTarget = isArray ? tElem?.bakeTarget : undefined;
|
|
3491
|
-
if (singleTarget) {
|
|
3492
|
-
const nodeDesc = registry.nodes.get(singleTarget.nodeTypeId);
|
|
3493
|
-
const inType = getInputTypeId(nodeDesc?.inputs, singleTarget.inputHandle);
|
|
3494
|
-
const coerced = await coerceIfNeeded(typeId, inType, unwrap(raw));
|
|
3495
|
-
newNodeId = wb.addNode({
|
|
3496
|
-
typeId: singleTarget.nodeTypeId,
|
|
3497
|
-
position: { x: pos.x + 180, y: pos.y },
|
|
3498
|
-
}, { inputs: { [singleTarget.inputHandle]: coerced } });
|
|
3499
|
-
}
|
|
3500
|
-
else if (isArray && arrTarget) {
|
|
3501
|
-
const nodeDesc = registry.nodes.get(arrTarget.nodeTypeId);
|
|
3502
|
-
const inType = getInputTypeId(nodeDesc?.inputs, arrTarget.inputHandle);
|
|
3503
|
-
const coerced = await coerceIfNeeded(typeId, inType, unwrap(raw));
|
|
3504
|
-
newNodeId = wb.addNode({
|
|
3505
|
-
typeId: arrTarget.nodeTypeId,
|
|
3506
|
-
position: { x: pos.x + 180, y: pos.y },
|
|
3507
|
-
}, { inputs: { [arrTarget.inputHandle]: coerced } });
|
|
3508
|
-
}
|
|
3509
|
-
else if (isArray && elemTarget) {
|
|
3510
|
-
const nodeDesc = registry.nodes.get(elemTarget.nodeTypeId);
|
|
3511
|
-
const inType = getInputTypeId(nodeDesc?.inputs, elemTarget.inputHandle);
|
|
3512
|
-
const src = unwrap(raw);
|
|
3513
|
-
const items = Array.isArray(src) ? src : [src];
|
|
3514
|
-
const coercedItems = await Promise.all(items.map((v) => coerceIfNeeded(baseTypeId, inType, v)));
|
|
3515
|
-
const COLS = 4;
|
|
3516
|
-
const DX = 180;
|
|
3517
|
-
const DY = 160;
|
|
3518
|
-
for (let idx = 0; idx < coercedItems.length; idx++) {
|
|
3519
|
-
const col = idx % COLS;
|
|
3520
|
-
const row = Math.floor(idx / COLS);
|
|
3521
|
-
newNodeId = wb.addNode({
|
|
3522
|
-
typeId: elemTarget.nodeTypeId,
|
|
3523
|
-
position: { x: pos.x + (col + 1) * DX, y: pos.y + row * DY },
|
|
3524
|
-
}, { inputs: { [elemTarget.inputHandle]: coercedItems[idx] } });
|
|
3525
|
-
}
|
|
3526
|
-
}
|
|
3527
|
-
if (newNodeId) {
|
|
3528
|
-
wb.setSelection({ nodes: [newNodeId], edges: [] }, { commit: true, reason: "bake" });
|
|
3529
|
-
}
|
|
3530
|
-
}
|
|
3531
|
-
catch { }
|
|
3532
|
-
};
|
|
3533
3653
|
return {
|
|
3534
3654
|
onDelete: () => {
|
|
3535
3655
|
wb.removeNode(nodeId, { commit: true });
|
|
3536
3656
|
onClose();
|
|
3537
3657
|
},
|
|
3538
3658
|
onDuplicate: async () => {
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
return onClose();
|
|
3543
|
-
const pos = wb.getPositions()[nodeId] || { x: 0, y: 0 };
|
|
3544
|
-
const inboundHandles = new Set(def.edges
|
|
3545
|
-
.filter((e) => e.target.nodeId === nodeId)
|
|
3546
|
-
.map((e) => e.target.handle));
|
|
3547
|
-
const allInputs = runner.getInputs(def)[nodeId] || {};
|
|
3548
|
-
const inputsWithoutBindings = Object.fromEntries(Object.entries(allInputs).filter(([handle]) => !inboundHandles.has(handle)));
|
|
3549
|
-
const newNodeId = wb.addNode({
|
|
3550
|
-
typeId: n.typeId,
|
|
3551
|
-
params: n.params,
|
|
3552
|
-
position: { x: pos.x + 24, y: pos.y + 24 },
|
|
3553
|
-
resolvedHandles: n.resolvedHandles,
|
|
3554
|
-
}, {
|
|
3555
|
-
inputs: inputsWithoutBindings,
|
|
3556
|
-
copyOutputsFrom: nodeId,
|
|
3557
|
-
dry: true,
|
|
3659
|
+
wb.duplicateNode(nodeId, runner, {
|
|
3660
|
+
commit: true,
|
|
3661
|
+
reason: "duplicate-node",
|
|
3558
3662
|
});
|
|
3559
|
-
// Select the newly duplicated node
|
|
3560
|
-
wb.setSelection({ nodes: [newNodeId], edges: [] }, { commit: true, reason: "duplicate" });
|
|
3561
3663
|
onClose();
|
|
3562
3664
|
},
|
|
3563
3665
|
onDuplicateWithEdges: async () => {
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
return onClose();
|
|
3568
|
-
const pos = wb.getPositions()[nodeId] || { x: 0, y: 0 };
|
|
3569
|
-
// Get inputs without bindings (literal values only)
|
|
3570
|
-
const inputs = runner.getInputs(def)[nodeId] || {};
|
|
3571
|
-
// Add the duplicated node
|
|
3572
|
-
const newNodeId = wb.addNode({
|
|
3573
|
-
typeId: n.typeId,
|
|
3574
|
-
params: n.params,
|
|
3575
|
-
position: { x: pos.x + 24, y: pos.y + 24 },
|
|
3576
|
-
resolvedHandles: n.resolvedHandles,
|
|
3577
|
-
}, {
|
|
3578
|
-
inputs,
|
|
3579
|
-
copyOutputsFrom: nodeId,
|
|
3580
|
-
dry: true,
|
|
3666
|
+
wb.duplicateNodeWithEdges(nodeId, runner, {
|
|
3667
|
+
commit: true,
|
|
3668
|
+
reason: "duplicate-node-with-edges",
|
|
3581
3669
|
});
|
|
3582
|
-
// Find all incoming edges (edges where target is the original node)
|
|
3583
|
-
const incomingEdges = def.edges.filter((e) => e.target.nodeId === nodeId);
|
|
3584
|
-
// Duplicate each incoming edge to point to the new node
|
|
3585
|
-
for (const edge of incomingEdges) {
|
|
3586
|
-
wb.connect({
|
|
3587
|
-
source: edge.source, // Keep the same source
|
|
3588
|
-
target: { nodeId: newNodeId, handle: edge.target.handle }, // Point to new node
|
|
3589
|
-
typeId: edge.typeId,
|
|
3590
|
-
}, { dry: true });
|
|
3591
|
-
}
|
|
3592
|
-
// Select the newly duplicated node and edges
|
|
3593
|
-
if (newNodeId) {
|
|
3594
|
-
wb.setSelection({ nodes: [newNodeId], edges: [] }, { commit: true, reason: "duplicate-with-edges" });
|
|
3595
|
-
}
|
|
3596
3670
|
onClose();
|
|
3597
3671
|
},
|
|
3598
3672
|
onRunPull: async () => {
|
|
@@ -3603,7 +3677,13 @@ function createNodeContextMenuHandlers(nodeId, wb, runner, registry, outputsMap,
|
|
|
3603
3677
|
onClose();
|
|
3604
3678
|
},
|
|
3605
3679
|
onBake: async (handleId) => {
|
|
3606
|
-
|
|
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
|
+
});
|
|
3607
3687
|
onClose();
|
|
3608
3688
|
},
|
|
3609
3689
|
onCopy: () => {
|
|
@@ -3660,42 +3740,10 @@ function createNodeCopyHandler(wb, runner, nodeId, getDefaultNodeSize, onCopyRes
|
|
|
3660
3740
|
function createSelectionContextMenuHandlers(wb, onClose, getDefaultNodeSize, onCopyResult, runner) {
|
|
3661
3741
|
const onDuplicate = runner
|
|
3662
3742
|
? () => {
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
}
|
|
3668
|
-
const def = wb.export();
|
|
3669
|
-
const positions = wb.getPositions();
|
|
3670
|
-
const newNodes = [];
|
|
3671
|
-
// Duplicate each selected node
|
|
3672
|
-
for (const nodeId of selection.nodes) {
|
|
3673
|
-
const n = def.nodes.find((n) => n.nodeId === nodeId);
|
|
3674
|
-
if (!n)
|
|
3675
|
-
continue;
|
|
3676
|
-
const pos = positions[nodeId] || { x: 0, y: 0 };
|
|
3677
|
-
// Get inputs without bindings (literal values only)
|
|
3678
|
-
const allInputs = runner.getInputs(def)[nodeId] || {};
|
|
3679
|
-
const inboundHandles = new Set(def.edges
|
|
3680
|
-
.filter((e) => e.target.nodeId === nodeId)
|
|
3681
|
-
.map((e) => e.target.handle));
|
|
3682
|
-
const inputsWithoutBindings = Object.fromEntries(Object.entries(allInputs).filter(([handle]) => !inboundHandles.has(handle)));
|
|
3683
|
-
const newNodeId = wb.addNode({
|
|
3684
|
-
typeId: n.typeId,
|
|
3685
|
-
params: n.params,
|
|
3686
|
-
position: { x: pos.x + 24, y: pos.y + 24 },
|
|
3687
|
-
resolvedHandles: n.resolvedHandles,
|
|
3688
|
-
}, {
|
|
3689
|
-
inputs: inputsWithoutBindings,
|
|
3690
|
-
copyOutputsFrom: nodeId,
|
|
3691
|
-
dry: true,
|
|
3692
|
-
});
|
|
3693
|
-
newNodes.push(newNodeId);
|
|
3694
|
-
}
|
|
3695
|
-
// Select all newly duplicated nodes
|
|
3696
|
-
if (newNodes.length > 0) {
|
|
3697
|
-
wb.setSelection({ nodes: newNodes, edges: [] }, { commit: true, reason: "duplicate-selection" });
|
|
3698
|
-
}
|
|
3743
|
+
wb.duplicateSelection(runner, {
|
|
3744
|
+
commit: true,
|
|
3745
|
+
reason: "duplicate-selection",
|
|
3746
|
+
});
|
|
3699
3747
|
onClose();
|
|
3700
3748
|
}
|
|
3701
3749
|
: undefined;
|
|
@@ -3730,9 +3778,8 @@ function createDefaultContextMenuHandlers(onAddNode, onClose, onPaste, runner, g
|
|
|
3730
3778
|
const canRedo = history ? history.redoCount > 0 : undefined;
|
|
3731
3779
|
const onSelectAll = wb
|
|
3732
3780
|
? () => {
|
|
3733
|
-
const
|
|
3734
|
-
const
|
|
3735
|
-
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);
|
|
3736
3783
|
wb.setSelection({ nodes: allNodeIds, edges: allEdgeIds }, { commit: true, reason: "select-all" });
|
|
3737
3784
|
onClose();
|
|
3738
3785
|
}
|
|
@@ -3751,8 +3798,7 @@ function createDefaultContextMenuHandlers(onAddNode, onClose, onPaste, runner, g
|
|
|
3751
3798
|
}
|
|
3752
3799
|
function getBakeableOutputs(nodeId, wb, registry, outputTypesMap) {
|
|
3753
3800
|
try {
|
|
3754
|
-
const
|
|
3755
|
-
const node = def.nodes.find((n) => n.nodeId === nodeId);
|
|
3801
|
+
const node = wb.def.nodes.find((n) => n.nodeId === nodeId);
|
|
3756
3802
|
if (!node)
|
|
3757
3803
|
return [];
|
|
3758
3804
|
const desc = registry.nodes.get(node.typeId);
|
|
@@ -3851,13 +3897,13 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
|
|
|
3851
3897
|
return String(value ?? "");
|
|
3852
3898
|
}
|
|
3853
3899
|
};
|
|
3854
|
-
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();
|
|
3855
3901
|
const nodeValidationIssues = validationByNode.issues;
|
|
3856
3902
|
const edgeValidationIssues = validationByEdge.issues;
|
|
3857
3903
|
const nodeValidationHandles = validationByNode;
|
|
3858
3904
|
const globalValidationIssues = validationGlobal;
|
|
3859
|
-
const selectedNode = def.nodes.find((n) => n.nodeId === selectedNodeId);
|
|
3860
|
-
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);
|
|
3861
3907
|
// Use computeEffectiveHandles to merge registry defaults with dynamically resolved handles
|
|
3862
3908
|
const effectiveHandles = selectedNode
|
|
3863
3909
|
? computeEffectiveHandles(selectedNode, registry)
|
|
@@ -3989,7 +4035,6 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
|
|
|
3989
4035
|
setOriginals(nextOriginals);
|
|
3990
4036
|
}, [selectedNodeId, selectedNode, registry, valuesTick]);
|
|
3991
4037
|
const widthClass = debug ? "w-[480px]" : "w-[320px]";
|
|
3992
|
-
const { wb } = useWorkbenchContext();
|
|
3993
4038
|
const deleteEdgeById = (edgeId) => {
|
|
3994
4039
|
if (!edgeId)
|
|
3995
4040
|
return;
|
|
@@ -4016,9 +4061,9 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
|
|
|
4016
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 ??
|
|
4017
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) => {
|
|
4018
4063
|
const typeId = getInputTypeId(effectiveHandles.inputs, h);
|
|
4019
|
-
const isLinked = def.edges.some((e) => e.target.nodeId === selectedNodeId &&
|
|
4064
|
+
const isLinked = wb.def.edges.some((e) => e.target.nodeId === selectedNodeId &&
|
|
4020
4065
|
e.target.handle === h);
|
|
4021
|
-
const inbound = new Set(def.edges
|
|
4066
|
+
const inbound = new Set(wb.def.edges
|
|
4022
4067
|
.filter((e) => e.target.nodeId === selectedNodeId &&
|
|
4023
4068
|
e.target.handle === h)
|
|
4024
4069
|
.map((e) => e.target.handle));
|
|
@@ -4594,10 +4639,9 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
4594
4639
|
const { nodeTypes, resolveNodeType } = useMemo(() => {
|
|
4595
4640
|
// Build nodeTypes map using UI extension registry
|
|
4596
4641
|
const custom = new Map(); // Include all types present in registry AND current graph to avoid timing issues
|
|
4597
|
-
const def = wb.export();
|
|
4598
4642
|
const ids = new Set([
|
|
4599
4643
|
...Array.from(registry.nodes.keys()),
|
|
4600
|
-
...def.nodes.map((n) => n.typeId),
|
|
4644
|
+
...wb.def.nodes.map((n) => n.typeId),
|
|
4601
4645
|
]);
|
|
4602
4646
|
for (const typeId of ids) {
|
|
4603
4647
|
const renderer = ui.getNodeRenderer(typeId);
|
|
@@ -4616,14 +4660,13 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
4616
4660
|
// Include uiVersion to recompute when custom renderers are registered
|
|
4617
4661
|
}, [wb, registry, uiVersion, ui]);
|
|
4618
4662
|
const { nodes, edges } = useMemo(() => {
|
|
4619
|
-
const def = wb.export();
|
|
4620
4663
|
const sel = wb.getSelection();
|
|
4621
4664
|
// Merge defaults with inputs for node display (defaults shown in lighter gray)
|
|
4622
4665
|
const inputsWithDefaults = {};
|
|
4623
|
-
for (const n of def.nodes) {
|
|
4666
|
+
for (const n of wb.def.nodes) {
|
|
4624
4667
|
const nodeInputs = inputsMap[n.nodeId] ?? {};
|
|
4625
4668
|
const nodeDefaults = inputDefaultsMap[n.nodeId] ?? {};
|
|
4626
|
-
const inbound = new Set(def.edges
|
|
4669
|
+
const inbound = new Set(wb.def.edges
|
|
4627
4670
|
.filter((e) => e.target.nodeId === n.nodeId)
|
|
4628
4671
|
.map((e) => e.target.handle));
|
|
4629
4672
|
const merged = { ...nodeInputs };
|
|
@@ -4636,7 +4679,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
4636
4679
|
inputsWithDefaults[n.nodeId] = merged;
|
|
4637
4680
|
}
|
|
4638
4681
|
}
|
|
4639
|
-
const out = toReactFlow(def, wb.getPositions(), registry, {
|
|
4682
|
+
const out = toReactFlow(wb.def, wb.getPositions(), registry, {
|
|
4640
4683
|
showValues,
|
|
4641
4684
|
inputs: inputsWithDefaults,
|
|
4642
4685
|
inputDefaults: inputDefaultsMap,
|
|
@@ -5160,11 +5203,11 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
5160
5203
|
});
|
|
5161
5204
|
|
|
5162
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, }) {
|
|
5163
|
-
const { wb, runner, registry,
|
|
5206
|
+
const { wb, runner, registry, selectedNodeId, runAutoLayout } = useWorkbenchContext();
|
|
5164
5207
|
const [transportStatus, setTransportStatus] = useState({
|
|
5165
5208
|
state: "local",
|
|
5166
5209
|
});
|
|
5167
|
-
const selectedNode = def.nodes.find((n) => n.nodeId === selectedNodeId);
|
|
5210
|
+
const selectedNode = wb.def.nodes.find((n) => n.nodeId === selectedNodeId);
|
|
5168
5211
|
const effectiveHandles = selectedNode
|
|
5169
5212
|
? computeEffectiveHandles(selectedNode, registry)
|
|
5170
5213
|
: { inputs: {}, outputs: {}, inputDefaults: {} };
|
|
@@ -5192,7 +5235,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
5192
5235
|
if (evt.shiftKey && !confirm("Invalidate and re-run graph?"))
|
|
5193
5236
|
return;
|
|
5194
5237
|
try {
|
|
5195
|
-
runner.launch(wb.
|
|
5238
|
+
runner.launch(wb.def, {
|
|
5196
5239
|
engine: kind,
|
|
5197
5240
|
invalidate: evt.shiftKey,
|
|
5198
5241
|
});
|
|
@@ -5260,12 +5303,12 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
5260
5303
|
const setInitialGraph = async (d, inputs) => {
|
|
5261
5304
|
await wb.load(d);
|
|
5262
5305
|
try {
|
|
5263
|
-
runner.build(wb.
|
|
5306
|
+
runner.build(wb.def);
|
|
5264
5307
|
}
|
|
5265
5308
|
catch { }
|
|
5266
5309
|
if (inputs) {
|
|
5267
5310
|
for (const [nodeId, map] of Object.entries(inputs)) {
|
|
5268
|
-
runner.setInputs(nodeId, map);
|
|
5311
|
+
runner.setInputs(nodeId, map, { dry: true });
|
|
5269
5312
|
}
|
|
5270
5313
|
}
|
|
5271
5314
|
};
|
|
@@ -5275,36 +5318,27 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
5275
5318
|
useEffect(() => {
|
|
5276
5319
|
if (!onChange)
|
|
5277
5320
|
return;
|
|
5278
|
-
const
|
|
5279
|
-
try {
|
|
5280
|
-
const cur = wb.export();
|
|
5281
|
-
const inputs = runner.getInputs(cur);
|
|
5282
|
-
onChange({ def: cur, inputs });
|
|
5283
|
-
}
|
|
5284
|
-
catch { }
|
|
5285
|
-
});
|
|
5286
|
-
const off2 = runner.on("value", () => {
|
|
5321
|
+
const offGraphChanged = wb.on("graphChanged", () => {
|
|
5287
5322
|
try {
|
|
5288
|
-
const cur = wb.
|
|
5323
|
+
const cur = wb.def;
|
|
5289
5324
|
const inputs = runner.getInputs(cur);
|
|
5290
5325
|
onChange({ def: cur, inputs });
|
|
5291
5326
|
}
|
|
5292
5327
|
catch { }
|
|
5293
5328
|
});
|
|
5294
|
-
const
|
|
5329
|
+
const offGraphUiChanged = wb.on("graphUiChanged", (evt) => {
|
|
5295
5330
|
if (!evt.commit)
|
|
5296
5331
|
return;
|
|
5297
5332
|
try {
|
|
5298
|
-
const cur = wb.
|
|
5333
|
+
const cur = wb.def;
|
|
5299
5334
|
const inputs = runner.getInputs(cur);
|
|
5300
5335
|
onChange({ def: cur, inputs });
|
|
5301
5336
|
}
|
|
5302
5337
|
catch { }
|
|
5303
5338
|
});
|
|
5304
5339
|
return () => {
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
off3();
|
|
5340
|
+
offGraphChanged();
|
|
5341
|
+
offGraphUiChanged();
|
|
5308
5342
|
};
|
|
5309
5343
|
}, [wb, runner, onChange]);
|
|
5310
5344
|
const applyExample = useCallback(async (key) => {
|
|
@@ -5327,11 +5361,11 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
5327
5361
|
}
|
|
5328
5362
|
await wb.load(def);
|
|
5329
5363
|
// Build a local runtime so seeded defaults are visible pre-run
|
|
5330
|
-
runner.build(wb.
|
|
5364
|
+
runner.build(wb.def);
|
|
5331
5365
|
// Set initial inputs if provided
|
|
5332
5366
|
if (inputs) {
|
|
5333
5367
|
for (const [nodeId, map] of Object.entries(inputs)) {
|
|
5334
|
-
runner.setInputs(nodeId, map);
|
|
5368
|
+
runner.setInputs(nodeId, map, { dry: true });
|
|
5335
5369
|
}
|
|
5336
5370
|
}
|
|
5337
5371
|
runAutoLayout();
|
|
@@ -5412,11 +5446,10 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
5412
5446
|
// Only auto-launch for local backend; require explicit Start for remote
|
|
5413
5447
|
if (backendKind !== "local")
|
|
5414
5448
|
return;
|
|
5415
|
-
|
|
5416
|
-
if (!d.nodes || d.nodes.length === 0)
|
|
5449
|
+
if (!wb.def.nodes || wb.def.nodes.length === 0)
|
|
5417
5450
|
return;
|
|
5418
5451
|
try {
|
|
5419
|
-
runner.launch(
|
|
5452
|
+
runner.launch(wb.def, { engine: engine });
|
|
5420
5453
|
}
|
|
5421
5454
|
catch {
|
|
5422
5455
|
// ignore
|
|
@@ -5426,12 +5459,12 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
5426
5459
|
if (!selectedNodeId)
|
|
5427
5460
|
return;
|
|
5428
5461
|
// If selected input is wired (has inbound edge), ignore user input to respect runtime value
|
|
5429
|
-
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);
|
|
5430
5463
|
if (isLinked)
|
|
5431
5464
|
return;
|
|
5432
5465
|
// If raw is undefined, pass it through to delete the input value
|
|
5433
5466
|
if (raw === undefined) {
|
|
5434
|
-
|
|
5467
|
+
wb.setInputs(selectedNodeId, { [handle]: undefined }, { commit: true });
|
|
5435
5468
|
return;
|
|
5436
5469
|
}
|
|
5437
5470
|
const typeId = getInputTypeId(effectiveHandles.inputs, handle);
|
|
@@ -5510,8 +5543,8 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
5510
5543
|
value = raw;
|
|
5511
5544
|
}
|
|
5512
5545
|
}
|
|
5513
|
-
|
|
5514
|
-
}, [selectedNodeId, def.edges, effectiveHandles,
|
|
5546
|
+
wb.setInputs(selectedNodeId, { [handle]: value }, { commit: true });
|
|
5547
|
+
}, [selectedNodeId, wb.def.edges, effectiveHandles, wb]);
|
|
5515
5548
|
const setInput = useMemo(() => {
|
|
5516
5549
|
if (overrides?.setInput) {
|
|
5517
5550
|
return overrides.setInput(baseSetInput, {
|