@bian-womp/spark-workbench 0.3.1 → 0.3.3
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 +265 -148
- package/lib/cjs/index.cjs.map +1 -1
- package/lib/cjs/src/core/InMemoryWorkbench.d.ts +53 -0
- package/lib/cjs/src/core/InMemoryWorkbench.d.ts.map +1 -1
- package/lib/cjs/src/core/contracts.d.ts +16 -0
- package/lib/cjs/src/core/contracts.d.ts.map +1 -1
- package/lib/cjs/src/misc/WorkbenchCanvas.d.ts.map +1 -1
- package/lib/cjs/src/misc/WorkbenchStudio.d.ts.map +1 -1
- package/lib/cjs/src/misc/context/WorkbenchContext.d.ts +0 -3
- package/lib/cjs/src/misc/context/WorkbenchContext.d.ts.map +1 -1
- package/lib/cjs/src/misc/context/WorkbenchContext.provider.d.ts.map +1 -1
- package/lib/cjs/src/misc/load.d.ts.map +1 -1
- package/lib/cjs/src/misc/mapping.d.ts +7 -0
- package/lib/cjs/src/misc/mapping.d.ts.map +1 -1
- package/lib/cjs/src/runtime/AbstractGraphRunner.d.ts +6 -4
- package/lib/cjs/src/runtime/AbstractGraphRunner.d.ts.map +1 -1
- package/lib/cjs/src/runtime/IGraphRunner.d.ts +5 -3
- package/lib/cjs/src/runtime/IGraphRunner.d.ts.map +1 -1
- package/lib/cjs/src/runtime/LocalGraphRunner.d.ts +9 -2
- package/lib/cjs/src/runtime/LocalGraphRunner.d.ts.map +1 -1
- package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts +4 -0
- package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
- package/lib/esm/index.js +266 -149
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/src/core/InMemoryWorkbench.d.ts +53 -0
- package/lib/esm/src/core/InMemoryWorkbench.d.ts.map +1 -1
- package/lib/esm/src/core/contracts.d.ts +16 -0
- package/lib/esm/src/core/contracts.d.ts.map +1 -1
- package/lib/esm/src/misc/WorkbenchCanvas.d.ts.map +1 -1
- package/lib/esm/src/misc/WorkbenchStudio.d.ts.map +1 -1
- package/lib/esm/src/misc/context/WorkbenchContext.d.ts +0 -3
- package/lib/esm/src/misc/context/WorkbenchContext.d.ts.map +1 -1
- package/lib/esm/src/misc/context/WorkbenchContext.provider.d.ts.map +1 -1
- package/lib/esm/src/misc/load.d.ts.map +1 -1
- package/lib/esm/src/misc/mapping.d.ts +7 -0
- package/lib/esm/src/misc/mapping.d.ts.map +1 -1
- package/lib/esm/src/runtime/AbstractGraphRunner.d.ts +6 -4
- package/lib/esm/src/runtime/AbstractGraphRunner.d.ts.map +1 -1
- package/lib/esm/src/runtime/IGraphRunner.d.ts +5 -3
- package/lib/esm/src/runtime/IGraphRunner.d.ts.map +1 -1
- package/lib/esm/src/runtime/LocalGraphRunner.d.ts +9 -2
- package/lib/esm/src/runtime/LocalGraphRunner.d.ts.map +1 -1
- package/lib/esm/src/runtime/RemoteGraphRunner.d.ts +4 -0
- package/lib/esm/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
- package/package.json +4 -4
package/lib/esm/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { generateId, createSimpleGraphRegistry, GraphBuilder, getTypedOutputValue, isTypedOutput, getInputTypeId,
|
|
1
|
+
import { generateId, createSimpleGraphRegistry, GraphBuilder, getTypedOutputValue, isTypedOutput, getInputTypeId, LocalEngine, parseJsonPath, setValueAtPathWithCreation, getTypedOutputTypeId, isInputPrivate, offsetImportedPositions, createSimpleGraphDef, createAsyncGraphDef, createAsyncGraphRegistry, createProgressGraphDef, createProgressGraphRegistry, createValidationGraphDef, createValidationGraphRegistry } from '@bian-womp/spark-graph';
|
|
2
2
|
import lod from 'lodash';
|
|
3
3
|
import { RemoteRuntimeClient } from '@bian-womp/spark-remote';
|
|
4
4
|
import { Position, Handle, NodeResizer, getBezierPath, BaseEdge, useReactFlow, ReactFlowProvider, ReactFlow, Background, BackgroundVariant, MiniMap, Controls } from '@xyflow/react';
|
|
@@ -144,6 +144,7 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
144
144
|
edges: [],
|
|
145
145
|
};
|
|
146
146
|
this.nodeNames = {};
|
|
147
|
+
this.customData = {};
|
|
147
148
|
this.runtimeState = null;
|
|
148
149
|
this.viewport = null;
|
|
149
150
|
this.historyState = undefined;
|
|
@@ -172,6 +173,17 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
172
173
|
const filteredSizes = Object.fromEntries(Object.entries(this.sizes).filter(([id]) => defNodeIds.has(id)));
|
|
173
174
|
const filteredNodes = this.selection.nodes.filter((id) => defNodeIds.has(id));
|
|
174
175
|
const filteredEdges = this.selection.edges.filter((id) => defEdgeIds.has(id));
|
|
176
|
+
// Clean up extData for removed nodes/edges
|
|
177
|
+
if (this.customData.nodes) {
|
|
178
|
+
const filteredExtNodes = Object.fromEntries(Object.entries(this.customData.nodes).filter(([id]) => defNodeIds.has(id)));
|
|
179
|
+
this.customData.nodes =
|
|
180
|
+
Object.keys(filteredExtNodes).length > 0 ? filteredExtNodes : undefined;
|
|
181
|
+
}
|
|
182
|
+
if (this.customData.edges) {
|
|
183
|
+
const filteredExtEdges = Object.fromEntries(Object.entries(this.customData.edges).filter(([id]) => defEdgeIds.has(id)));
|
|
184
|
+
this.customData.edges =
|
|
185
|
+
Object.keys(filteredExtEdges).length > 0 ? filteredExtEdges : undefined;
|
|
186
|
+
}
|
|
175
187
|
this.positions = filteredPositions;
|
|
176
188
|
this.sizes = filteredSizes;
|
|
177
189
|
this.selection = { nodes: filteredNodes, edges: filteredEdges };
|
|
@@ -849,6 +861,107 @@ class InMemoryWorkbench extends AbstractWorkbench {
|
|
|
849
861
|
...options,
|
|
850
862
|
});
|
|
851
863
|
}
|
|
864
|
+
/**
|
|
865
|
+
* Get custom data for a specific node.
|
|
866
|
+
*/
|
|
867
|
+
getCustomNodeData(nodeId) {
|
|
868
|
+
return this.customData.nodes?.[nodeId];
|
|
869
|
+
}
|
|
870
|
+
/**
|
|
871
|
+
* Set custom data for a specific node.
|
|
872
|
+
*/
|
|
873
|
+
setCustomNodeData(nodeId, data, options) {
|
|
874
|
+
if (!this.customData.nodes) {
|
|
875
|
+
this.customData.nodes = {};
|
|
876
|
+
}
|
|
877
|
+
if (data === undefined) {
|
|
878
|
+
delete this.customData.nodes[nodeId];
|
|
879
|
+
if (Object.keys(this.customData.nodes).length === 0) {
|
|
880
|
+
delete this.customData.nodes;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
else {
|
|
884
|
+
this.customData.nodes[nodeId] = data;
|
|
885
|
+
}
|
|
886
|
+
this.emit("graphUiChanged", {
|
|
887
|
+
change: { type: "customNodeData", nodeId, data },
|
|
888
|
+
...options,
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* Get custom data for a specific edge.
|
|
893
|
+
*/
|
|
894
|
+
getCustomEdgeData(edgeId) {
|
|
895
|
+
return this.customData.edges?.[edgeId];
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
898
|
+
* Set custom data for a specific edge.
|
|
899
|
+
*/
|
|
900
|
+
setCustomEdgeData(edgeId, data, options) {
|
|
901
|
+
if (!this.customData.edges) {
|
|
902
|
+
this.customData.edges = {};
|
|
903
|
+
}
|
|
904
|
+
if (data === undefined) {
|
|
905
|
+
delete this.customData.edges[edgeId];
|
|
906
|
+
if (Object.keys(this.customData.edges).length === 0) {
|
|
907
|
+
delete this.customData.edges;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
else {
|
|
911
|
+
this.customData.edges[edgeId] = data;
|
|
912
|
+
}
|
|
913
|
+
this.emit("graphUiChanged", {
|
|
914
|
+
change: { type: "customEdgeData", edgeId, data },
|
|
915
|
+
...options,
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
/**
|
|
919
|
+
* Get custom metadata.
|
|
920
|
+
*/
|
|
921
|
+
getCustomMetaData() {
|
|
922
|
+
return this.customData.meta;
|
|
923
|
+
}
|
|
924
|
+
/**
|
|
925
|
+
* Set custom metadata.
|
|
926
|
+
*/
|
|
927
|
+
setCustomMetaData(meta, options) {
|
|
928
|
+
if (meta === undefined) {
|
|
929
|
+
delete this.customData.meta;
|
|
930
|
+
}
|
|
931
|
+
else {
|
|
932
|
+
this.customData.meta = meta;
|
|
933
|
+
}
|
|
934
|
+
this.emit("graphUiChanged", {
|
|
935
|
+
change: { type: "customMetaData", meta },
|
|
936
|
+
...options,
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
/**
|
|
940
|
+
* Get all custom data.
|
|
941
|
+
*/
|
|
942
|
+
getCustomData() {
|
|
943
|
+
return { ...this.customData };
|
|
944
|
+
}
|
|
945
|
+
/**
|
|
946
|
+
* Set all custom data.
|
|
947
|
+
*/
|
|
948
|
+
setCustomData(custom, options) {
|
|
949
|
+
if (custom === undefined) {
|
|
950
|
+
this.customData = {};
|
|
951
|
+
}
|
|
952
|
+
else {
|
|
953
|
+
this.customData = lod.pick(custom, ["nodes", "edges", "meta"]);
|
|
954
|
+
}
|
|
955
|
+
this.emit("graphUiChanged", {
|
|
956
|
+
change: {
|
|
957
|
+
type: "customData",
|
|
958
|
+
nodes: custom?.nodes,
|
|
959
|
+
edges: custom?.edges,
|
|
960
|
+
meta: custom?.meta,
|
|
961
|
+
},
|
|
962
|
+
...options,
|
|
963
|
+
});
|
|
964
|
+
}
|
|
852
965
|
}
|
|
853
966
|
|
|
854
967
|
class CLIWorkbench {
|
|
@@ -909,36 +1022,14 @@ class AbstractGraphRunner {
|
|
|
909
1022
|
this.stagedInputs = {};
|
|
910
1023
|
this.runnerId = "";
|
|
911
1024
|
}
|
|
912
|
-
launch(def, opts) {
|
|
913
|
-
// Auto-stop if engine is already running
|
|
914
|
-
if (this.engine) {
|
|
915
|
-
this.stop();
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
1025
|
async whenIdle() {
|
|
919
1026
|
await this.engine?.whenIdle();
|
|
920
1027
|
}
|
|
921
|
-
stop() {
|
|
922
|
-
if (!this.engine)
|
|
923
|
-
return;
|
|
924
|
-
// Dispose engine (cleans up timers, listeners, etc.)
|
|
925
|
-
this.engine.dispose();
|
|
926
|
-
this.engine = undefined;
|
|
927
|
-
// Emit status but keep runtime alive
|
|
928
|
-
if (this.runMode) {
|
|
929
|
-
this.runMode = undefined;
|
|
930
|
-
this.emit("status", { running: false, runMode: undefined });
|
|
931
|
-
}
|
|
932
|
-
}
|
|
933
1028
|
setRunMode(runMode) {
|
|
934
|
-
if (
|
|
935
|
-
|
|
1029
|
+
if (this.engine) {
|
|
1030
|
+
this.engine.setRunMode(runMode);
|
|
1031
|
+
this.emit("status", { running: true, runMode });
|
|
936
1032
|
}
|
|
937
|
-
// Update engine run mode (this will update pause/resume state)
|
|
938
|
-
this.engine.setRunMode(runMode);
|
|
939
|
-
// Update local state and emit status event
|
|
940
|
-
this.runMode = runMode;
|
|
941
|
-
this.emit("status", { running: true, runMode: this.runMode });
|
|
942
1033
|
}
|
|
943
1034
|
getInputDefaults(def) {
|
|
944
1035
|
const out = {};
|
|
@@ -968,17 +1059,10 @@ class AbstractGraphRunner {
|
|
|
968
1059
|
this.engine = undefined;
|
|
969
1060
|
this.runtime?.dispose();
|
|
970
1061
|
this.runtime = undefined;
|
|
971
|
-
if (this.runMode) {
|
|
972
|
-
this.runMode = undefined;
|
|
973
|
-
this.emit("status", { running: false, runMode: undefined });
|
|
974
|
-
}
|
|
975
1062
|
}
|
|
976
1063
|
isRunning() {
|
|
977
1064
|
return !!this.engine;
|
|
978
1065
|
}
|
|
979
|
-
getRunMode() {
|
|
980
|
-
return this.runMode;
|
|
981
|
-
}
|
|
982
1066
|
// Optional undo/redo support
|
|
983
1067
|
async undo() {
|
|
984
1068
|
return false;
|
|
@@ -997,12 +1081,12 @@ let localRunnerCounter = 0;
|
|
|
997
1081
|
class LocalGraphRunner extends AbstractGraphRunner {
|
|
998
1082
|
constructor(registry) {
|
|
999
1083
|
super(registry, { kind: "local" });
|
|
1084
|
+
this.extData = {};
|
|
1000
1085
|
this.setEnvironment = (env, opts) => {
|
|
1001
1086
|
if (!this.runtime)
|
|
1002
1087
|
return;
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
this.runtime.pause();
|
|
1088
|
+
// Use requestPause for dry mode to temporarily pause without affecting base run mode
|
|
1089
|
+
const releasePause = opts?.dry ? this.runtime.requestPause() : null;
|
|
1006
1090
|
try {
|
|
1007
1091
|
if (opts?.merge) {
|
|
1008
1092
|
const current = this.runtime.getEnvironment();
|
|
@@ -1014,8 +1098,7 @@ class LocalGraphRunner extends AbstractGraphRunner {
|
|
|
1014
1098
|
}
|
|
1015
1099
|
}
|
|
1016
1100
|
finally {
|
|
1017
|
-
|
|
1018
|
-
this.runtime.resume();
|
|
1101
|
+
releasePause?.();
|
|
1019
1102
|
}
|
|
1020
1103
|
};
|
|
1021
1104
|
this.getEnvironment = () => {
|
|
@@ -1036,24 +1119,21 @@ class LocalGraphRunner extends AbstractGraphRunner {
|
|
|
1036
1119
|
update(def, options) {
|
|
1037
1120
|
if (!this.runtime)
|
|
1038
1121
|
return;
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
if (options?.dry && !wasPaused) {
|
|
1042
|
-
this.runtime.pause();
|
|
1043
|
-
}
|
|
1122
|
+
// Use requestPause for dry mode to temporarily pause without affecting base run mode
|
|
1123
|
+
const releasePause = options?.dry ? this.runtime.requestPause() : null;
|
|
1044
1124
|
try {
|
|
1045
1125
|
this.runtime.update(def, this.registry);
|
|
1046
1126
|
this.emit("invalidate", { reason: "graph-updated" });
|
|
1047
1127
|
}
|
|
1048
1128
|
finally {
|
|
1049
|
-
|
|
1050
|
-
if (options?.dry && !wasPaused) {
|
|
1051
|
-
this.runtime.resume();
|
|
1052
|
-
}
|
|
1129
|
+
releasePause?.();
|
|
1053
1130
|
}
|
|
1054
1131
|
}
|
|
1055
1132
|
launch(def, opts) {
|
|
1056
|
-
|
|
1133
|
+
if (this.engine) {
|
|
1134
|
+
this.engine.dispose();
|
|
1135
|
+
this.engine = undefined;
|
|
1136
|
+
}
|
|
1057
1137
|
this.build(def);
|
|
1058
1138
|
if (!this.runtime)
|
|
1059
1139
|
throw new Error("Runtime not built");
|
|
@@ -1072,7 +1152,7 @@ class LocalGraphRunner extends AbstractGraphRunner {
|
|
|
1072
1152
|
if (!this.runtime)
|
|
1073
1153
|
throw new Error("Runtime not built");
|
|
1074
1154
|
// Use shared engine factory
|
|
1075
|
-
this.engine = new
|
|
1155
|
+
this.engine = new LocalEngine(this.runtime, opts?.runMode);
|
|
1076
1156
|
if (!this.engine)
|
|
1077
1157
|
throw new Error("Failed to create engine");
|
|
1078
1158
|
this.engine.on("value", (e) => this.emit("value", e));
|
|
@@ -1080,8 +1160,8 @@ class LocalGraphRunner extends AbstractGraphRunner {
|
|
|
1080
1160
|
this.engine.on("invalidate", (e) => this.emit("invalidate", e));
|
|
1081
1161
|
this.engine.on("stats", (e) => this.emit("stats", e));
|
|
1082
1162
|
this.engine.launch(opts?.invalidate);
|
|
1083
|
-
|
|
1084
|
-
this.emit("status", { running: true, runMode
|
|
1163
|
+
const runMode = opts?.runMode ?? "manual";
|
|
1164
|
+
this.emit("status", { running: true, runMode });
|
|
1085
1165
|
for (const [nodeId, map] of Object.entries(this.stagedInputs)) {
|
|
1086
1166
|
this.engine.setInputs(nodeId, map);
|
|
1087
1167
|
}
|
|
@@ -1175,6 +1255,24 @@ class LocalGraphRunner extends AbstractGraphRunner {
|
|
|
1175
1255
|
this.engine.copyOutputs(fromNodeId, toNodeId, options);
|
|
1176
1256
|
}
|
|
1177
1257
|
}
|
|
1258
|
+
async setExtData(data) {
|
|
1259
|
+
if (!data || typeof data !== "object") {
|
|
1260
|
+
this.extData = {};
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1263
|
+
this.extData = { ...this.extData, ...data };
|
|
1264
|
+
}
|
|
1265
|
+
async updateExtData(updates) {
|
|
1266
|
+
if (!this.extData ||
|
|
1267
|
+
typeof this.extData !== "object" ||
|
|
1268
|
+
Array.isArray(this.extData)) {
|
|
1269
|
+
this.extData = {};
|
|
1270
|
+
}
|
|
1271
|
+
for (const { path, value } of updates) {
|
|
1272
|
+
const pathSegments = parseJsonPath(path);
|
|
1273
|
+
setValueAtPathWithCreation(this.extData, pathSegments, value);
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1178
1276
|
async snapshotFull() {
|
|
1179
1277
|
const def = undefined; // UI will supply def/positions on download for local
|
|
1180
1278
|
const inputs = this.getInputs(this.runtime
|
|
@@ -1196,7 +1294,8 @@ class LocalGraphRunner extends AbstractGraphRunner {
|
|
|
1196
1294
|
}
|
|
1197
1295
|
: { nodes: [], edges: [] });
|
|
1198
1296
|
const environment = this.getEnvironment() || {};
|
|
1199
|
-
|
|
1297
|
+
const extData = this.extData;
|
|
1298
|
+
return { def, environment, inputs, outputs, extData };
|
|
1200
1299
|
}
|
|
1201
1300
|
async applySnapshotFull(payload, options) {
|
|
1202
1301
|
if (payload.def && !options?.skipBuild) {
|
|
@@ -1204,6 +1303,9 @@ class LocalGraphRunner extends AbstractGraphRunner {
|
|
|
1204
1303
|
}
|
|
1205
1304
|
this.setEnvironment?.(payload.environment || {}, { merge: false });
|
|
1206
1305
|
this.hydrate(payload, { dry: options?.dry });
|
|
1306
|
+
if (payload.extData) {
|
|
1307
|
+
await this.setExtData(payload.extData);
|
|
1308
|
+
}
|
|
1207
1309
|
}
|
|
1208
1310
|
hydrate(snapshot, opts) {
|
|
1209
1311
|
// Hydrate via runtime for exact restore (this emits events on runtime emitter)
|
|
@@ -1573,7 +1675,10 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
1573
1675
|
}
|
|
1574
1676
|
}
|
|
1575
1677
|
launch(def, opts) {
|
|
1576
|
-
|
|
1678
|
+
if (this.engine) {
|
|
1679
|
+
this.engine.dispose();
|
|
1680
|
+
this.engine = undefined;
|
|
1681
|
+
}
|
|
1577
1682
|
// Remote: build remotely then launch
|
|
1578
1683
|
this.ensureClient().then(async (client) => {
|
|
1579
1684
|
await client.api.build(def);
|
|
@@ -1629,8 +1734,8 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
1629
1734
|
this.listenersBound = true;
|
|
1630
1735
|
}
|
|
1631
1736
|
this.engine = eng;
|
|
1632
|
-
|
|
1633
|
-
this.emit("status", { running: true, runMode
|
|
1737
|
+
const runMode = opts?.runMode ?? "manual";
|
|
1738
|
+
this.emit("status", { running: true, runMode });
|
|
1634
1739
|
// Re-apply staged inputs using client.setInputs for consistency
|
|
1635
1740
|
for (const [nodeId, map] of Object.entries(this.stagedInputs)) {
|
|
1636
1741
|
await eng.setInputs(nodeId, map, undefined).catch(() => {
|
|
@@ -1646,7 +1751,10 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
1646
1751
|
* the runtime state that was just restored.
|
|
1647
1752
|
*/
|
|
1648
1753
|
launchExisting(def, opts) {
|
|
1649
|
-
|
|
1754
|
+
if (this.engine) {
|
|
1755
|
+
this.engine.dispose();
|
|
1756
|
+
this.engine = undefined;
|
|
1757
|
+
}
|
|
1650
1758
|
// Remote: attach to existing runtime and launch (do NOT rebuild)
|
|
1651
1759
|
this.ensureClient().then(async (client) => {
|
|
1652
1760
|
// NOTE: We do NOT call client.build(def) here because the backend runtime
|
|
@@ -1657,14 +1765,10 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
1657
1765
|
});
|
|
1658
1766
|
}
|
|
1659
1767
|
setRunMode(runMode) {
|
|
1660
|
-
if (
|
|
1661
|
-
|
|
1768
|
+
if (this.engine) {
|
|
1769
|
+
this.engine.setRunMode(runMode);
|
|
1770
|
+
this.emit("status", { running: true, runMode });
|
|
1662
1771
|
}
|
|
1663
|
-
// Update engine run mode (sends SetRunMode command to backend)
|
|
1664
|
-
this.engine.setRunMode(runMode);
|
|
1665
|
-
// Update local state and emit status event
|
|
1666
|
-
this.runMode = runMode;
|
|
1667
|
-
this.emit("status", { running: true, runMode: this.runMode });
|
|
1668
1772
|
}
|
|
1669
1773
|
async computeNode(nodeId, options) {
|
|
1670
1774
|
const client = await this.ensureClient();
|
|
@@ -1727,6 +1831,10 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
1727
1831
|
const client = await this.ensureClient();
|
|
1728
1832
|
await client.api.setExtData(data);
|
|
1729
1833
|
}
|
|
1834
|
+
async updateExtData(updates) {
|
|
1835
|
+
const client = await this.ensureClient();
|
|
1836
|
+
await client.api.updateExtData(updates);
|
|
1837
|
+
}
|
|
1730
1838
|
async commit(reason) {
|
|
1731
1839
|
const client = await this.ensureClient();
|
|
1732
1840
|
try {
|
|
@@ -2598,35 +2706,40 @@ function toReactFlow(def, positions, sizes, registry, opts) {
|
|
|
2598
2706
|
}));
|
|
2599
2707
|
const handleLayout = geom.handleLayout;
|
|
2600
2708
|
const handles = geom.handles;
|
|
2709
|
+
const baseData = {
|
|
2710
|
+
typeId: n.typeId,
|
|
2711
|
+
params: n.params,
|
|
2712
|
+
inputHandles,
|
|
2713
|
+
outputHandles,
|
|
2714
|
+
inputConnected: Object.fromEntries(inputHandles.map((h) => [
|
|
2715
|
+
h.id,
|
|
2716
|
+
!!connectedInputs[n.nodeId]?.has(h.id),
|
|
2717
|
+
])),
|
|
2718
|
+
handleLayout,
|
|
2719
|
+
showValues: opts.showValues,
|
|
2720
|
+
renderWidth,
|
|
2721
|
+
renderHeight,
|
|
2722
|
+
initialWidth: initialGeom.width,
|
|
2723
|
+
initialHeight: initialGeom.height,
|
|
2724
|
+
inputValues: opts.inputs?.[n.nodeId],
|
|
2725
|
+
inputDefaults: opts.inputDefaults?.[n.nodeId],
|
|
2726
|
+
outputValues: opts.outputs?.[n.nodeId],
|
|
2727
|
+
status: opts.nodeStatus?.[n.nodeId],
|
|
2728
|
+
validation: {
|
|
2729
|
+
inputs: opts.nodeValidation?.inputs?.[n.nodeId] ?? [],
|
|
2730
|
+
outputs: opts.nodeValidation?.outputs?.[n.nodeId] ?? [],
|
|
2731
|
+
issues: opts.nodeValidation?.issues?.[n.nodeId] ?? [],
|
|
2732
|
+
},
|
|
2733
|
+
toString: opts.toString,
|
|
2734
|
+
toElement: opts.toElement,
|
|
2735
|
+
};
|
|
2736
|
+
const customNodeData = opts.customData?.nodes;
|
|
2737
|
+
const mergedData = customNodeData?.[n.nodeId]
|
|
2738
|
+
? { ...baseData, custom: customNodeData[n.nodeId] }
|
|
2739
|
+
: baseData;
|
|
2601
2740
|
return {
|
|
2602
2741
|
id: n.nodeId,
|
|
2603
|
-
data:
|
|
2604
|
-
typeId: n.typeId,
|
|
2605
|
-
params: n.params,
|
|
2606
|
-
inputHandles,
|
|
2607
|
-
outputHandles,
|
|
2608
|
-
inputConnected: Object.fromEntries(inputHandles.map((h) => [
|
|
2609
|
-
h.id,
|
|
2610
|
-
!!connectedInputs[n.nodeId]?.has(h.id),
|
|
2611
|
-
])),
|
|
2612
|
-
handleLayout,
|
|
2613
|
-
showValues: opts.showValues,
|
|
2614
|
-
renderWidth,
|
|
2615
|
-
renderHeight,
|
|
2616
|
-
initialWidth: initialGeom.width,
|
|
2617
|
-
initialHeight: initialGeom.height,
|
|
2618
|
-
inputValues: opts.inputs?.[n.nodeId],
|
|
2619
|
-
inputDefaults: opts.inputDefaults?.[n.nodeId],
|
|
2620
|
-
outputValues: opts.outputs?.[n.nodeId],
|
|
2621
|
-
status: opts.nodeStatus?.[n.nodeId],
|
|
2622
|
-
validation: {
|
|
2623
|
-
inputs: opts.nodeValidation?.inputs?.[n.nodeId] ?? [],
|
|
2624
|
-
outputs: opts.nodeValidation?.outputs?.[n.nodeId] ?? [],
|
|
2625
|
-
issues: opts.nodeValidation?.issues?.[n.nodeId] ?? [],
|
|
2626
|
-
},
|
|
2627
|
-
toString: opts.toString,
|
|
2628
|
-
toElement: opts.toElement,
|
|
2629
|
-
},
|
|
2742
|
+
data: mergedData,
|
|
2630
2743
|
position: positions[n.nodeId] ?? { x: 0, y: 0 },
|
|
2631
2744
|
type: opts.resolveNodeType?.(n.typeId) ?? "spark-default",
|
|
2632
2745
|
selected: opts.selectedNodeIds
|
|
@@ -2669,6 +2782,25 @@ function toReactFlow(def, positions, sizes, registry, opts) {
|
|
|
2669
2782
|
const targetHandleTypeId = targetHandles?.inputs[e.target.handle]
|
|
2670
2783
|
? getInputTypeId(targetHandles.inputs, e.target.handle) ?? "unknown"
|
|
2671
2784
|
: "unknown";
|
|
2785
|
+
const baseEdgeData = {
|
|
2786
|
+
sourceNodeId: e.source.nodeId,
|
|
2787
|
+
sourceNodeTypeId: sourceNode?.typeId || "unknown",
|
|
2788
|
+
sourceHandle: e.source.handle,
|
|
2789
|
+
sourceHandleTypeId,
|
|
2790
|
+
targetNodeId: e.target.nodeId,
|
|
2791
|
+
targetNodeTypeId: targetNode?.typeId || "unknown",
|
|
2792
|
+
targetHandle: e.target.handle,
|
|
2793
|
+
targetHandleTypeId,
|
|
2794
|
+
edgeTypeId,
|
|
2795
|
+
isRunning,
|
|
2796
|
+
hasError,
|
|
2797
|
+
isInvalid: isInvalidEdge,
|
|
2798
|
+
isMissing,
|
|
2799
|
+
};
|
|
2800
|
+
const customEdgeData = opts.customData?.edges;
|
|
2801
|
+
const mergedEdgeData = customEdgeData?.[e.id]
|
|
2802
|
+
? { ...baseEdgeData, custom: customEdgeData[e.id] }
|
|
2803
|
+
: baseEdgeData;
|
|
2672
2804
|
return {
|
|
2673
2805
|
id: e.id,
|
|
2674
2806
|
source: e.source.nodeId,
|
|
@@ -2682,21 +2814,7 @@ function toReactFlow(def, positions, sizes, registry, opts) {
|
|
|
2682
2814
|
style,
|
|
2683
2815
|
label: edgeTypeId,
|
|
2684
2816
|
type: "default",
|
|
2685
|
-
data:
|
|
2686
|
-
sourceNodeId: e.source.nodeId,
|
|
2687
|
-
sourceNodeTypeId: sourceNode?.typeId || "unknown",
|
|
2688
|
-
sourceHandle: e.source.handle,
|
|
2689
|
-
sourceHandleTypeId,
|
|
2690
|
-
targetNodeId: e.target.nodeId,
|
|
2691
|
-
targetNodeTypeId: targetNode?.typeId || "unknown",
|
|
2692
|
-
targetHandle: e.target.handle,
|
|
2693
|
-
targetHandleTypeId,
|
|
2694
|
-
edgeTypeId,
|
|
2695
|
-
isRunning,
|
|
2696
|
-
hasError,
|
|
2697
|
-
isInvalid: isInvalidEdge,
|
|
2698
|
-
isMissing,
|
|
2699
|
-
},
|
|
2817
|
+
data: mergedEdgeData,
|
|
2700
2818
|
};
|
|
2701
2819
|
});
|
|
2702
2820
|
return { nodes, edges };
|
|
@@ -2852,6 +2970,9 @@ async function upload(parsed, wb, runner) {
|
|
|
2852
2970
|
if (extData.runtime && typeof extData.runtime === "object") {
|
|
2853
2971
|
wb.setRuntimeState(extData.runtime);
|
|
2854
2972
|
}
|
|
2973
|
+
if (extData.custom && typeof extData.custom === "object") {
|
|
2974
|
+
wb.setCustomData(extData.custom);
|
|
2975
|
+
}
|
|
2855
2976
|
if (runner.isRunning()) {
|
|
2856
2977
|
await runner.applySnapshotFull({
|
|
2857
2978
|
def: wb.def,
|
|
@@ -2868,6 +2989,9 @@ async function upload(parsed, wb, runner) {
|
|
|
2868
2989
|
runner.setInputs(nodeId, map, { dry: true });
|
|
2869
2990
|
}
|
|
2870
2991
|
}
|
|
2992
|
+
if (extData) {
|
|
2993
|
+
await runner.setExtData(extData);
|
|
2994
|
+
}
|
|
2871
2995
|
}
|
|
2872
2996
|
}
|
|
2873
2997
|
|
|
@@ -3664,15 +3788,7 @@ function WorkbenchProvider({ wb, runner, overrides, uiVersion, children, }) {
|
|
|
3664
3788
|
const graphUiTick = useWorkbenchGraphUiTick(wb);
|
|
3665
3789
|
const versionTick = useWorkbenchVersionTick(runner);
|
|
3666
3790
|
const valuesTick = versionTick + graphTick + graphUiTick;
|
|
3667
|
-
// Keep local runMode state loosely in sync with runner status.
|
|
3668
|
-
// - Seed from runner.getRunMode() on mount if available.
|
|
3669
|
-
// - On status events, update only when a non-undefined runMode is reported,
|
|
3670
|
-
// so the UI preserves the last selected mode after stop().
|
|
3671
3791
|
useEffect(() => {
|
|
3672
|
-
const initialMode = runner.getRunMode();
|
|
3673
|
-
if (initialMode) {
|
|
3674
|
-
setRunModeState(initialMode);
|
|
3675
|
-
}
|
|
3676
3792
|
const offRunnerStatus = runner.on("status", (status) => {
|
|
3677
3793
|
if (status.runMode) {
|
|
3678
3794
|
setRunModeState(status.runMode);
|
|
@@ -3832,12 +3948,13 @@ function WorkbenchProvider({ wb, runner, overrides, uiVersion, children, }) {
|
|
|
3832
3948
|
workbench.setRuntimeState(metadata);
|
|
3833
3949
|
const fullUiState = workbench.getUIState();
|
|
3834
3950
|
const uiWithoutViewport = excludeViewportFromUIState(fullUiState);
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
});
|
|
3951
|
+
// Use updateExtData for efficient batched partial updates
|
|
3952
|
+
const updates = [];
|
|
3953
|
+
if (Object.keys(uiWithoutViewport || {}).length > 0) {
|
|
3954
|
+
updates.push({ path: "ui", value: uiWithoutViewport });
|
|
3955
|
+
}
|
|
3956
|
+
updates.push({ path: "runtime", value: metadata });
|
|
3957
|
+
await graphRunner.updateExtData(updates);
|
|
3841
3958
|
}
|
|
3842
3959
|
catch (err) {
|
|
3843
3960
|
console.warn("[WorkbenchContext] Failed to save runtime metadata:", err);
|
|
@@ -4270,6 +4387,18 @@ function WorkbenchProvider({ wb, runner, overrides, uiVersion, children, }) {
|
|
|
4270
4387
|
else if (changeType === "selection") {
|
|
4271
4388
|
reason = "selection";
|
|
4272
4389
|
}
|
|
4390
|
+
else if (changeType === "customNodeData") {
|
|
4391
|
+
reason = "custom-node-data";
|
|
4392
|
+
}
|
|
4393
|
+
else if (changeType === "customEdgeData") {
|
|
4394
|
+
reason = "custom-edge-data";
|
|
4395
|
+
}
|
|
4396
|
+
else if (changeType === "customMetaData") {
|
|
4397
|
+
reason = "custom-meta";
|
|
4398
|
+
}
|
|
4399
|
+
else if (changeType === "customData") {
|
|
4400
|
+
reason = "custom";
|
|
4401
|
+
}
|
|
4273
4402
|
}
|
|
4274
4403
|
await saveUiRuntimeMetadata(wb, runner);
|
|
4275
4404
|
const history = await runner
|
|
@@ -4375,27 +4504,11 @@ function WorkbenchProvider({ wb, runner, overrides, uiVersion, children, }) {
|
|
|
4375
4504
|
};
|
|
4376
4505
|
}, [runner, wb]);
|
|
4377
4506
|
const isRunning = useCallback(() => runner.isRunning(), [runner]);
|
|
4378
|
-
const getRunMode = useCallback(() => runner.getRunMode(), [runner]);
|
|
4379
|
-
const stop = useCallback(() => runner.stop(), [runner]);
|
|
4380
|
-
// Run mode actions
|
|
4381
4507
|
const setRunMode = useCallback((mode) => {
|
|
4382
4508
|
if (mode === runMode)
|
|
4383
4509
|
return;
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
// Use setRunMode to change run mode without rebuilding
|
|
4387
|
-
try {
|
|
4388
|
-
runner.setRunMode(mode);
|
|
4389
|
-
setRunModeState(mode);
|
|
4390
|
-
}
|
|
4391
|
-
catch (err) {
|
|
4392
|
-
console.error("Failed to set run mode:", err);
|
|
4393
|
-
}
|
|
4394
|
-
}
|
|
4395
|
-
else {
|
|
4396
|
-
// Just update state if not running (will be applied on next launch)
|
|
4397
|
-
setRunModeState(mode);
|
|
4398
|
-
}
|
|
4510
|
+
runner.setRunMode(mode);
|
|
4511
|
+
setRunModeState(mode);
|
|
4399
4512
|
}, [runMode, runner]);
|
|
4400
4513
|
const runNodeAction = useCallback(async (nodeId) => {
|
|
4401
4514
|
await runner.computeNode(nodeId);
|
|
@@ -4497,8 +4610,6 @@ function WorkbenchProvider({ wb, runner, overrides, uiVersion, children, }) {
|
|
|
4497
4610
|
removeRegistryError,
|
|
4498
4611
|
removeInputValidationError,
|
|
4499
4612
|
isRunning,
|
|
4500
|
-
getRunMode,
|
|
4501
|
-
stop,
|
|
4502
4613
|
runMode,
|
|
4503
4614
|
setRunMode,
|
|
4504
4615
|
runNode: runNodeAction,
|
|
@@ -4539,8 +4650,6 @@ function WorkbenchProvider({ wb, runner, overrides, uiVersion, children, }) {
|
|
|
4539
4650
|
events,
|
|
4540
4651
|
clearEvents,
|
|
4541
4652
|
isRunning,
|
|
4542
|
-
getRunMode,
|
|
4543
|
-
stop,
|
|
4544
4653
|
runMode,
|
|
4545
4654
|
setRunMode,
|
|
4546
4655
|
runNodeAction,
|
|
@@ -5574,6 +5683,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
5574
5683
|
status: n.data.status,
|
|
5575
5684
|
validation: n.data.validation,
|
|
5576
5685
|
inputConnected: n.data.inputConnected,
|
|
5686
|
+
custom: n.data.custom,
|
|
5577
5687
|
},
|
|
5578
5688
|
});
|
|
5579
5689
|
return lod.isEqual(pick(a), pick(b));
|
|
@@ -5589,6 +5699,14 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
5589
5699
|
style: e.style,
|
|
5590
5700
|
label: e.label,
|
|
5591
5701
|
type: e.type,
|
|
5702
|
+
data: e.data && {
|
|
5703
|
+
edgeTypeId: e.data.edgeTypeId,
|
|
5704
|
+
isRunning: e.data.isRunning,
|
|
5705
|
+
hasError: e.data.hasError,
|
|
5706
|
+
isInvalid: e.data.isInvalid,
|
|
5707
|
+
isMissing: e.data.isMissing,
|
|
5708
|
+
custom: e.data.custom,
|
|
5709
|
+
},
|
|
5592
5710
|
});
|
|
5593
5711
|
return lod.isEqual(pick(a), pick(b));
|
|
5594
5712
|
};
|
|
@@ -5676,6 +5794,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
5676
5794
|
selectedEdgeIds: new Set(sel.edges),
|
|
5677
5795
|
getDefaultNodeSize,
|
|
5678
5796
|
ui,
|
|
5797
|
+
customData: wb.getCustomData(),
|
|
5679
5798
|
});
|
|
5680
5799
|
// Retain references for unchanged items
|
|
5681
5800
|
const stableNodes = retainStabilityById(prevNodesRef.current, out.nodes, isSameNode);
|
|
@@ -6223,14 +6342,14 @@ function WorkbenchStudioCanvas({ autoScroll, onAutoScrollChange, example, onExam
|
|
|
6223
6342
|
const isConnecting = transportStatus.state === "connecting" ||
|
|
6224
6343
|
transportStatus.state === "retrying";
|
|
6225
6344
|
// Only allow Start/Stop when transport is connected or local
|
|
6345
|
+
// For local backend, always allow control (transport state is "local")
|
|
6346
|
+
// For remote backends, require connection
|
|
6226
6347
|
const canControl = transportStatus.state === "connected" ||
|
|
6227
|
-
transportStatus.state === "local"
|
|
6348
|
+
transportStatus.state === "local" ||
|
|
6349
|
+
backendKind === "local"; // Always allow control for local backend
|
|
6228
6350
|
if (isConnecting) {
|
|
6229
6351
|
return (jsxs("button", { className: "border rounded px-2 py-1.5 text-gray-500 border-gray-400 flex items-center gap-1 disabled:opacity-50", disabled: true, title: "Connecting to backend...", children: [jsx(ClockClockwiseIcon, { size: 16, className: "animate-spin" }), jsx("span", { className: "font-medium ml-1", children: "Connecting..." })] }));
|
|
6230
6352
|
}
|
|
6231
|
-
if (isGraphRunning) {
|
|
6232
|
-
return (jsxs("button", { className: "border rounded px-2 py-1.5 text-red-700 border-red-600 flex items-center gap-1 disabled:opacity-50 disabled:text-gray-400 disabled:border-gray-300", onClick: () => runner.stop(), disabled: !canControl, title: canControl ? "Stop engine" : "Waiting for connection", children: [jsx(StopIcon, { size: 16, weight: "fill" }), jsx("span", { className: "font-medium ml-1", children: "Stop" })] }));
|
|
6233
|
-
}
|
|
6234
6353
|
return (jsxs("button", { className: "border rounded px-2 py-1.5 text-green-700 border-green-600 flex items-center gap-1 disabled:text-gray-400 disabled:border-gray-300 disabled:opacity-50", onClick: (evt) => {
|
|
6235
6354
|
if (evt.shiftKey && !confirm("Invalidate and re-run graph?"))
|
|
6236
6355
|
return;
|
|
@@ -6247,7 +6366,7 @@ function WorkbenchStudioCanvas({ autoScroll, onAutoScrollChange, example, onExam
|
|
|
6247
6366
|
}, disabled: !canControl, title: !canControl
|
|
6248
6367
|
? "Waiting for connection"
|
|
6249
6368
|
: `Start ${runMode === "manual" ? "manual" : "auto"} mode`, children: [jsx(PlayIcon, { size: 16, weight: "fill" }), jsx("span", { className: "font-medium ml-1", children: "Start" })] }));
|
|
6250
|
-
}, [transportStatus, isGraphRunning, runner, runMode, wb]);
|
|
6369
|
+
}, [transportStatus, isGraphRunning, runner, runMode, wb, backendKind]);
|
|
6251
6370
|
const defaultExamples = useMemo(() => [
|
|
6252
6371
|
{
|
|
6253
6372
|
id: "simple",
|
|
@@ -6617,9 +6736,7 @@ function WorkbenchStudioCanvas({ autoScroll, onAutoScrollChange, example, onExam
|
|
|
6617
6736
|
if (mode !== runMode) {
|
|
6618
6737
|
await setRunMode(mode);
|
|
6619
6738
|
}
|
|
6620
|
-
},
|
|
6621
|
-
? "Stop before switching run mode"
|
|
6622
|
-
: "Select run mode", children: [jsx("option", { value: "manual", children: "Manual" }), jsx("option", { value: "auto", children: "Auto" })] }), renderStartStopButton(), jsx("button", { className: "border border-gray-300 rounded p-1", onClick: runAutoLayout, children: jsx(TreeStructureIcon, { size: 24 }) }), jsx("button", { className: "border border-gray-300 rounded p-1", onClick: () => canvasRef.current?.fitView?.(), title: "Fit View", children: jsx(CornersOutIcon, { size: 24 }) }), jsx("button", { className: "border border-gray-300 rounded p-1", onClick: download$1, children: jsx(DownloadIcon, { size: 24 }) }), jsx("input", { ref: uploadInputRef, type: "file", accept: "application/json,.json", className: "hidden", onChange: onUploadPicked }), jsx("button", { className: "border border-gray-300 rounded p-1", onClick: triggerUpload, children: jsx(UploadIcon, { size: 24 }) }), jsx("button", { className: "border border-gray-300 rounded p-1", onClick: async () => {
|
|
6739
|
+
}, title: "Select run mode", children: [jsx("option", { value: "manual", children: "Manual" }), jsx("option", { value: "auto", children: "Auto" })] }), renderStartStopButton(), jsx("button", { className: "border border-gray-300 rounded p-1", onClick: runAutoLayout, children: jsx(TreeStructureIcon, { size: 24 }) }), jsx("button", { className: "border border-gray-300 rounded p-1", onClick: () => canvasRef.current?.fitView?.(), title: "Fit View", children: jsx(CornersOutIcon, { size: 24 }) }), jsx("button", { className: "border border-gray-300 rounded p-1", onClick: download$1, children: jsx(DownloadIcon, { size: 24 }) }), jsx("input", { ref: uploadInputRef, type: "file", accept: "application/json,.json", className: "hidden", onChange: onUploadPicked }), jsx("button", { className: "border border-gray-300 rounded p-1", onClick: triggerUpload, children: jsx(UploadIcon, { size: 24 }) }), jsx("button", { className: "border border-gray-300 rounded p-1", onClick: async () => {
|
|
6623
6740
|
await downloadCanvasThumbnail(canvasContainerRef.current);
|
|
6624
6741
|
}, title: "Download Flow Thumbnail (SVG)", children: jsx(ImageIcon, { size: 24 }) }), jsxs("label", { className: "flex items-center gap-1", children: [jsx("input", { type: "checkbox", checked: debug, onChange: (e) => onDebugChange(e.target.checked) }), jsx(BugBeetleIcon, { size: 24, weight: debug ? "fill" : undefined })] }), jsxs("label", { className: "flex items-center gap-1", children: [jsx("input", { type: "checkbox", checked: showValues, onChange: (e) => onShowValuesChange(e.target.checked) }), jsx(ListBulletsIcon, { size: 24, weight: showValues ? "fill" : undefined })] })] }), jsxs("div", { className: "flex flex-1 min-h-0", children: [jsx("div", { className: "flex-1 min-w-0", ref: canvasContainerRef, children: jsx(WorkbenchCanvas, { ref: canvasRef, showValues: showValues, toString: toString, toElement: toElement, getDefaultNodeSize: overrides?.getDefaultNodeSize }) }), jsx(Inspector, { setInput: setInput, debug: debug, autoScroll: autoScroll, hideWorkbench: hideWorkbench, onAutoScrollChange: onAutoScrollChange, onHideWorkbenchChange: onHideWorkbenchChange, toString: toString, contextPanel: overrides?.contextPanel })] })] }));
|
|
6625
6742
|
}
|