@bian-womp/spark-workbench 0.2.61 → 0.2.63
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 +271 -131
- package/lib/cjs/index.cjs.map +1 -1
- package/lib/cjs/src/core/AbstractWorkbench.d.ts +3 -1
- package/lib/cjs/src/core/AbstractWorkbench.d.ts.map +1 -1
- package/lib/cjs/src/core/InMemoryWorkbench.d.ts +6 -0
- package/lib/cjs/src/core/InMemoryWorkbench.d.ts.map +1 -1
- package/lib/cjs/src/core/contracts.d.ts +7 -0
- package/lib/cjs/src/core/contracts.d.ts.map +1 -1
- package/lib/cjs/src/misc/NodeContextMenu.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/ContextMenuHandlers.d.ts +1 -1
- package/lib/cjs/src/misc/context/ContextMenuHandlers.d.ts.map +1 -1
- package/lib/cjs/src/misc/context/ContextMenuHelpers.d.ts.map +1 -1
- package/lib/cjs/src/misc/context/WorkbenchContext.d.ts +1 -0
- 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/runtime/AbstractGraphRunner.d.ts +15 -5
- package/lib/cjs/src/runtime/AbstractGraphRunner.d.ts.map +1 -1
- package/lib/cjs/src/runtime/IGraphRunner.d.ts +16 -5
- package/lib/cjs/src/runtime/IGraphRunner.d.ts.map +1 -1
- package/lib/cjs/src/runtime/LocalGraphRunner.d.ts +13 -1
- package/lib/cjs/src/runtime/LocalGraphRunner.d.ts.map +1 -1
- package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts +17 -5
- package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
- package/lib/esm/index.js +271 -131
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/src/core/AbstractWorkbench.d.ts +3 -1
- package/lib/esm/src/core/AbstractWorkbench.d.ts.map +1 -1
- package/lib/esm/src/core/InMemoryWorkbench.d.ts +6 -0
- package/lib/esm/src/core/InMemoryWorkbench.d.ts.map +1 -1
- package/lib/esm/src/core/contracts.d.ts +7 -0
- package/lib/esm/src/core/contracts.d.ts.map +1 -1
- package/lib/esm/src/misc/NodeContextMenu.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/ContextMenuHandlers.d.ts +1 -1
- package/lib/esm/src/misc/context/ContextMenuHandlers.d.ts.map +1 -1
- package/lib/esm/src/misc/context/ContextMenuHelpers.d.ts.map +1 -1
- package/lib/esm/src/misc/context/WorkbenchContext.d.ts +1 -0
- 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/runtime/AbstractGraphRunner.d.ts +15 -5
- package/lib/esm/src/runtime/AbstractGraphRunner.d.ts.map +1 -1
- package/lib/esm/src/runtime/IGraphRunner.d.ts +16 -5
- package/lib/esm/src/runtime/IGraphRunner.d.ts.map +1 -1
- package/lib/esm/src/runtime/LocalGraphRunner.d.ts +13 -1
- package/lib/esm/src/runtime/LocalGraphRunner.d.ts.map +1 -1
- package/lib/esm/src/runtime/RemoteGraphRunner.d.ts +17 -5
- package/lib/esm/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
- package/package.json +4 -4
package/lib/esm/index.js
CHANGED
|
@@ -188,21 +188,26 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
188
188
|
}
|
|
189
189
|
return { ok: issues.every((i) => i.level !== "error"), issues };
|
|
190
190
|
}
|
|
191
|
-
addNode(node) {
|
|
191
|
+
addNode(node, options) {
|
|
192
192
|
const id = node.nodeId ??
|
|
193
193
|
this.genId("n", new Set(this.def.nodes.map((n) => n.nodeId)));
|
|
194
194
|
this.def.nodes.push({
|
|
195
195
|
nodeId: id,
|
|
196
196
|
typeId: node.typeId,
|
|
197
197
|
params: node.params,
|
|
198
|
-
initialInputs: node.initialInputs,
|
|
199
198
|
resolvedHandles: node.resolvedHandles,
|
|
200
199
|
});
|
|
201
200
|
if (node.position)
|
|
202
201
|
this.positions[id] = node.position;
|
|
203
202
|
this.emit("graphChanged", {
|
|
204
203
|
def: this.def,
|
|
205
|
-
change: {
|
|
204
|
+
change: {
|
|
205
|
+
type: "addNode",
|
|
206
|
+
nodeId: id,
|
|
207
|
+
inputs: options?.inputs,
|
|
208
|
+
copyOutputsFrom: options?.copyOutputsFrom,
|
|
209
|
+
},
|
|
210
|
+
dry: options?.dry,
|
|
206
211
|
});
|
|
207
212
|
this.refreshValidation();
|
|
208
213
|
return id;
|
|
@@ -217,7 +222,7 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
217
222
|
});
|
|
218
223
|
this.refreshValidation();
|
|
219
224
|
}
|
|
220
|
-
connect(edge) {
|
|
225
|
+
connect(edge, options) {
|
|
221
226
|
const id = edge.id ?? this.genId("e", new Set(this.def.edges.map((e) => e.id)));
|
|
222
227
|
this.def.edges.push({
|
|
223
228
|
id,
|
|
@@ -228,6 +233,7 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
228
233
|
this.emit("graphChanged", {
|
|
229
234
|
def: this.def,
|
|
230
235
|
change: { type: "connect", edgeId: id },
|
|
236
|
+
dry: options?.dry,
|
|
231
237
|
});
|
|
232
238
|
this.refreshValidation();
|
|
233
239
|
return id;
|
|
@@ -425,35 +431,6 @@ class AbstractGraphRunner {
|
|
|
425
431
|
this.stop();
|
|
426
432
|
}
|
|
427
433
|
}
|
|
428
|
-
triggerExternal(nodeId, event) {
|
|
429
|
-
this.engine?.triggerExternal(nodeId, event);
|
|
430
|
-
}
|
|
431
|
-
// Batch update multiple inputs on a node and trigger a single run
|
|
432
|
-
setInputs(nodeId, inputs) {
|
|
433
|
-
if (!inputs)
|
|
434
|
-
return;
|
|
435
|
-
if (!this.stagedInputs[nodeId])
|
|
436
|
-
this.stagedInputs[nodeId] = {};
|
|
437
|
-
for (const [handle, value] of Object.entries(inputs)) {
|
|
438
|
-
if (value === undefined) {
|
|
439
|
-
delete this.stagedInputs[nodeId][handle];
|
|
440
|
-
}
|
|
441
|
-
else {
|
|
442
|
-
this.stagedInputs[nodeId][handle] = value;
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
if (this.engine) {
|
|
446
|
-
// Running: set all inputs
|
|
447
|
-
this.engine.setInputs(nodeId, inputs);
|
|
448
|
-
}
|
|
449
|
-
else {
|
|
450
|
-
// Not running: emit a single synthetic value event per handle; UI will coalesce
|
|
451
|
-
console.warn("Engine does not exists");
|
|
452
|
-
for (const [handle, value] of Object.entries(inputs)) {
|
|
453
|
-
this.emit("value", { nodeId, handle, value, io: "input" });
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
434
|
async whenIdle() {
|
|
458
435
|
await this.engine?.whenIdle();
|
|
459
436
|
}
|
|
@@ -494,10 +471,8 @@ class AbstractGraphRunner {
|
|
|
494
471
|
const out = {};
|
|
495
472
|
for (const n of def.nodes) {
|
|
496
473
|
const dynDefaults = n.resolvedHandles?.inputDefaults ?? {};
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
if (Object.keys(merged).length > 0) {
|
|
500
|
-
out[n.nodeId] = merged;
|
|
474
|
+
if (Object.keys(dynDefaults).length > 0) {
|
|
475
|
+
out[n.nodeId] = dynDefaults;
|
|
501
476
|
}
|
|
502
477
|
}
|
|
503
478
|
return out;
|
|
@@ -541,13 +516,22 @@ class LocalGraphRunner extends AbstractGraphRunner {
|
|
|
541
516
|
this.setEnvironment = (env, opts) => {
|
|
542
517
|
if (!this.runtime)
|
|
543
518
|
return;
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
519
|
+
const wasPaused = this.runtime.isPaused();
|
|
520
|
+
if (opts?.dry && !wasPaused)
|
|
521
|
+
this.runtime.pause();
|
|
522
|
+
try {
|
|
523
|
+
if (opts?.merge) {
|
|
524
|
+
const current = this.runtime.getEnvironment();
|
|
525
|
+
const next = { ...(current || {}), ...(env || {}) };
|
|
526
|
+
this.runtime.setEnvironment(next);
|
|
527
|
+
}
|
|
528
|
+
else {
|
|
529
|
+
this.runtime.setEnvironment(env);
|
|
530
|
+
}
|
|
548
531
|
}
|
|
549
|
-
|
|
550
|
-
|
|
532
|
+
finally {
|
|
533
|
+
if (opts?.dry && !wasPaused)
|
|
534
|
+
this.runtime.resume();
|
|
551
535
|
}
|
|
552
536
|
};
|
|
553
537
|
this.getEnvironment = () => {
|
|
@@ -565,14 +549,24 @@ class LocalGraphRunner extends AbstractGraphRunner {
|
|
|
565
549
|
// Signal UI that freshly built graph should be considered invalidated
|
|
566
550
|
this.emit("invalidate", { reason: "graph-built" });
|
|
567
551
|
}
|
|
568
|
-
update(def) {
|
|
552
|
+
update(def, options) {
|
|
569
553
|
if (!this.runtime)
|
|
570
554
|
return;
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
555
|
+
const wasPaused = this.runtime.isPaused();
|
|
556
|
+
// Pause runtime if dry option is set (to prevent execution) or if not paused already
|
|
557
|
+
if (options?.dry && !wasPaused) {
|
|
558
|
+
this.runtime.pause();
|
|
559
|
+
}
|
|
560
|
+
try {
|
|
561
|
+
this.runtime.update(def, this.registry);
|
|
562
|
+
this.emit("invalidate", { reason: "graph-updated" });
|
|
563
|
+
}
|
|
564
|
+
finally {
|
|
565
|
+
// Resume only if we paused it due to dry option
|
|
566
|
+
if (options?.dry && !wasPaused) {
|
|
567
|
+
this.runtime.resume();
|
|
568
|
+
}
|
|
569
|
+
}
|
|
576
570
|
}
|
|
577
571
|
launch(def, opts) {
|
|
578
572
|
super.launch(def, opts);
|
|
@@ -663,6 +657,70 @@ class LocalGraphRunner extends AbstractGraphRunner {
|
|
|
663
657
|
}
|
|
664
658
|
return out;
|
|
665
659
|
}
|
|
660
|
+
triggerExternal(nodeId, event, options) {
|
|
661
|
+
// Handle dry option: pause runtime before triggering, resume after
|
|
662
|
+
const wasPaused = this.runtime?.isPaused() ?? false;
|
|
663
|
+
if (options?.dry && !wasPaused && this.runtime) {
|
|
664
|
+
this.runtime.pause();
|
|
665
|
+
}
|
|
666
|
+
try {
|
|
667
|
+
this.engine?.triggerExternal(nodeId, event);
|
|
668
|
+
}
|
|
669
|
+
finally {
|
|
670
|
+
if (options?.dry && !wasPaused && this.runtime) {
|
|
671
|
+
this.runtime.resume();
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
// Batch update multiple inputs on a node and trigger a single run
|
|
676
|
+
setInputs(nodeId, inputs, options) {
|
|
677
|
+
if (!inputs)
|
|
678
|
+
return;
|
|
679
|
+
if (!this.stagedInputs[nodeId])
|
|
680
|
+
this.stagedInputs[nodeId] = {};
|
|
681
|
+
for (const [handle, value] of Object.entries(inputs)) {
|
|
682
|
+
if (value === undefined) {
|
|
683
|
+
delete this.stagedInputs[nodeId][handle];
|
|
684
|
+
}
|
|
685
|
+
else {
|
|
686
|
+
this.stagedInputs[nodeId][handle] = value;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
// Handle dry option: pause runtime before setting inputs, resume after
|
|
690
|
+
const wasPaused = this.runtime?.isPaused() ?? false;
|
|
691
|
+
if (options?.dry && !wasPaused && this.runtime) {
|
|
692
|
+
this.runtime.pause();
|
|
693
|
+
}
|
|
694
|
+
try {
|
|
695
|
+
if (this.engine) {
|
|
696
|
+
this.engine.setInputs(nodeId, inputs);
|
|
697
|
+
}
|
|
698
|
+
else {
|
|
699
|
+
// Not running: emit a single synthetic value event per handle; UI will coalesce
|
|
700
|
+
console.warn("Engine does not exists");
|
|
701
|
+
for (const [handle, value] of Object.entries(inputs)) {
|
|
702
|
+
this.emit("value", { nodeId, handle, value, io: "input" });
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
finally {
|
|
707
|
+
if (options?.dry && !wasPaused && this.runtime) {
|
|
708
|
+
this.runtime.resume();
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
copyOutputs(fromNodeId, toNodeId, options) {
|
|
713
|
+
if (!this.runtime)
|
|
714
|
+
return;
|
|
715
|
+
// Get outputs from source node
|
|
716
|
+
const fromNode = this.runtime.getNodeData(fromNodeId);
|
|
717
|
+
if (!fromNode?.outputs)
|
|
718
|
+
return;
|
|
719
|
+
// Copy outputs to target node using hydrate
|
|
720
|
+
// hydrate already pauses internally, so we don't need to handle dry option here
|
|
721
|
+
// reemit: !options?.dry means don't propagate downstream if dry mode
|
|
722
|
+
this.runtime.hydrate({ outputs: { [toNodeId]: { ...fromNode.outputs } } }, { reemit: !options?.dry });
|
|
723
|
+
}
|
|
666
724
|
async snapshotFull() {
|
|
667
725
|
const def = undefined; // UI will supply def/positions on download for local
|
|
668
726
|
const inputs = this.getInputs(this.runtime
|
|
@@ -687,7 +745,7 @@ class LocalGraphRunner extends AbstractGraphRunner {
|
|
|
687
745
|
return { def, environment, inputs, outputs };
|
|
688
746
|
}
|
|
689
747
|
async applySnapshotFull(payload, options) {
|
|
690
|
-
if (payload.def && options?.skipBuild
|
|
748
|
+
if (payload.def && !options?.skipBuild) {
|
|
691
749
|
this.build(payload.def);
|
|
692
750
|
}
|
|
693
751
|
this.setEnvironment?.(payload.environment || {}, { merge: false });
|
|
@@ -985,7 +1043,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
985
1043
|
// Trigger update so validation/UI refreshes using last known graph
|
|
986
1044
|
try {
|
|
987
1045
|
if (this.lastDef)
|
|
988
|
-
this.update(this.lastDef);
|
|
1046
|
+
await this.update(this.lastDef);
|
|
989
1047
|
}
|
|
990
1048
|
catch {
|
|
991
1049
|
console.error("Failed to update graph definition after registry changed");
|
|
@@ -1000,16 +1058,18 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
1000
1058
|
});
|
|
1001
1059
|
}
|
|
1002
1060
|
build(def) { }
|
|
1003
|
-
update(def) {
|
|
1004
|
-
// Remote: forward update
|
|
1005
|
-
this.ensureClient()
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1061
|
+
async update(def, options) {
|
|
1062
|
+
// Remote: forward update and await completion
|
|
1063
|
+
const client = await this.ensureClient();
|
|
1064
|
+
try {
|
|
1065
|
+
await client.update(def, options);
|
|
1066
|
+
this.emit("invalidate", { reason: "graph-updated" });
|
|
1067
|
+
this.lastDef = def;
|
|
1068
|
+
}
|
|
1069
|
+
catch (err) {
|
|
1070
|
+
// Log error but don't throw to maintain compatibility
|
|
1071
|
+
console.error("[RemoteGraphRunner] Error updating graph:", err);
|
|
1072
|
+
}
|
|
1013
1073
|
}
|
|
1014
1074
|
launch(def, opts) {
|
|
1015
1075
|
super.launch(def, opts);
|
|
@@ -1130,13 +1190,53 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
1130
1190
|
const client = await this.ensureClient();
|
|
1131
1191
|
await client.flush();
|
|
1132
1192
|
}
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1193
|
+
setInputs(nodeId, inputs, options) {
|
|
1194
|
+
// Update staged inputs (for getInputs to work correctly)
|
|
1195
|
+
if (!this.stagedInputs[nodeId])
|
|
1196
|
+
this.stagedInputs[nodeId] = {};
|
|
1197
|
+
for (const [handle, value] of Object.entries(inputs)) {
|
|
1198
|
+
if (value === undefined) {
|
|
1199
|
+
delete this.stagedInputs[nodeId][handle];
|
|
1137
1200
|
}
|
|
1138
|
-
|
|
1139
|
-
|
|
1201
|
+
else {
|
|
1202
|
+
this.stagedInputs[nodeId][handle] = value;
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
// If engine exists, call directly; otherwise ensure client (fire-and-forget)
|
|
1206
|
+
if (this.engine) {
|
|
1207
|
+
this.engine.setInputs(nodeId, inputs, options);
|
|
1208
|
+
}
|
|
1209
|
+
else {
|
|
1210
|
+
this.ensureClient()
|
|
1211
|
+
.then((client) => {
|
|
1212
|
+
client.getEngine().setInputs(nodeId, inputs, options);
|
|
1213
|
+
})
|
|
1214
|
+
.catch(() => {
|
|
1215
|
+
// Emit synthetic events if connection fails
|
|
1216
|
+
for (const [handle, value] of Object.entries(inputs)) {
|
|
1217
|
+
this.emit("value", { nodeId, handle, value, io: "input" });
|
|
1218
|
+
}
|
|
1219
|
+
});
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
async copyOutputs(fromNodeId, toNodeId, options) {
|
|
1223
|
+
const client = await this.ensureClient();
|
|
1224
|
+
await client.copyOutputs(fromNodeId, toNodeId, options);
|
|
1225
|
+
}
|
|
1226
|
+
triggerExternal(nodeId, event, options) {
|
|
1227
|
+
// If engine exists, call directly; otherwise ensure client (fire-and-forget)
|
|
1228
|
+
if (this.engine) {
|
|
1229
|
+
this.engine.triggerExternal(nodeId, event, options);
|
|
1230
|
+
}
|
|
1231
|
+
else {
|
|
1232
|
+
this.ensureClient()
|
|
1233
|
+
.then((client) => {
|
|
1234
|
+
client.getEngine().triggerExternal(nodeId, event, options);
|
|
1235
|
+
})
|
|
1236
|
+
.catch(() => {
|
|
1237
|
+
// Silently fail if connection not available
|
|
1238
|
+
});
|
|
1239
|
+
}
|
|
1140
1240
|
}
|
|
1141
1241
|
async coerce(from, to, value) {
|
|
1142
1242
|
const client = await this.ensureClient();
|
|
@@ -1194,18 +1294,19 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
1194
1294
|
}
|
|
1195
1295
|
}
|
|
1196
1296
|
}
|
|
1197
|
-
setEnvironment(env, opts) {
|
|
1297
|
+
async setEnvironment(env, opts) {
|
|
1198
1298
|
// Use client if available, otherwise ensure client and then set environment
|
|
1199
1299
|
if (this.client) {
|
|
1200
|
-
this.client.setEnvironment(env, opts)
|
|
1300
|
+
await this.client.setEnvironment(env, opts);
|
|
1201
1301
|
}
|
|
1202
1302
|
else {
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
.
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1303
|
+
try {
|
|
1304
|
+
const client = await this.ensureClient();
|
|
1305
|
+
await client.setEnvironment(env, opts);
|
|
1306
|
+
}
|
|
1307
|
+
catch {
|
|
1308
|
+
// Silently fail if connection not available
|
|
1309
|
+
}
|
|
1209
1310
|
}
|
|
1210
1311
|
}
|
|
1211
1312
|
getEnvironment() {
|
|
@@ -1266,7 +1367,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
1266
1367
|
}
|
|
1267
1368
|
return out;
|
|
1268
1369
|
}
|
|
1269
|
-
dispose() {
|
|
1370
|
+
async dispose() {
|
|
1270
1371
|
// Idempotent: allow multiple calls safely
|
|
1271
1372
|
if (this.disposed)
|
|
1272
1373
|
return;
|
|
@@ -1287,9 +1388,12 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
1287
1388
|
this.registryFetched = false; // Reset so registry is fetched again on reconnect
|
|
1288
1389
|
this.registryFetching = false; // Reset fetching state
|
|
1289
1390
|
if (clientToDispose) {
|
|
1290
|
-
|
|
1391
|
+
try {
|
|
1392
|
+
await clientToDispose.dispose();
|
|
1393
|
+
}
|
|
1394
|
+
catch (err) {
|
|
1291
1395
|
console.warn("[RemoteGraphRunner] Error disposing client:", err);
|
|
1292
|
-
}
|
|
1396
|
+
}
|
|
1293
1397
|
}
|
|
1294
1398
|
const disconnectedStatus = {
|
|
1295
1399
|
state: "disconnected",
|
|
@@ -2612,6 +2716,36 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
2612
2716
|
}));
|
|
2613
2717
|
}
|
|
2614
2718
|
});
|
|
2719
|
+
const offWbGraphChangedForUpdate = wb.on("graphChanged", async (event) => {
|
|
2720
|
+
if (!runner.isRunning())
|
|
2721
|
+
return;
|
|
2722
|
+
try {
|
|
2723
|
+
if (event.change?.type === "addNode") {
|
|
2724
|
+
const { nodeId, inputs, copyOutputsFrom } = event.change;
|
|
2725
|
+
if (event.dry) {
|
|
2726
|
+
await runner.update(event.def, { dry: true });
|
|
2727
|
+
if (inputs) {
|
|
2728
|
+
runner.setInputs(nodeId, inputs, { dry: true });
|
|
2729
|
+
}
|
|
2730
|
+
if (copyOutputsFrom) {
|
|
2731
|
+
runner.copyOutputs(copyOutputsFrom, nodeId, { dry: true });
|
|
2732
|
+
}
|
|
2733
|
+
}
|
|
2734
|
+
else {
|
|
2735
|
+
await runner.update(event.def, { dry: !!inputs });
|
|
2736
|
+
if (inputs) {
|
|
2737
|
+
runner.setInputs(nodeId, inputs, { dry: false });
|
|
2738
|
+
}
|
|
2739
|
+
}
|
|
2740
|
+
}
|
|
2741
|
+
else {
|
|
2742
|
+
await runner.update(event.def, { dry: event.dry });
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2745
|
+
catch (err) {
|
|
2746
|
+
console.error("[WorkbenchContext] Error updating graph:", err);
|
|
2747
|
+
}
|
|
2748
|
+
});
|
|
2615
2749
|
const offWbdSetValidation = wb.on("validationChanged", (r) => setValidation(r));
|
|
2616
2750
|
const offWbSelectionChanged = wb.on("selectionChanged", (sel) => {
|
|
2617
2751
|
setSelectedNodeId(sel.nodes?.[0]);
|
|
@@ -2619,13 +2753,13 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
2619
2753
|
});
|
|
2620
2754
|
const offWbError = wb.on("error", add("workbench", "error"));
|
|
2621
2755
|
// Registry updates: swap registry and refresh graph validation/UI
|
|
2622
|
-
const offRunnerRegistry = runner.on("registry", (newReg) => {
|
|
2756
|
+
const offRunnerRegistry = runner.on("registry", async (newReg) => {
|
|
2623
2757
|
try {
|
|
2624
2758
|
setRegistry(newReg);
|
|
2625
2759
|
wb.setRegistry(newReg);
|
|
2626
2760
|
// Trigger a graph update so the UI revalidates with new types/enums/nodes
|
|
2627
2761
|
try {
|
|
2628
|
-
runner.update(wb.export());
|
|
2762
|
+
await runner.update(wb.export());
|
|
2629
2763
|
}
|
|
2630
2764
|
catch {
|
|
2631
2765
|
console.error("Failed to update graph definition after registry changed");
|
|
@@ -2656,6 +2790,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
2656
2790
|
offWbValidationChanged();
|
|
2657
2791
|
offWbError();
|
|
2658
2792
|
offWbAddNode();
|
|
2793
|
+
offWbGraphChangedForUpdate();
|
|
2659
2794
|
offWbdSetValidation();
|
|
2660
2795
|
offWbSelectionChanged();
|
|
2661
2796
|
offRunnerRegistry();
|
|
@@ -2673,16 +2808,6 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
2673
2808
|
const stop = useCallback(() => runner.stop(), [runner]);
|
|
2674
2809
|
const step = useCallback(() => runner.step(), [runner]);
|
|
2675
2810
|
const flush = useCallback(() => runner.flush(), [runner]);
|
|
2676
|
-
// Push incremental updates into running engine without full reload
|
|
2677
|
-
const isGraphRunning = isRunning();
|
|
2678
|
-
useEffect(() => {
|
|
2679
|
-
if (isGraphRunning) {
|
|
2680
|
-
try {
|
|
2681
|
-
runner.update(def);
|
|
2682
|
-
}
|
|
2683
|
-
catch { }
|
|
2684
|
-
}
|
|
2685
|
-
}, [runner, isGraphRunning, def, graphTick]);
|
|
2686
2811
|
const validationByNode = useMemo(() => {
|
|
2687
2812
|
const inputs = {};
|
|
2688
2813
|
const outputs = {};
|
|
@@ -3434,7 +3559,7 @@ function NodeContextMenu({ open, clientPos, nodeId, handlers, canRunPull, bakeab
|
|
|
3434
3559
|
return (jsxs("div", { ref: ref, tabIndex: -1, className: "fixed z-[1000] bg-white border border-gray-300 rounded-lg shadow-lg p-1 min-w-[180px] text-sm text-gray-700", style: { left: x, top: y }, onClick: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), onWheel: (e) => e.stopPropagation(), onContextMenu: (e) => {
|
|
3435
3560
|
e.preventDefault();
|
|
3436
3561
|
e.stopPropagation();
|
|
3437
|
-
}, children: [jsxs("div", { className: "px-2 py-1 font-semibold text-gray-700", children: ["Node (", nodeId, ")"] }), jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onDelete, children: "Delete" }), jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onDuplicate, children: "Duplicate" }), canRunPull && (jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onRunPull, children: "Run (pull)" })), jsx("div", { className: "h-px bg-gray-200 my-1" }), bakeableOutputs.length > 0 && (jsxs(Fragment, { children: [jsx("div", { className: "px-2 py-1 font-semibold text-gray-700", children: "Bake" }), bakeableOutputs.map((h) => (jsxs("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: () => handlers.onBake(h), children: ["Bake: ", h] }, h))), jsx("div", { className: "h-px bg-gray-200 my-1" })] })), jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onCopyId, children: "Copy Node ID" })] }));
|
|
3562
|
+
}, children: [jsxs("div", { className: "px-2 py-1 font-semibold text-gray-700", children: ["Node (", nodeId, ")"] }), jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onDelete, children: "Delete" }), jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onDuplicate, children: "Duplicate" }), jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onDuplicateWithEdges, children: "Duplicate with edges" }), canRunPull && (jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onRunPull, children: "Run (pull)" })), jsx("div", { className: "h-px bg-gray-200 my-1" }), bakeableOutputs.length > 0 && (jsxs(Fragment, { children: [jsx("div", { className: "px-2 py-1 font-semibold text-gray-700", children: "Bake" }), bakeableOutputs.map((h) => (jsxs("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: () => handlers.onBake(h), children: ["Bake: ", h] }, h))), jsx("div", { className: "h-px bg-gray-200 my-1" })] })), jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handlers.onCopyId, children: "Copy Node ID" })] }));
|
|
3438
3563
|
}
|
|
3439
3564
|
|
|
3440
3565
|
function createNodeContextMenuHandlers(nodeId, wb, runner, registry, outputsMap, outputTypesMap, onClose) {
|
|
@@ -3467,26 +3592,20 @@ function createNodeContextMenuHandlers(nodeId, wb, runner, registry, outputsMap,
|
|
|
3467
3592
|
const nodeDesc = registry.nodes.get(singleTarget.nodeTypeId);
|
|
3468
3593
|
const inType = getInputTypeId(nodeDesc?.inputs, singleTarget.inputHandle);
|
|
3469
3594
|
const coerced = await coerceIfNeeded(typeId, inType, unwrap(raw));
|
|
3470
|
-
|
|
3595
|
+
wb.addNode({
|
|
3471
3596
|
typeId: singleTarget.nodeTypeId,
|
|
3472
3597
|
position: { x: pos.x + 180, y: pos.y },
|
|
3473
|
-
});
|
|
3474
|
-
runner.update(wb.export());
|
|
3475
|
-
await runner.whenIdle();
|
|
3476
|
-
runner.setInputs(newId, { [singleTarget.inputHandle]: coerced });
|
|
3598
|
+
}, { inputs: { [singleTarget.inputHandle]: coerced } });
|
|
3477
3599
|
return;
|
|
3478
3600
|
}
|
|
3479
3601
|
if (isArray && arrTarget) {
|
|
3480
3602
|
const nodeDesc = registry.nodes.get(arrTarget.nodeTypeId);
|
|
3481
3603
|
const inType = getInputTypeId(nodeDesc?.inputs, arrTarget.inputHandle);
|
|
3482
3604
|
const coerced = await coerceIfNeeded(typeId, inType, unwrap(raw));
|
|
3483
|
-
|
|
3605
|
+
wb.addNode({
|
|
3484
3606
|
typeId: arrTarget.nodeTypeId,
|
|
3485
3607
|
position: { x: pos.x + 180, y: pos.y },
|
|
3486
|
-
});
|
|
3487
|
-
runner.update(wb.export());
|
|
3488
|
-
await runner.whenIdle();
|
|
3489
|
-
runner.setInputs(newId, { [arrTarget.inputHandle]: coerced });
|
|
3608
|
+
}, { inputs: { [arrTarget.inputHandle]: coerced } });
|
|
3490
3609
|
return;
|
|
3491
3610
|
}
|
|
3492
3611
|
if (isArray && elemTarget) {
|
|
@@ -3498,24 +3617,13 @@ function createNodeContextMenuHandlers(nodeId, wb, runner, registry, outputsMap,
|
|
|
3498
3617
|
const COLS = 4;
|
|
3499
3618
|
const DX = 180;
|
|
3500
3619
|
const DY = 160;
|
|
3501
|
-
const nodeIds = [];
|
|
3502
3620
|
for (let idx = 0; idx < coercedItems.length; idx++) {
|
|
3503
3621
|
const col = idx % COLS;
|
|
3504
3622
|
const row = Math.floor(idx / COLS);
|
|
3505
|
-
|
|
3623
|
+
wb.addNode({
|
|
3506
3624
|
typeId: elemTarget.nodeTypeId,
|
|
3507
3625
|
position: { x: pos.x + (col + 1) * DX, y: pos.y + row * DY },
|
|
3508
|
-
});
|
|
3509
|
-
nodeIds.push(newId);
|
|
3510
|
-
}
|
|
3511
|
-
if (nodeIds.length > 0) {
|
|
3512
|
-
runner.update(wb.export());
|
|
3513
|
-
await runner.whenIdle();
|
|
3514
|
-
for (let idx = 0; idx < coercedItems.length; idx++) {
|
|
3515
|
-
runner.setInputs(nodeIds[idx], {
|
|
3516
|
-
[elemTarget.inputHandle]: coercedItems[idx],
|
|
3517
|
-
});
|
|
3518
|
-
}
|
|
3626
|
+
}, { inputs: { [elemTarget.inputHandle]: coercedItems[idx] } });
|
|
3519
3627
|
}
|
|
3520
3628
|
return;
|
|
3521
3629
|
}
|
|
@@ -3533,15 +3641,52 @@ function createNodeContextMenuHandlers(nodeId, wb, runner, registry, outputsMap,
|
|
|
3533
3641
|
if (!n)
|
|
3534
3642
|
return onClose();
|
|
3535
3643
|
const pos = wb.getPositions?.()[nodeId] || { x: 0, y: 0 };
|
|
3536
|
-
const
|
|
3644
|
+
const inboundHandles = new Set(def.edges
|
|
3645
|
+
.filter((e) => e.target.nodeId === nodeId)
|
|
3646
|
+
.map((e) => e.target.handle));
|
|
3647
|
+
const allInputs = runner.getInputs(def)[nodeId] || {};
|
|
3648
|
+
const inputsWithoutBindings = Object.fromEntries(Object.entries(allInputs).filter(([handle]) => !inboundHandles.has(handle)));
|
|
3649
|
+
wb.addNode({
|
|
3650
|
+
typeId: n.typeId,
|
|
3651
|
+
params: n.params,
|
|
3652
|
+
position: { x: pos.x + 24, y: pos.y + 24 },
|
|
3653
|
+
resolvedHandles: n.resolvedHandles,
|
|
3654
|
+
}, {
|
|
3655
|
+
inputs: inputsWithoutBindings,
|
|
3656
|
+
copyOutputsFrom: nodeId,
|
|
3657
|
+
dry: true,
|
|
3658
|
+
});
|
|
3659
|
+
onClose();
|
|
3660
|
+
},
|
|
3661
|
+
onDuplicateWithEdges: async () => {
|
|
3662
|
+
const def = wb.export();
|
|
3663
|
+
const n = def.nodes.find((n) => n.nodeId === nodeId);
|
|
3664
|
+
if (!n)
|
|
3665
|
+
return onClose();
|
|
3666
|
+
const pos = wb.getPositions?.()[nodeId] || { x: 0, y: 0 };
|
|
3667
|
+
// Get inputs without bindings (literal values only)
|
|
3668
|
+
const inputs = runner.getInputs(def)[nodeId] || {};
|
|
3669
|
+
// Add the duplicated node
|
|
3670
|
+
const newNodeId = wb.addNode({
|
|
3537
3671
|
typeId: n.typeId,
|
|
3538
3672
|
params: n.params,
|
|
3539
3673
|
position: { x: pos.x + 24, y: pos.y + 24 },
|
|
3540
|
-
initialInputs: n.initialInputs,
|
|
3541
3674
|
resolvedHandles: n.resolvedHandles,
|
|
3675
|
+
}, {
|
|
3676
|
+
inputs,
|
|
3677
|
+
copyOutputsFrom: nodeId,
|
|
3678
|
+
dry: true,
|
|
3542
3679
|
});
|
|
3543
|
-
|
|
3544
|
-
|
|
3680
|
+
// Find all incoming edges (edges where target is the original node)
|
|
3681
|
+
const incomingEdges = def.edges.filter((e) => e.target.nodeId === nodeId);
|
|
3682
|
+
// Duplicate each incoming edge to point to the new node
|
|
3683
|
+
for (const edge of incomingEdges) {
|
|
3684
|
+
wb.connect({
|
|
3685
|
+
source: edge.source, // Keep the same source
|
|
3686
|
+
target: { nodeId: newNodeId, handle: edge.target.handle }, // Point to new node
|
|
3687
|
+
typeId: edge.typeId,
|
|
3688
|
+
}, { dry: true });
|
|
3689
|
+
}
|
|
3545
3690
|
onClose();
|
|
3546
3691
|
},
|
|
3547
3692
|
onRunPull: async () => {
|
|
@@ -3860,18 +4005,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
3860
4005
|
setNodeMenuOpen(false);
|
|
3861
4006
|
}
|
|
3862
4007
|
};
|
|
3863
|
-
const addNodeAt = useCallback(async (typeId, opts) => {
|
|
3864
|
-
const nodeId = wb.addNode({
|
|
3865
|
-
typeId,
|
|
3866
|
-
initialInputs: opts.initialInputs,
|
|
3867
|
-
position: opts.position,
|
|
3868
|
-
});
|
|
3869
|
-
if (opts.inputs) {
|
|
3870
|
-
runner.update(wb.export());
|
|
3871
|
-
await runner.whenIdle();
|
|
3872
|
-
runner.setInputs(nodeId, opts.inputs);
|
|
3873
|
-
}
|
|
3874
|
-
}, [wb, runner]);
|
|
4008
|
+
const addNodeAt = useCallback(async (typeId, opts) => wb.addNode({ typeId, position: opts.position }, { inputs: opts.inputs }), [wb]);
|
|
3875
4009
|
const onCloseMenu = useCallback(() => {
|
|
3876
4010
|
setMenuOpen(false);
|
|
3877
4011
|
}, []);
|
|
@@ -4115,7 +4249,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
4115
4249
|
const ex = examples.find((e) => e.id === key) ?? examples[0];
|
|
4116
4250
|
if (!ex)
|
|
4117
4251
|
return;
|
|
4118
|
-
const { registry: r, def } = await ex.load();
|
|
4252
|
+
const { registry: r, def, inputs } = await ex.load();
|
|
4119
4253
|
// Keep registry consistent with backend:
|
|
4120
4254
|
// - For local backend, allow example to provide its own registry
|
|
4121
4255
|
// - For remote backend, registry is automatically managed by RemoteGraphRunner
|
|
@@ -4128,6 +4262,12 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
4128
4262
|
await wb.load(def);
|
|
4129
4263
|
// Build a local runtime so seeded defaults are visible pre-run
|
|
4130
4264
|
runner.build(wb.export());
|
|
4265
|
+
// Set initial inputs if provided
|
|
4266
|
+
if (inputs) {
|
|
4267
|
+
for (const [nodeId, map] of Object.entries(inputs)) {
|
|
4268
|
+
runner.setInputs(nodeId, map);
|
|
4269
|
+
}
|
|
4270
|
+
}
|
|
4131
4271
|
runAutoLayout();
|
|
4132
4272
|
setExampleState(key);
|
|
4133
4273
|
onExampleChange?.(key);
|