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