@bian-womp/spark-workbench 0.2.62 → 0.2.64
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 +236 -112
- 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/context/ContextMenuHandlers.d.ts +1 -0
- 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.provider.d.ts.map +1 -1
- package/lib/cjs/src/runtime/AbstractGraphRunner.d.ts +12 -9
- package/lib/cjs/src/runtime/AbstractGraphRunner.d.ts.map +1 -1
- package/lib/cjs/src/runtime/IGraphRunner.d.ts +7 -3
- package/lib/cjs/src/runtime/IGraphRunner.d.ts.map +1 -1
- package/lib/cjs/src/runtime/LocalGraphRunner.d.ts +9 -0
- package/lib/cjs/src/runtime/LocalGraphRunner.d.ts.map +1 -1
- package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts +11 -4
- package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
- package/lib/esm/index.js +236 -112
- 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/context/ContextMenuHandlers.d.ts +1 -0
- 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.provider.d.ts.map +1 -1
- package/lib/esm/src/runtime/AbstractGraphRunner.d.ts +12 -9
- package/lib/esm/src/runtime/AbstractGraphRunner.d.ts.map +1 -1
- package/lib/esm/src/runtime/IGraphRunner.d.ts +7 -3
- package/lib/esm/src/runtime/IGraphRunner.d.ts.map +1 -1
- package/lib/esm/src/runtime/LocalGraphRunner.d.ts +9 -0
- package/lib/esm/src/runtime/LocalGraphRunner.d.ts.map +1 -1
- package/lib/esm/src/runtime/RemoteGraphRunner.d.ts +11 -4
- package/lib/esm/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
- package/package.json +4 -4
package/lib/esm/index.js
CHANGED
|
@@ -188,7 +188,7 @@ 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({
|
|
@@ -201,7 +201,13 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
201
201
|
this.positions[id] = node.position;
|
|
202
202
|
this.emit("graphChanged", {
|
|
203
203
|
def: this.def,
|
|
204
|
-
change: {
|
|
204
|
+
change: {
|
|
205
|
+
type: "addNode",
|
|
206
|
+
nodeId: id,
|
|
207
|
+
inputs: options?.inputs,
|
|
208
|
+
copyOutputsFrom: options?.copyOutputsFrom,
|
|
209
|
+
},
|
|
210
|
+
dry: options?.dry,
|
|
205
211
|
});
|
|
206
212
|
this.refreshValidation();
|
|
207
213
|
return id;
|
|
@@ -216,7 +222,7 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
216
222
|
});
|
|
217
223
|
this.refreshValidation();
|
|
218
224
|
}
|
|
219
|
-
connect(edge) {
|
|
225
|
+
connect(edge, options) {
|
|
220
226
|
const id = edge.id ?? this.genId("e", new Set(this.def.edges.map((e) => e.id)));
|
|
221
227
|
this.def.edges.push({
|
|
222
228
|
id,
|
|
@@ -227,6 +233,7 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
227
233
|
this.emit("graphChanged", {
|
|
228
234
|
def: this.def,
|
|
229
235
|
change: { type: "connect", edgeId: id },
|
|
236
|
+
dry: options?.dry,
|
|
230
237
|
});
|
|
231
238
|
this.refreshValidation();
|
|
232
239
|
return id;
|
|
@@ -424,35 +431,6 @@ class AbstractGraphRunner {
|
|
|
424
431
|
this.stop();
|
|
425
432
|
}
|
|
426
433
|
}
|
|
427
|
-
triggerExternal(nodeId, event, options) {
|
|
428
|
-
this.engine?.triggerExternal(nodeId, event);
|
|
429
|
-
}
|
|
430
|
-
// Batch update multiple inputs on a node and trigger a single run
|
|
431
|
-
setInputs(nodeId, inputs, options) {
|
|
432
|
-
if (!inputs)
|
|
433
|
-
return;
|
|
434
|
-
if (!this.stagedInputs[nodeId])
|
|
435
|
-
this.stagedInputs[nodeId] = {};
|
|
436
|
-
for (const [handle, value] of Object.entries(inputs)) {
|
|
437
|
-
if (value === undefined) {
|
|
438
|
-
delete this.stagedInputs[nodeId][handle];
|
|
439
|
-
}
|
|
440
|
-
else {
|
|
441
|
-
this.stagedInputs[nodeId][handle] = value;
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
if (this.engine) {
|
|
445
|
-
// Running: set all inputs
|
|
446
|
-
this.engine.setInputs(nodeId, inputs);
|
|
447
|
-
}
|
|
448
|
-
else {
|
|
449
|
-
// Not running: emit a single synthetic value event per handle; UI will coalesce
|
|
450
|
-
console.warn("Engine does not exists");
|
|
451
|
-
for (const [handle, value] of Object.entries(inputs)) {
|
|
452
|
-
this.emit("value", { nodeId, handle, value, io: "input" });
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
434
|
async whenIdle() {
|
|
457
435
|
await this.engine?.whenIdle();
|
|
458
436
|
}
|
|
@@ -575,15 +553,19 @@ class LocalGraphRunner extends AbstractGraphRunner {
|
|
|
575
553
|
if (!this.runtime)
|
|
576
554
|
return;
|
|
577
555
|
const wasPaused = this.runtime.isPaused();
|
|
578
|
-
if (
|
|
556
|
+
// Pause runtime if dry option is set (to prevent execution) or if not paused already
|
|
557
|
+
if (options?.dry && !wasPaused) {
|
|
579
558
|
this.runtime.pause();
|
|
559
|
+
}
|
|
580
560
|
try {
|
|
581
561
|
this.runtime.update(def, this.registry);
|
|
582
562
|
this.emit("invalidate", { reason: "graph-updated" });
|
|
583
563
|
}
|
|
584
564
|
finally {
|
|
585
|
-
if
|
|
565
|
+
// Resume only if we paused it due to dry option
|
|
566
|
+
if (options?.dry && !wasPaused) {
|
|
586
567
|
this.runtime.resume();
|
|
568
|
+
}
|
|
587
569
|
}
|
|
588
570
|
}
|
|
589
571
|
launch(def, opts) {
|
|
@@ -675,6 +657,70 @@ class LocalGraphRunner extends AbstractGraphRunner {
|
|
|
675
657
|
}
|
|
676
658
|
return out;
|
|
677
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
|
+
}
|
|
678
724
|
async snapshotFull() {
|
|
679
725
|
const def = undefined; // UI will supply def/positions on download for local
|
|
680
726
|
const inputs = this.getInputs(this.runtime
|
|
@@ -997,7 +1043,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
997
1043
|
// Trigger update so validation/UI refreshes using last known graph
|
|
998
1044
|
try {
|
|
999
1045
|
if (this.lastDef)
|
|
1000
|
-
this.update(this.lastDef);
|
|
1046
|
+
await this.update(this.lastDef);
|
|
1001
1047
|
}
|
|
1002
1048
|
catch {
|
|
1003
1049
|
console.error("Failed to update graph definition after registry changed");
|
|
@@ -1012,16 +1058,18 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
1012
1058
|
});
|
|
1013
1059
|
}
|
|
1014
1060
|
build(def) { }
|
|
1015
|
-
update(def, options) {
|
|
1016
|
-
// Remote: forward update
|
|
1017
|
-
this.ensureClient()
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
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
|
+
}
|
|
1025
1073
|
}
|
|
1026
1074
|
launch(def, opts) {
|
|
1027
1075
|
super.launch(def, opts);
|
|
@@ -1142,13 +1190,53 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
1142
1190
|
const client = await this.ensureClient();
|
|
1143
1191
|
await client.flush();
|
|
1144
1192
|
}
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
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];
|
|
1149
1200
|
}
|
|
1150
|
-
|
|
1151
|
-
|
|
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
|
+
}
|
|
1152
1240
|
}
|
|
1153
1241
|
async coerce(from, to, value) {
|
|
1154
1242
|
const client = await this.ensureClient();
|
|
@@ -1206,18 +1294,19 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
1206
1294
|
}
|
|
1207
1295
|
}
|
|
1208
1296
|
}
|
|
1209
|
-
setEnvironment(env, opts) {
|
|
1297
|
+
async setEnvironment(env, opts) {
|
|
1210
1298
|
// Use client if available, otherwise ensure client and then set environment
|
|
1211
1299
|
if (this.client) {
|
|
1212
|
-
this.client.setEnvironment(env, opts)
|
|
1300
|
+
await this.client.setEnvironment(env, opts);
|
|
1213
1301
|
}
|
|
1214
1302
|
else {
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
.
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
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
|
+
}
|
|
1221
1310
|
}
|
|
1222
1311
|
}
|
|
1223
1312
|
getEnvironment() {
|
|
@@ -1278,7 +1367,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
1278
1367
|
}
|
|
1279
1368
|
return out;
|
|
1280
1369
|
}
|
|
1281
|
-
dispose() {
|
|
1370
|
+
async dispose() {
|
|
1282
1371
|
// Idempotent: allow multiple calls safely
|
|
1283
1372
|
if (this.disposed)
|
|
1284
1373
|
return;
|
|
@@ -1299,9 +1388,12 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
1299
1388
|
this.registryFetched = false; // Reset so registry is fetched again on reconnect
|
|
1300
1389
|
this.registryFetching = false; // Reset fetching state
|
|
1301
1390
|
if (clientToDispose) {
|
|
1302
|
-
|
|
1391
|
+
try {
|
|
1392
|
+
await clientToDispose.dispose();
|
|
1393
|
+
}
|
|
1394
|
+
catch (err) {
|
|
1303
1395
|
console.warn("[RemoteGraphRunner] Error disposing client:", err);
|
|
1304
|
-
}
|
|
1396
|
+
}
|
|
1305
1397
|
}
|
|
1306
1398
|
const disconnectedStatus = {
|
|
1307
1399
|
state: "disconnected",
|
|
@@ -2624,6 +2716,36 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
2624
2716
|
}));
|
|
2625
2717
|
}
|
|
2626
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
|
+
});
|
|
2627
2749
|
const offWbdSetValidation = wb.on("validationChanged", (r) => setValidation(r));
|
|
2628
2750
|
const offWbSelectionChanged = wb.on("selectionChanged", (sel) => {
|
|
2629
2751
|
setSelectedNodeId(sel.nodes?.[0]);
|
|
@@ -2631,13 +2753,13 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
2631
2753
|
});
|
|
2632
2754
|
const offWbError = wb.on("error", add("workbench", "error"));
|
|
2633
2755
|
// Registry updates: swap registry and refresh graph validation/UI
|
|
2634
|
-
const offRunnerRegistry = runner.on("registry", (newReg) => {
|
|
2756
|
+
const offRunnerRegistry = runner.on("registry", async (newReg) => {
|
|
2635
2757
|
try {
|
|
2636
2758
|
setRegistry(newReg);
|
|
2637
2759
|
wb.setRegistry(newReg);
|
|
2638
2760
|
// Trigger a graph update so the UI revalidates with new types/enums/nodes
|
|
2639
2761
|
try {
|
|
2640
|
-
runner.update(wb.export());
|
|
2762
|
+
await runner.update(wb.export());
|
|
2641
2763
|
}
|
|
2642
2764
|
catch {
|
|
2643
2765
|
console.error("Failed to update graph definition after registry changed");
|
|
@@ -2668,6 +2790,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
2668
2790
|
offWbValidationChanged();
|
|
2669
2791
|
offWbError();
|
|
2670
2792
|
offWbAddNode();
|
|
2793
|
+
offWbGraphChangedForUpdate();
|
|
2671
2794
|
offWbdSetValidation();
|
|
2672
2795
|
offWbSelectionChanged();
|
|
2673
2796
|
offRunnerRegistry();
|
|
@@ -2685,16 +2808,6 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
2685
2808
|
const stop = useCallback(() => runner.stop(), [runner]);
|
|
2686
2809
|
const step = useCallback(() => runner.step(), [runner]);
|
|
2687
2810
|
const flush = useCallback(() => runner.flush(), [runner]);
|
|
2688
|
-
// Push incremental updates into running engine without full reload
|
|
2689
|
-
const isGraphRunning = isRunning();
|
|
2690
|
-
useEffect(() => {
|
|
2691
|
-
if (isGraphRunning) {
|
|
2692
|
-
try {
|
|
2693
|
-
runner.update(def);
|
|
2694
|
-
}
|
|
2695
|
-
catch { }
|
|
2696
|
-
}
|
|
2697
|
-
}, [runner, isGraphRunning, def, graphTick]);
|
|
2698
2811
|
const validationByNode = useMemo(() => {
|
|
2699
2812
|
const inputs = {};
|
|
2700
2813
|
const outputs = {};
|
|
@@ -3446,7 +3559,7 @@ function NodeContextMenu({ open, clientPos, nodeId, handlers, canRunPull, bakeab
|
|
|
3446
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) => {
|
|
3447
3560
|
e.preventDefault();
|
|
3448
3561
|
e.stopPropagation();
|
|
3449
|
-
}, 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" })] }));
|
|
3450
3563
|
}
|
|
3451
3564
|
|
|
3452
3565
|
function createNodeContextMenuHandlers(nodeId, wb, runner, registry, outputsMap, outputTypesMap, onClose) {
|
|
@@ -3479,26 +3592,20 @@ function createNodeContextMenuHandlers(nodeId, wb, runner, registry, outputsMap,
|
|
|
3479
3592
|
const nodeDesc = registry.nodes.get(singleTarget.nodeTypeId);
|
|
3480
3593
|
const inType = getInputTypeId(nodeDesc?.inputs, singleTarget.inputHandle);
|
|
3481
3594
|
const coerced = await coerceIfNeeded(typeId, inType, unwrap(raw));
|
|
3482
|
-
|
|
3595
|
+
wb.addNode({
|
|
3483
3596
|
typeId: singleTarget.nodeTypeId,
|
|
3484
3597
|
position: { x: pos.x + 180, y: pos.y },
|
|
3485
|
-
});
|
|
3486
|
-
runner.update(wb.export());
|
|
3487
|
-
await runner.whenIdle();
|
|
3488
|
-
runner.setInputs(newId, { [singleTarget.inputHandle]: coerced });
|
|
3598
|
+
}, { inputs: { [singleTarget.inputHandle]: coerced } });
|
|
3489
3599
|
return;
|
|
3490
3600
|
}
|
|
3491
3601
|
if (isArray && arrTarget) {
|
|
3492
3602
|
const nodeDesc = registry.nodes.get(arrTarget.nodeTypeId);
|
|
3493
3603
|
const inType = getInputTypeId(nodeDesc?.inputs, arrTarget.inputHandle);
|
|
3494
3604
|
const coerced = await coerceIfNeeded(typeId, inType, unwrap(raw));
|
|
3495
|
-
|
|
3605
|
+
wb.addNode({
|
|
3496
3606
|
typeId: arrTarget.nodeTypeId,
|
|
3497
3607
|
position: { x: pos.x + 180, y: pos.y },
|
|
3498
|
-
});
|
|
3499
|
-
runner.update(wb.export());
|
|
3500
|
-
await runner.whenIdle();
|
|
3501
|
-
runner.setInputs(newId, { [arrTarget.inputHandle]: coerced });
|
|
3608
|
+
}, { inputs: { [arrTarget.inputHandle]: coerced } });
|
|
3502
3609
|
return;
|
|
3503
3610
|
}
|
|
3504
3611
|
if (isArray && elemTarget) {
|
|
@@ -3510,24 +3617,13 @@ function createNodeContextMenuHandlers(nodeId, wb, runner, registry, outputsMap,
|
|
|
3510
3617
|
const COLS = 4;
|
|
3511
3618
|
const DX = 180;
|
|
3512
3619
|
const DY = 160;
|
|
3513
|
-
const nodeIds = [];
|
|
3514
3620
|
for (let idx = 0; idx < coercedItems.length; idx++) {
|
|
3515
3621
|
const col = idx % COLS;
|
|
3516
3622
|
const row = Math.floor(idx / COLS);
|
|
3517
|
-
|
|
3623
|
+
wb.addNode({
|
|
3518
3624
|
typeId: elemTarget.nodeTypeId,
|
|
3519
3625
|
position: { x: pos.x + (col + 1) * DX, y: pos.y + row * DY },
|
|
3520
|
-
});
|
|
3521
|
-
nodeIds.push(newId);
|
|
3522
|
-
}
|
|
3523
|
-
if (nodeIds.length > 0) {
|
|
3524
|
-
runner.update(wb.export());
|
|
3525
|
-
await runner.whenIdle();
|
|
3526
|
-
for (let idx = 0; idx < coercedItems.length; idx++) {
|
|
3527
|
-
runner.setInputs(nodeIds[idx], {
|
|
3528
|
-
[elemTarget.inputHandle]: coercedItems[idx],
|
|
3529
|
-
});
|
|
3530
|
-
}
|
|
3626
|
+
}, { inputs: { [elemTarget.inputHandle]: coercedItems[idx] } });
|
|
3531
3627
|
}
|
|
3532
3628
|
return;
|
|
3533
3629
|
}
|
|
@@ -3545,14 +3641,52 @@ function createNodeContextMenuHandlers(nodeId, wb, runner, registry, outputsMap,
|
|
|
3545
3641
|
if (!n)
|
|
3546
3642
|
return onClose();
|
|
3547
3643
|
const pos = wb.getPositions?.()[nodeId] || { x: 0, y: 0 };
|
|
3548
|
-
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({
|
|
3549
3671
|
typeId: n.typeId,
|
|
3550
3672
|
params: n.params,
|
|
3551
3673
|
position: { x: pos.x + 24, y: pos.y + 24 },
|
|
3552
3674
|
resolvedHandles: n.resolvedHandles,
|
|
3675
|
+
}, {
|
|
3676
|
+
inputs,
|
|
3677
|
+
copyOutputsFrom: nodeId,
|
|
3678
|
+
dry: true,
|
|
3553
3679
|
});
|
|
3554
|
-
|
|
3555
|
-
|
|
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
|
+
}
|
|
3556
3690
|
onClose();
|
|
3557
3691
|
},
|
|
3558
3692
|
onRunPull: async () => {
|
|
@@ -3871,17 +4005,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
3871
4005
|
setNodeMenuOpen(false);
|
|
3872
4006
|
}
|
|
3873
4007
|
};
|
|
3874
|
-
const addNodeAt = useCallback(async (typeId, opts) => {
|
|
3875
|
-
const nodeId = wb.addNode({
|
|
3876
|
-
typeId,
|
|
3877
|
-
position: opts.position,
|
|
3878
|
-
});
|
|
3879
|
-
if (opts.inputs) {
|
|
3880
|
-
runner.update(wb.export());
|
|
3881
|
-
await runner.whenIdle();
|
|
3882
|
-
runner.setInputs(nodeId, opts.inputs);
|
|
3883
|
-
}
|
|
3884
|
-
}, [wb, runner]);
|
|
4008
|
+
const addNodeAt = useCallback(async (typeId, opts) => wb.addNode({ typeId, position: opts.position }, { inputs: opts.inputs }), [wb]);
|
|
3885
4009
|
const onCloseMenu = useCallback(() => {
|
|
3886
4010
|
setMenuOpen(false);
|
|
3887
4011
|
}, []);
|