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