@bian-womp/spark-workbench 0.2.62 → 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 +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/cjs/index.cjs
CHANGED
|
@@ -190,7 +190,7 @@ 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({
|
|
@@ -203,7 +203,13 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
203
203
|
this.positions[id] = node.position;
|
|
204
204
|
this.emit("graphChanged", {
|
|
205
205
|
def: this.def,
|
|
206
|
-
change: {
|
|
206
|
+
change: {
|
|
207
|
+
type: "addNode",
|
|
208
|
+
nodeId: id,
|
|
209
|
+
inputs: options?.inputs,
|
|
210
|
+
copyOutputsFrom: options?.copyOutputsFrom,
|
|
211
|
+
},
|
|
212
|
+
dry: options?.dry,
|
|
207
213
|
});
|
|
208
214
|
this.refreshValidation();
|
|
209
215
|
return id;
|
|
@@ -218,7 +224,7 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
218
224
|
});
|
|
219
225
|
this.refreshValidation();
|
|
220
226
|
}
|
|
221
|
-
connect(edge) {
|
|
227
|
+
connect(edge, options) {
|
|
222
228
|
const id = edge.id ?? this.genId("e", new Set(this.def.edges.map((e) => e.id)));
|
|
223
229
|
this.def.edges.push({
|
|
224
230
|
id,
|
|
@@ -229,6 +235,7 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
229
235
|
this.emit("graphChanged", {
|
|
230
236
|
def: this.def,
|
|
231
237
|
change: { type: "connect", edgeId: id },
|
|
238
|
+
dry: options?.dry,
|
|
232
239
|
});
|
|
233
240
|
this.refreshValidation();
|
|
234
241
|
return id;
|
|
@@ -426,35 +433,6 @@ class AbstractGraphRunner {
|
|
|
426
433
|
this.stop();
|
|
427
434
|
}
|
|
428
435
|
}
|
|
429
|
-
triggerExternal(nodeId, event, options) {
|
|
430
|
-
this.engine?.triggerExternal(nodeId, event);
|
|
431
|
-
}
|
|
432
|
-
// Batch update multiple inputs on a node and trigger a single run
|
|
433
|
-
setInputs(nodeId, inputs, options) {
|
|
434
|
-
if (!inputs)
|
|
435
|
-
return;
|
|
436
|
-
if (!this.stagedInputs[nodeId])
|
|
437
|
-
this.stagedInputs[nodeId] = {};
|
|
438
|
-
for (const [handle, value] of Object.entries(inputs)) {
|
|
439
|
-
if (value === undefined) {
|
|
440
|
-
delete this.stagedInputs[nodeId][handle];
|
|
441
|
-
}
|
|
442
|
-
else {
|
|
443
|
-
this.stagedInputs[nodeId][handle] = value;
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
if (this.engine) {
|
|
447
|
-
// Running: set all inputs
|
|
448
|
-
this.engine.setInputs(nodeId, inputs);
|
|
449
|
-
}
|
|
450
|
-
else {
|
|
451
|
-
// Not running: emit a single synthetic value event per handle; UI will coalesce
|
|
452
|
-
console.warn("Engine does not exists");
|
|
453
|
-
for (const [handle, value] of Object.entries(inputs)) {
|
|
454
|
-
this.emit("value", { nodeId, handle, value, io: "input" });
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
436
|
async whenIdle() {
|
|
459
437
|
await this.engine?.whenIdle();
|
|
460
438
|
}
|
|
@@ -577,15 +555,19 @@ class LocalGraphRunner extends AbstractGraphRunner {
|
|
|
577
555
|
if (!this.runtime)
|
|
578
556
|
return;
|
|
579
557
|
const wasPaused = this.runtime.isPaused();
|
|
580
|
-
if (
|
|
558
|
+
// Pause runtime if dry option is set (to prevent execution) or if not paused already
|
|
559
|
+
if (options?.dry && !wasPaused) {
|
|
581
560
|
this.runtime.pause();
|
|
561
|
+
}
|
|
582
562
|
try {
|
|
583
563
|
this.runtime.update(def, this.registry);
|
|
584
564
|
this.emit("invalidate", { reason: "graph-updated" });
|
|
585
565
|
}
|
|
586
566
|
finally {
|
|
587
|
-
if
|
|
567
|
+
// Resume only if we paused it due to dry option
|
|
568
|
+
if (options?.dry && !wasPaused) {
|
|
588
569
|
this.runtime.resume();
|
|
570
|
+
}
|
|
589
571
|
}
|
|
590
572
|
}
|
|
591
573
|
launch(def, opts) {
|
|
@@ -677,6 +659,70 @@ class LocalGraphRunner extends AbstractGraphRunner {
|
|
|
677
659
|
}
|
|
678
660
|
return out;
|
|
679
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
|
+
}
|
|
680
726
|
async snapshotFull() {
|
|
681
727
|
const def = undefined; // UI will supply def/positions on download for local
|
|
682
728
|
const inputs = this.getInputs(this.runtime
|
|
@@ -999,7 +1045,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
999
1045
|
// Trigger update so validation/UI refreshes using last known graph
|
|
1000
1046
|
try {
|
|
1001
1047
|
if (this.lastDef)
|
|
1002
|
-
this.update(this.lastDef);
|
|
1048
|
+
await this.update(this.lastDef);
|
|
1003
1049
|
}
|
|
1004
1050
|
catch {
|
|
1005
1051
|
console.error("Failed to update graph definition after registry changed");
|
|
@@ -1014,16 +1060,18 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
1014
1060
|
});
|
|
1015
1061
|
}
|
|
1016
1062
|
build(def) { }
|
|
1017
|
-
update(def, options) {
|
|
1018
|
-
// Remote: forward update
|
|
1019
|
-
this.ensureClient()
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
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
|
+
}
|
|
1027
1075
|
}
|
|
1028
1076
|
launch(def, opts) {
|
|
1029
1077
|
super.launch(def, opts);
|
|
@@ -1144,13 +1192,53 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
1144
1192
|
const client = await this.ensureClient();
|
|
1145
1193
|
await client.flush();
|
|
1146
1194
|
}
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
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];
|
|
1151
1202
|
}
|
|
1152
|
-
|
|
1153
|
-
|
|
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
|
+
}
|
|
1154
1242
|
}
|
|
1155
1243
|
async coerce(from, to, value) {
|
|
1156
1244
|
const client = await this.ensureClient();
|
|
@@ -1208,18 +1296,19 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
1208
1296
|
}
|
|
1209
1297
|
}
|
|
1210
1298
|
}
|
|
1211
|
-
setEnvironment(env, opts) {
|
|
1299
|
+
async setEnvironment(env, opts) {
|
|
1212
1300
|
// Use client if available, otherwise ensure client and then set environment
|
|
1213
1301
|
if (this.client) {
|
|
1214
|
-
this.client.setEnvironment(env, opts)
|
|
1302
|
+
await this.client.setEnvironment(env, opts);
|
|
1215
1303
|
}
|
|
1216
1304
|
else {
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
.
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
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
|
+
}
|
|
1223
1312
|
}
|
|
1224
1313
|
}
|
|
1225
1314
|
getEnvironment() {
|
|
@@ -1280,7 +1369,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
1280
1369
|
}
|
|
1281
1370
|
return out;
|
|
1282
1371
|
}
|
|
1283
|
-
dispose() {
|
|
1372
|
+
async dispose() {
|
|
1284
1373
|
// Idempotent: allow multiple calls safely
|
|
1285
1374
|
if (this.disposed)
|
|
1286
1375
|
return;
|
|
@@ -1301,9 +1390,12 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
1301
1390
|
this.registryFetched = false; // Reset so registry is fetched again on reconnect
|
|
1302
1391
|
this.registryFetching = false; // Reset fetching state
|
|
1303
1392
|
if (clientToDispose) {
|
|
1304
|
-
|
|
1393
|
+
try {
|
|
1394
|
+
await clientToDispose.dispose();
|
|
1395
|
+
}
|
|
1396
|
+
catch (err) {
|
|
1305
1397
|
console.warn("[RemoteGraphRunner] Error disposing client:", err);
|
|
1306
|
-
}
|
|
1398
|
+
}
|
|
1307
1399
|
}
|
|
1308
1400
|
const disconnectedStatus = {
|
|
1309
1401
|
state: "disconnected",
|
|
@@ -2626,6 +2718,36 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
2626
2718
|
}));
|
|
2627
2719
|
}
|
|
2628
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
|
+
});
|
|
2629
2751
|
const offWbdSetValidation = wb.on("validationChanged", (r) => setValidation(r));
|
|
2630
2752
|
const offWbSelectionChanged = wb.on("selectionChanged", (sel) => {
|
|
2631
2753
|
setSelectedNodeId(sel.nodes?.[0]);
|
|
@@ -2633,13 +2755,13 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
2633
2755
|
});
|
|
2634
2756
|
const offWbError = wb.on("error", add("workbench", "error"));
|
|
2635
2757
|
// Registry updates: swap registry and refresh graph validation/UI
|
|
2636
|
-
const offRunnerRegistry = runner.on("registry", (newReg) => {
|
|
2758
|
+
const offRunnerRegistry = runner.on("registry", async (newReg) => {
|
|
2637
2759
|
try {
|
|
2638
2760
|
setRegistry(newReg);
|
|
2639
2761
|
wb.setRegistry(newReg);
|
|
2640
2762
|
// Trigger a graph update so the UI revalidates with new types/enums/nodes
|
|
2641
2763
|
try {
|
|
2642
|
-
runner.update(wb.export());
|
|
2764
|
+
await runner.update(wb.export());
|
|
2643
2765
|
}
|
|
2644
2766
|
catch {
|
|
2645
2767
|
console.error("Failed to update graph definition after registry changed");
|
|
@@ -2670,6 +2792,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
2670
2792
|
offWbValidationChanged();
|
|
2671
2793
|
offWbError();
|
|
2672
2794
|
offWbAddNode();
|
|
2795
|
+
offWbGraphChangedForUpdate();
|
|
2673
2796
|
offWbdSetValidation();
|
|
2674
2797
|
offWbSelectionChanged();
|
|
2675
2798
|
offRunnerRegistry();
|
|
@@ -2687,16 +2810,6 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
2687
2810
|
const stop = React.useCallback(() => runner.stop(), [runner]);
|
|
2688
2811
|
const step = React.useCallback(() => runner.step(), [runner]);
|
|
2689
2812
|
const flush = React.useCallback(() => runner.flush(), [runner]);
|
|
2690
|
-
// Push incremental updates into running engine without full reload
|
|
2691
|
-
const isGraphRunning = isRunning();
|
|
2692
|
-
React.useEffect(() => {
|
|
2693
|
-
if (isGraphRunning) {
|
|
2694
|
-
try {
|
|
2695
|
-
runner.update(def);
|
|
2696
|
-
}
|
|
2697
|
-
catch { }
|
|
2698
|
-
}
|
|
2699
|
-
}, [runner, isGraphRunning, def, graphTick]);
|
|
2700
2813
|
const validationByNode = React.useMemo(() => {
|
|
2701
2814
|
const inputs = {};
|
|
2702
2815
|
const outputs = {};
|
|
@@ -3448,7 +3561,7 @@ function NodeContextMenu({ open, clientPos, nodeId, handlers, canRunPull, bakeab
|
|
|
3448
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) => {
|
|
3449
3562
|
e.preventDefault();
|
|
3450
3563
|
e.stopPropagation();
|
|
3451
|
-
}, 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" })] }));
|
|
3452
3565
|
}
|
|
3453
3566
|
|
|
3454
3567
|
function createNodeContextMenuHandlers(nodeId, wb, runner, registry, outputsMap, outputTypesMap, onClose) {
|
|
@@ -3481,26 +3594,20 @@ function createNodeContextMenuHandlers(nodeId, wb, runner, registry, outputsMap,
|
|
|
3481
3594
|
const nodeDesc = registry.nodes.get(singleTarget.nodeTypeId);
|
|
3482
3595
|
const inType = sparkGraph.getInputTypeId(nodeDesc?.inputs, singleTarget.inputHandle);
|
|
3483
3596
|
const coerced = await coerceIfNeeded(typeId, inType, unwrap(raw));
|
|
3484
|
-
|
|
3597
|
+
wb.addNode({
|
|
3485
3598
|
typeId: singleTarget.nodeTypeId,
|
|
3486
3599
|
position: { x: pos.x + 180, y: pos.y },
|
|
3487
|
-
});
|
|
3488
|
-
runner.update(wb.export());
|
|
3489
|
-
await runner.whenIdle();
|
|
3490
|
-
runner.setInputs(newId, { [singleTarget.inputHandle]: coerced });
|
|
3600
|
+
}, { inputs: { [singleTarget.inputHandle]: coerced } });
|
|
3491
3601
|
return;
|
|
3492
3602
|
}
|
|
3493
3603
|
if (isArray && arrTarget) {
|
|
3494
3604
|
const nodeDesc = registry.nodes.get(arrTarget.nodeTypeId);
|
|
3495
3605
|
const inType = sparkGraph.getInputTypeId(nodeDesc?.inputs, arrTarget.inputHandle);
|
|
3496
3606
|
const coerced = await coerceIfNeeded(typeId, inType, unwrap(raw));
|
|
3497
|
-
|
|
3607
|
+
wb.addNode({
|
|
3498
3608
|
typeId: arrTarget.nodeTypeId,
|
|
3499
3609
|
position: { x: pos.x + 180, y: pos.y },
|
|
3500
|
-
});
|
|
3501
|
-
runner.update(wb.export());
|
|
3502
|
-
await runner.whenIdle();
|
|
3503
|
-
runner.setInputs(newId, { [arrTarget.inputHandle]: coerced });
|
|
3610
|
+
}, { inputs: { [arrTarget.inputHandle]: coerced } });
|
|
3504
3611
|
return;
|
|
3505
3612
|
}
|
|
3506
3613
|
if (isArray && elemTarget) {
|
|
@@ -3512,24 +3619,13 @@ function createNodeContextMenuHandlers(nodeId, wb, runner, registry, outputsMap,
|
|
|
3512
3619
|
const COLS = 4;
|
|
3513
3620
|
const DX = 180;
|
|
3514
3621
|
const DY = 160;
|
|
3515
|
-
const nodeIds = [];
|
|
3516
3622
|
for (let idx = 0; idx < coercedItems.length; idx++) {
|
|
3517
3623
|
const col = idx % COLS;
|
|
3518
3624
|
const row = Math.floor(idx / COLS);
|
|
3519
|
-
|
|
3625
|
+
wb.addNode({
|
|
3520
3626
|
typeId: elemTarget.nodeTypeId,
|
|
3521
3627
|
position: { x: pos.x + (col + 1) * DX, y: pos.y + row * DY },
|
|
3522
|
-
});
|
|
3523
|
-
nodeIds.push(newId);
|
|
3524
|
-
}
|
|
3525
|
-
if (nodeIds.length > 0) {
|
|
3526
|
-
runner.update(wb.export());
|
|
3527
|
-
await runner.whenIdle();
|
|
3528
|
-
for (let idx = 0; idx < coercedItems.length; idx++) {
|
|
3529
|
-
runner.setInputs(nodeIds[idx], {
|
|
3530
|
-
[elemTarget.inputHandle]: coercedItems[idx],
|
|
3531
|
-
});
|
|
3532
|
-
}
|
|
3628
|
+
}, { inputs: { [elemTarget.inputHandle]: coercedItems[idx] } });
|
|
3533
3629
|
}
|
|
3534
3630
|
return;
|
|
3535
3631
|
}
|
|
@@ -3547,14 +3643,52 @@ function createNodeContextMenuHandlers(nodeId, wb, runner, registry, outputsMap,
|
|
|
3547
3643
|
if (!n)
|
|
3548
3644
|
return onClose();
|
|
3549
3645
|
const pos = wb.getPositions?.()[nodeId] || { x: 0, y: 0 };
|
|
3550
|
-
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({
|
|
3551
3673
|
typeId: n.typeId,
|
|
3552
3674
|
params: n.params,
|
|
3553
3675
|
position: { x: pos.x + 24, y: pos.y + 24 },
|
|
3554
3676
|
resolvedHandles: n.resolvedHandles,
|
|
3677
|
+
}, {
|
|
3678
|
+
inputs,
|
|
3679
|
+
copyOutputsFrom: nodeId,
|
|
3680
|
+
dry: true,
|
|
3555
3681
|
});
|
|
3556
|
-
|
|
3557
|
-
|
|
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
|
+
}
|
|
3558
3692
|
onClose();
|
|
3559
3693
|
},
|
|
3560
3694
|
onRunPull: async () => {
|
|
@@ -3873,17 +4007,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
3873
4007
|
setNodeMenuOpen(false);
|
|
3874
4008
|
}
|
|
3875
4009
|
};
|
|
3876
|
-
const addNodeAt = React.useCallback(async (typeId, opts) => {
|
|
3877
|
-
const nodeId = wb.addNode({
|
|
3878
|
-
typeId,
|
|
3879
|
-
position: opts.position,
|
|
3880
|
-
});
|
|
3881
|
-
if (opts.inputs) {
|
|
3882
|
-
runner.update(wb.export());
|
|
3883
|
-
await runner.whenIdle();
|
|
3884
|
-
runner.setInputs(nodeId, opts.inputs);
|
|
3885
|
-
}
|
|
3886
|
-
}, [wb, runner]);
|
|
4010
|
+
const addNodeAt = React.useCallback(async (typeId, opts) => wb.addNode({ typeId, position: opts.position }, { inputs: opts.inputs }), [wb]);
|
|
3887
4011
|
const onCloseMenu = React.useCallback(() => {
|
|
3888
4012
|
setMenuOpen(false);
|
|
3889
4013
|
}, []);
|