@bian-womp/spark-workbench 0.2.34 → 0.2.35
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 +214 -98
- package/lib/cjs/index.cjs.map +1 -1
- package/lib/cjs/src/misc/DefaultNode.d.ts.map +1 -1
- package/lib/cjs/src/misc/Inspector.d.ts.map +1 -1
- package/lib/cjs/src/misc/WorkbenchCanvas.d.ts.map +1 -1
- package/lib/cjs/src/misc/context/WorkbenchContext.d.ts +1 -0
- package/lib/cjs/src/misc/context/WorkbenchContext.d.ts.map +1 -1
- package/lib/cjs/src/misc/context/WorkbenchContext.provider.d.ts.map +1 -1
- package/lib/cjs/src/misc/mapping.d.ts +2 -0
- package/lib/cjs/src/misc/mapping.d.ts.map +1 -1
- package/lib/cjs/src/runtime/AbstractGraphRunner.d.ts +4 -2
- package/lib/cjs/src/runtime/AbstractGraphRunner.d.ts.map +1 -1
- package/lib/cjs/src/runtime/IGraphRunner.d.ts +3 -2
- package/lib/cjs/src/runtime/IGraphRunner.d.ts.map +1 -1
- package/lib/cjs/src/runtime/LocalGraphRunner.d.ts +3 -2
- package/lib/cjs/src/runtime/LocalGraphRunner.d.ts.map +1 -1
- package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts +3 -3
- package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
- package/lib/esm/index.js +214 -98
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/src/misc/DefaultNode.d.ts.map +1 -1
- package/lib/esm/src/misc/Inspector.d.ts.map +1 -1
- package/lib/esm/src/misc/WorkbenchCanvas.d.ts.map +1 -1
- package/lib/esm/src/misc/context/WorkbenchContext.d.ts +1 -0
- package/lib/esm/src/misc/context/WorkbenchContext.d.ts.map +1 -1
- package/lib/esm/src/misc/context/WorkbenchContext.provider.d.ts.map +1 -1
- package/lib/esm/src/misc/mapping.d.ts +2 -0
- package/lib/esm/src/misc/mapping.d.ts.map +1 -1
- package/lib/esm/src/runtime/AbstractGraphRunner.d.ts +4 -2
- package/lib/esm/src/runtime/AbstractGraphRunner.d.ts.map +1 -1
- package/lib/esm/src/runtime/IGraphRunner.d.ts +3 -2
- package/lib/esm/src/runtime/IGraphRunner.d.ts.map +1 -1
- package/lib/esm/src/runtime/LocalGraphRunner.d.ts +3 -2
- package/lib/esm/src/runtime/LocalGraphRunner.d.ts.map +1 -1
- package/lib/esm/src/runtime/RemoteGraphRunner.d.ts +3 -3
- package/lib/esm/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
- package/package.json +4 -4
package/lib/esm/index.js
CHANGED
|
@@ -337,6 +337,7 @@ class AbstractGraphRunner {
|
|
|
337
337
|
if (this.engine) {
|
|
338
338
|
throw new Error("Engine already running. Stop the current engine first.");
|
|
339
339
|
}
|
|
340
|
+
this.currentDef = def;
|
|
340
341
|
}
|
|
341
342
|
setInput(nodeId, handle, value) {
|
|
342
343
|
if (!this.stagedInputs[nodeId])
|
|
@@ -393,6 +394,7 @@ class AbstractGraphRunner {
|
|
|
393
394
|
this.engine = undefined;
|
|
394
395
|
this.runtime?.dispose();
|
|
395
396
|
this.runtime = undefined;
|
|
397
|
+
this.currentDef = undefined;
|
|
396
398
|
if (this.runningKind) {
|
|
397
399
|
this.runningKind = undefined;
|
|
398
400
|
this.emit("status", { running: false, engine: undefined });
|
|
@@ -427,12 +429,14 @@ class LocalGraphRunner extends AbstractGraphRunner {
|
|
|
427
429
|
this.emit("transport", { state: "local" });
|
|
428
430
|
}
|
|
429
431
|
build(def) {
|
|
432
|
+
this.currentDef = def;
|
|
430
433
|
const builder = new GraphBuilder(this.registry);
|
|
431
434
|
this.runtime = builder.build(def);
|
|
432
435
|
// Signal UI that freshly built graph should be considered invalidated
|
|
433
436
|
this.emit("invalidate", { reason: "graph-built" });
|
|
434
437
|
}
|
|
435
438
|
update(def) {
|
|
439
|
+
this.currentDef = def;
|
|
436
440
|
if (!this.runtime)
|
|
437
441
|
return;
|
|
438
442
|
// Prevent mid-run churn while wiring changes are applied
|
|
@@ -497,11 +501,11 @@ class LocalGraphRunner extends AbstractGraphRunner {
|
|
|
497
501
|
if (eng instanceof BatchedEngine)
|
|
498
502
|
eng.flush();
|
|
499
503
|
}
|
|
500
|
-
getOutputs(
|
|
504
|
+
getOutputs() {
|
|
501
505
|
const out = {};
|
|
502
|
-
if (!this.runtime)
|
|
506
|
+
if (!this.runtime || !this.currentDef)
|
|
503
507
|
return out;
|
|
504
|
-
for (const n of
|
|
508
|
+
for (const n of this.currentDef.nodes) {
|
|
505
509
|
const desc = this.registry.nodes.get(n.typeId);
|
|
506
510
|
const handles = Object.keys(desc?.outputs ?? {});
|
|
507
511
|
for (const h of handles) {
|
|
@@ -515,15 +519,17 @@ class LocalGraphRunner extends AbstractGraphRunner {
|
|
|
515
519
|
}
|
|
516
520
|
return out;
|
|
517
521
|
}
|
|
518
|
-
getInputs(
|
|
522
|
+
getInputs() {
|
|
519
523
|
const out = {};
|
|
520
|
-
|
|
524
|
+
if (!this.currentDef)
|
|
525
|
+
return out;
|
|
526
|
+
for (const n of this.currentDef.nodes) {
|
|
521
527
|
const staged = this.stagedInputs[n.nodeId] ?? {};
|
|
522
528
|
const runtimeInputs = this.runtime
|
|
523
529
|
? this.runtime.getNodeData?.(n.nodeId)?.inputs ?? {}
|
|
524
530
|
: {};
|
|
525
531
|
// Build inbound handle set for this node from current def
|
|
526
|
-
const inbound = new Set(
|
|
532
|
+
const inbound = new Set(this.currentDef.edges
|
|
527
533
|
.filter((e) => e.target.nodeId === n.nodeId)
|
|
528
534
|
.map((e) => e.target.handle));
|
|
529
535
|
// Merge staged only for non-inbound handles so UI reflects runtime values for wired inputs
|
|
@@ -537,26 +543,22 @@ class LocalGraphRunner extends AbstractGraphRunner {
|
|
|
537
543
|
}
|
|
538
544
|
return out;
|
|
539
545
|
}
|
|
546
|
+
getInputDefaults() {
|
|
547
|
+
const out = {};
|
|
548
|
+
if (!this.currentDef)
|
|
549
|
+
return out;
|
|
550
|
+
for (const n of this.currentDef.nodes) {
|
|
551
|
+
const dynDefaults = n.resolvedHandles?.inputDefaults ?? {};
|
|
552
|
+
if (Object.keys(dynDefaults).length > 0) {
|
|
553
|
+
out[n.nodeId] = dynDefaults;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
return out;
|
|
557
|
+
}
|
|
540
558
|
async snapshotFull() {
|
|
541
559
|
const def = undefined; // UI will supply def/positions on download for local
|
|
542
|
-
const inputs = this.getInputs(
|
|
543
|
-
|
|
544
|
-
nodes: Array.from(this.runtime.getNodeIds()).map((id) => ({
|
|
545
|
-
nodeId: id,
|
|
546
|
-
typeId: "",
|
|
547
|
-
})),
|
|
548
|
-
edges: [],
|
|
549
|
-
}
|
|
550
|
-
: { nodes: [], edges: [] });
|
|
551
|
-
const outputs = this.getOutputs(this.runtime
|
|
552
|
-
? {
|
|
553
|
-
nodes: Array.from(this.runtime.getNodeIds()).map((id) => ({
|
|
554
|
-
nodeId: id,
|
|
555
|
-
typeId: "",
|
|
556
|
-
})),
|
|
557
|
-
edges: [],
|
|
558
|
-
}
|
|
559
|
-
: { nodes: [], edges: [] });
|
|
560
|
+
const inputs = this.getInputs();
|
|
561
|
+
const outputs = this.getOutputs();
|
|
560
562
|
const environment = this.getEnvironment() || {};
|
|
561
563
|
return { def, environment, inputs, outputs };
|
|
562
564
|
}
|
|
@@ -633,8 +635,8 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
633
635
|
this.emit("registry", this.registry);
|
|
634
636
|
// Trigger update so validation/UI refreshes using last known graph
|
|
635
637
|
try {
|
|
636
|
-
if (this.
|
|
637
|
-
this.update(this.
|
|
638
|
+
if (this.currentDef)
|
|
639
|
+
this.update(this.currentDef);
|
|
638
640
|
}
|
|
639
641
|
catch {
|
|
640
642
|
console.error("Failed to update graph definition after registry changed");
|
|
@@ -652,12 +654,12 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
652
654
|
console.warn("Unsupported operation for remote runner");
|
|
653
655
|
}
|
|
654
656
|
update(def) {
|
|
657
|
+
this.currentDef = def;
|
|
655
658
|
// Remote: forward update; ignore errors (fire-and-forget)
|
|
656
659
|
this.ensureRemoteRunner().then(async (runner) => {
|
|
657
660
|
try {
|
|
658
661
|
await runner.update(def);
|
|
659
662
|
this.emit("invalidate", { reason: "graph-updated" });
|
|
660
|
-
this.lastDef = def;
|
|
661
663
|
}
|
|
662
664
|
catch { }
|
|
663
665
|
});
|
|
@@ -669,7 +671,6 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
669
671
|
await runner.build(def);
|
|
670
672
|
// Signal UI after remote build as well
|
|
671
673
|
this.emit("invalidate", { reason: "graph-built" });
|
|
672
|
-
this.lastDef = def;
|
|
673
674
|
// Hydrate current remote inputs/outputs (including defaults) into cache
|
|
674
675
|
try {
|
|
675
676
|
const snap = await runner.snapshot();
|
|
@@ -805,12 +806,12 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
805
806
|
// For now, we expose an async helper on RemoteRunner. Keep sync signature per interface.
|
|
806
807
|
return undefined;
|
|
807
808
|
}
|
|
808
|
-
getOutputs(
|
|
809
|
+
getOutputs() {
|
|
809
810
|
const out = {};
|
|
810
811
|
const cache = this.valueCache;
|
|
811
|
-
if (!cache)
|
|
812
|
+
if (!cache || !this.currentDef)
|
|
812
813
|
return out;
|
|
813
|
-
for (const n of
|
|
814
|
+
for (const n of this.currentDef.nodes) {
|
|
814
815
|
const resolved = n.resolvedHandles?.outputs;
|
|
815
816
|
const desc = this.registry.nodes.get(n.typeId);
|
|
816
817
|
const handles = Object.keys(resolved ?? desc?.outputs ?? {});
|
|
@@ -826,17 +827,19 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
826
827
|
}
|
|
827
828
|
return out;
|
|
828
829
|
}
|
|
829
|
-
getInputs(
|
|
830
|
+
getInputs() {
|
|
830
831
|
const out = {};
|
|
831
832
|
const cache = this.valueCache;
|
|
832
|
-
|
|
833
|
+
if (!this.currentDef)
|
|
834
|
+
return out;
|
|
835
|
+
for (const n of this.currentDef.nodes) {
|
|
833
836
|
const staged = this.stagedInputs[n.nodeId] ?? {};
|
|
834
837
|
const resolved = n.resolvedHandles?.inputs;
|
|
835
838
|
const desc = this.registry.nodes.get(n.typeId);
|
|
836
839
|
const handles = Object.keys(resolved ?? desc?.inputs ?? {});
|
|
837
840
|
const cur = {};
|
|
838
841
|
// Build inbound handle set for this node to honor wiring precedence
|
|
839
|
-
const inbound = new Set(
|
|
842
|
+
const inbound = new Set(this.currentDef.edges
|
|
840
843
|
.filter((e) => e.target.nodeId === n.nodeId)
|
|
841
844
|
.map((e) => e.target.handle));
|
|
842
845
|
for (const h of handles) {
|
|
@@ -855,6 +858,18 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
855
858
|
}
|
|
856
859
|
return out;
|
|
857
860
|
}
|
|
861
|
+
getInputDefaults() {
|
|
862
|
+
const out = {};
|
|
863
|
+
if (!this.currentDef)
|
|
864
|
+
return out;
|
|
865
|
+
for (const n of this.currentDef.nodes) {
|
|
866
|
+
const dynDefaults = n.resolvedHandles?.inputDefaults ?? {};
|
|
867
|
+
if (Object.keys(dynDefaults).length > 0) {
|
|
868
|
+
out[n.nodeId] = dynDefaults;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
return out;
|
|
872
|
+
}
|
|
858
873
|
dispose() {
|
|
859
874
|
super.dispose();
|
|
860
875
|
this.runner = undefined;
|
|
@@ -1606,6 +1621,7 @@ function toReactFlow(def, positions, registry, opts) {
|
|
|
1606
1621
|
renderWidth: initialWidth,
|
|
1607
1622
|
renderHeight: initialHeight,
|
|
1608
1623
|
inputValues: opts.inputs?.[n.nodeId],
|
|
1624
|
+
inputDefaults: opts.inputDefaults?.[n.nodeId],
|
|
1609
1625
|
outputValues: opts.outputs?.[n.nodeId],
|
|
1610
1626
|
status: opts.nodeStatus?.[n.nodeId],
|
|
1611
1627
|
validation: {
|
|
@@ -1771,8 +1787,9 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
1771
1787
|
const valuesTick = versionTick + graphTick + graphUiTick;
|
|
1772
1788
|
// Def and IO values
|
|
1773
1789
|
const def = wb.export();
|
|
1774
|
-
const inputsMap = useMemo(() => runner.getInputs(
|
|
1775
|
-
const
|
|
1790
|
+
const inputsMap = useMemo(() => runner.getInputs(), [runner, valuesTick]);
|
|
1791
|
+
const inputDefaultsMap = useMemo(() => runner.getInputDefaults(), [runner, valuesTick]);
|
|
1792
|
+
const outputsMap = useMemo(() => runner.getOutputs(), [runner, valuesTick]);
|
|
1776
1793
|
const outputTypesMap = useMemo(() => {
|
|
1777
1794
|
const out = {};
|
|
1778
1795
|
// Local: runtimeTypeId is not stored; derive from typed wrapper in outputsMap
|
|
@@ -1935,7 +1952,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
1935
1952
|
}
|
|
1936
1953
|
catch { }
|
|
1937
1954
|
};
|
|
1938
|
-
const
|
|
1955
|
+
const offRunnerValue = runner.on("value", (e) => {
|
|
1939
1956
|
if (e?.io === "input") {
|
|
1940
1957
|
const nodeId = e?.nodeId;
|
|
1941
1958
|
setNodeStatus((s) => ({
|
|
@@ -1945,7 +1962,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
1945
1962
|
}
|
|
1946
1963
|
return add("runner", "value")(e);
|
|
1947
1964
|
});
|
|
1948
|
-
const
|
|
1965
|
+
const offRunnerError = runner.on("error", (e) => {
|
|
1949
1966
|
const edgeError = e;
|
|
1950
1967
|
const nodeError = e;
|
|
1951
1968
|
const registryError = e;
|
|
@@ -1998,14 +2015,14 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
1998
2015
|
}
|
|
1999
2016
|
return add("runner", "error")(e);
|
|
2000
2017
|
});
|
|
2001
|
-
const
|
|
2018
|
+
const offRunnerInvalidate = runner.on("invalidate", (e) => {
|
|
2002
2019
|
// After build/update, pull resolved handles and merge in-place (no graphChanged)
|
|
2003
2020
|
if (e?.reason === "graph-updated" || e?.reason === "graph-built") {
|
|
2004
2021
|
refreshResolvedHandles();
|
|
2005
2022
|
}
|
|
2006
2023
|
return add("runner", "invalidate")(e);
|
|
2007
2024
|
});
|
|
2008
|
-
const
|
|
2025
|
+
const offRunnerStats = runner.on("stats", (s) => {
|
|
2009
2026
|
if (!s)
|
|
2010
2027
|
return;
|
|
2011
2028
|
if (s.kind === "node-start") {
|
|
@@ -2108,9 +2125,11 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
2108
2125
|
}
|
|
2109
2126
|
return add("runner", "stats")(s);
|
|
2110
2127
|
});
|
|
2111
|
-
const
|
|
2128
|
+
const offWbGraphChanged = wb.on("graphChanged", add("workbench", "graphChanged"));
|
|
2129
|
+
const offWbGraphUiChanged = wb.on("graphUiChanged", add("workbench", "graphUiChanged"));
|
|
2130
|
+
const offWbValidationChanged = wb.on("validationChanged", add("workbench", "validationChanged"));
|
|
2112
2131
|
// Ensure newly added nodes start as invalidated until first evaluation
|
|
2113
|
-
const
|
|
2132
|
+
const offWbAddNode = wb.on("graphChanged", (e) => {
|
|
2114
2133
|
const change = e.change;
|
|
2115
2134
|
if (change?.type === "addNode" && typeof change.nodeId === "string") {
|
|
2116
2135
|
const id = change.nodeId;
|
|
@@ -2120,17 +2139,14 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
2120
2139
|
}));
|
|
2121
2140
|
}
|
|
2122
2141
|
});
|
|
2123
|
-
const
|
|
2124
|
-
const
|
|
2125
|
-
const off5b = wb.on("validationChanged", (r) => setValidation(r));
|
|
2126
|
-
const off6 = wb.on("selectionChanged", (sel) => {
|
|
2142
|
+
const offWbdSetValidation = wb.on("validationChanged", (r) => setValidation(r));
|
|
2143
|
+
const offWbSelectionChanged = wb.on("selectionChanged", (sel) => {
|
|
2127
2144
|
setSelectedNodeId(sel.nodes?.[0]);
|
|
2128
2145
|
setSelectedEdgeId(sel.edges?.[0]);
|
|
2129
2146
|
});
|
|
2130
|
-
const
|
|
2131
|
-
wb.refreshValidation();
|
|
2147
|
+
const offWbError = wb.on("error", add("workbench", "error"));
|
|
2132
2148
|
// Registry updates: swap registry and refresh graph validation/UI
|
|
2133
|
-
const
|
|
2149
|
+
const offRunnerRegistry = runner.on("registry", (newReg) => {
|
|
2134
2150
|
try {
|
|
2135
2151
|
setRegistry(newReg);
|
|
2136
2152
|
wb.setRegistry(newReg);
|
|
@@ -2146,29 +2162,40 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
2146
2162
|
console.error("Failed to handle registry changed event");
|
|
2147
2163
|
}
|
|
2148
2164
|
});
|
|
2165
|
+
// Handle transport disconnect: reset runtime status when connection is lost
|
|
2166
|
+
const offRunnerTransport = runner.on("transport", (t) => {
|
|
2167
|
+
if (t.state === "disconnected") {
|
|
2168
|
+
console.info("[WorkbenchContext] Transport disconnected, resetting node status");
|
|
2169
|
+
setNodeStatus({});
|
|
2170
|
+
setEdgeStatus({});
|
|
2171
|
+
setFallbackStarts({});
|
|
2172
|
+
errorRunsRef.current = {};
|
|
2173
|
+
}
|
|
2174
|
+
});
|
|
2175
|
+
wb.refreshValidation();
|
|
2149
2176
|
return () => {
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2177
|
+
offRunnerValue();
|
|
2178
|
+
offRunnerError();
|
|
2179
|
+
offRunnerInvalidate();
|
|
2180
|
+
offRunnerStats();
|
|
2181
|
+
offWbGraphChanged();
|
|
2182
|
+
offWbGraphUiChanged();
|
|
2183
|
+
offWbValidationChanged();
|
|
2184
|
+
offWbError();
|
|
2185
|
+
offWbAddNode();
|
|
2186
|
+
offWbdSetValidation();
|
|
2187
|
+
offWbSelectionChanged();
|
|
2188
|
+
offRunnerRegistry();
|
|
2189
|
+
offRunnerTransport();
|
|
2162
2190
|
};
|
|
2163
2191
|
}, [runner, wb]);
|
|
2164
|
-
//
|
|
2192
|
+
// Keep runner.currentDef in sync with the workbench graph at all times.
|
|
2193
|
+
// When an engine/runtime exists, this also pushes incremental wiring changes into it.
|
|
2165
2194
|
useEffect(() => {
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
runner.update(def);
|
|
2169
|
-
}
|
|
2170
|
-
catch { }
|
|
2195
|
+
try {
|
|
2196
|
+
runner.update(def);
|
|
2171
2197
|
}
|
|
2198
|
+
catch { }
|
|
2172
2199
|
}, [runner, def, graphTick]);
|
|
2173
2200
|
const validationByNode = useMemo(() => {
|
|
2174
2201
|
const inputs = {};
|
|
@@ -2257,6 +2284,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
2257
2284
|
edgeStatus,
|
|
2258
2285
|
valuesTick,
|
|
2259
2286
|
inputsMap,
|
|
2287
|
+
inputDefaultsMap,
|
|
2260
2288
|
outputsMap,
|
|
2261
2289
|
outputTypesMap,
|
|
2262
2290
|
validationByNode,
|
|
@@ -2299,6 +2327,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
2299
2327
|
removeSystemError,
|
|
2300
2328
|
removeRegistryError,
|
|
2301
2329
|
inputsMap,
|
|
2330
|
+
inputDefaultsMap,
|
|
2302
2331
|
outputsMap,
|
|
2303
2332
|
validationByNode,
|
|
2304
2333
|
validationByEdge,
|
|
@@ -2384,7 +2413,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
|
|
|
2384
2413
|
return String(value ?? "");
|
|
2385
2414
|
}
|
|
2386
2415
|
};
|
|
2387
|
-
const { registry, def, selectedNodeId, selectedEdgeId, inputsMap, outputsMap, outputTypesMap, nodeStatus, edgeStatus, validationByNode, validationByEdge, validationGlobal, valuesTick, updateEdgeType, systemErrors, registryErrors, clearSystemErrors, clearRegistryErrors, removeSystemError, removeRegistryError, } = useWorkbenchContext();
|
|
2416
|
+
const { registry, def, selectedNodeId, selectedEdgeId, inputsMap, inputDefaultsMap, outputsMap, outputTypesMap, nodeStatus, edgeStatus, validationByNode, validationByEdge, validationGlobal, valuesTick, updateEdgeType, systemErrors, registryErrors, clearSystemErrors, clearRegistryErrors, removeSystemError, removeRegistryError, } = useWorkbenchContext();
|
|
2388
2417
|
const nodeValidationIssues = validationByNode.issues;
|
|
2389
2418
|
const edgeValidationIssues = validationByEdge.issues;
|
|
2390
2419
|
const nodeValidationHandles = validationByNode;
|
|
@@ -2399,8 +2428,33 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
|
|
|
2399
2428
|
.filter(([k]) => !isInputPrivate(effectiveHandles.inputs, k))
|
|
2400
2429
|
.map(([k]) => k);
|
|
2401
2430
|
const outputHandles = Object.keys(effectiveHandles.outputs);
|
|
2402
|
-
const
|
|
2431
|
+
const nodeInputsRaw = selectedNodeId ? inputsMap[selectedNodeId] ?? {} : {};
|
|
2432
|
+
const nodeInputsDefaults = selectedNodeId
|
|
2433
|
+
? inputDefaultsMap[selectedNodeId] ?? {}
|
|
2434
|
+
: {};
|
|
2435
|
+
// Keep defaults separate for placeholder use (don't merge into nodeInputs)
|
|
2436
|
+
const nodeInputs = nodeInputsRaw;
|
|
2403
2437
|
const nodeOutputs = selectedNodeId ? outputsMap[selectedNodeId] ?? {} : {};
|
|
2438
|
+
// Helper to truncate long values
|
|
2439
|
+
const truncateValue = (str, maxLen = 50) => {
|
|
2440
|
+
if (str.length <= maxLen)
|
|
2441
|
+
return str;
|
|
2442
|
+
const start = Math.floor(maxLen / 2) - 5;
|
|
2443
|
+
const end = str.length - Math.floor(maxLen / 2) + 5;
|
|
2444
|
+
return `${str.slice(0, start)}...${str.slice(end)}`;
|
|
2445
|
+
};
|
|
2446
|
+
// Helper to copy to clipboard
|
|
2447
|
+
const copyToClipboard = (text) => {
|
|
2448
|
+
navigator.clipboard.writeText(text).catch(() => {
|
|
2449
|
+
// Fallback for older browsers
|
|
2450
|
+
const textarea = document.createElement("textarea");
|
|
2451
|
+
textarea.value = text;
|
|
2452
|
+
document.body.appendChild(textarea);
|
|
2453
|
+
textarea.select();
|
|
2454
|
+
document.execCommand("copy");
|
|
2455
|
+
document.body.removeChild(textarea);
|
|
2456
|
+
});
|
|
2457
|
+
};
|
|
2404
2458
|
const selectedNodeStatus = selectedNodeId
|
|
2405
2459
|
? nodeStatus?.[selectedNodeId]
|
|
2406
2460
|
: undefined;
|
|
@@ -2486,19 +2540,30 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
|
|
|
2486
2540
|
}, children: [jsx("option", { value: "", children: "(infer from source)" }), Array.from(registry.types.keys()).map((tid) => (jsx("option", { value: tid, children: tid }, tid)))] })] })] }), selectedEdgeValidation.length > 0 && (jsxs("div", { className: "mt-2 text-xs bg-red-50 border border-red-200 rounded px-2 py-1", children: [jsx("div", { className: "font-semibold mb-1", children: "Validation" }), jsx("ul", { className: "list-disc ml-4", children: selectedEdgeValidation.map((m, i) => (jsxs("li", { className: "flex items-center gap-1", children: [jsx(IssueBadge, { level: m.level, size: 24, className: "w-6 h-6" }), jsx("span", { children: `${m.code}: ${m.message}` }), jsx("button", { className: "ml-2 text-[10px] px-1 py-[2px] border border-red-300 rounded text-red-700 hover:bg-red-50", onClick: (e) => {
|
|
2487
2541
|
e.stopPropagation();
|
|
2488
2542
|
deleteEdgeById(selectedEdge.id);
|
|
2489
|
-
}, title: "Delete this edge", children: "Delete edge" })] }, i))) })] }))] })) : (jsxs("div", { children: [selectedNode && (jsxs("div", { className: "mb-2", children: [jsxs("div", { children: ["Node: ", selectedNode.nodeId] }), jsxs("div", { children: ["Type: ", selectedNode.typeId] }), selectedNodeStatus?.activeRuns &&
|
|
2543
|
+
}, title: "Delete this edge", children: "Delete edge" })] }, i))) })] }))] })) : (jsxs("div", { children: [selectedNode && (jsxs("div", { className: "mb-2", children: [jsxs("div", { children: ["Node: ", selectedNode.nodeId] }), jsxs("div", { children: ["Type: ", selectedNode.typeId] }), !!selectedNodeStatus?.activeRuns &&
|
|
2490
2544
|
selectedNodeStatus.activeRuns > 0 && (jsxs("div", { className: "mt-1 text-xs text-blue-700 bg-blue-50 border border-blue-200 rounded px-2 py-1", children: [jsxs("div", { className: "font-semibold", children: ["Running (", selectedNodeStatus.activeRuns, ")"] }), selectedNodeStatus.activeRunIds &&
|
|
2491
2545
|
selectedNodeStatus.activeRunIds.length > 0 ? (jsxs("div", { className: "mt-1", children: [jsx("div", { className: "text-[10px] text-blue-600", children: "RunIds:" }), jsx("div", { className: "flex flex-wrap gap-1 mt-1", children: selectedNodeStatus.activeRunIds.map((runId, idx) => (jsx("span", { className: "text-[10px] px-1.5 py-0.5 bg-blue-100 border border-blue-300 rounded font-mono", children: runId }, idx))) })] })) : (jsx("div", { className: "text-[10px] text-blue-600 mt-1", children: "RunIds not available (some runs may have started without runId)" }))] })), !!selectedNodeStatus?.lastError && (jsx("div", { className: "mt-2 text-sm text-red-700 bg-red-50 border border-red-200 rounded px-2 py-1 break-words", children: String(selectedNodeStatus.lastError?.message ??
|
|
2492
2546
|
selectedNodeStatus.lastError) }))] })), jsxs("div", { className: "mb-2", children: [jsx("div", { className: "font-semibold mb-1", children: "Inputs" }), inputHandles.length === 0 ? (jsx("div", { className: "text-gray-500", children: "No inputs" })) : (inputHandles.map((h) => {
|
|
2493
2547
|
const typeId = getInputTypeId(effectiveHandles.inputs, h);
|
|
2494
2548
|
const isLinked = def.edges.some((e) => e.target.nodeId === selectedNodeId &&
|
|
2495
2549
|
e.target.handle === h);
|
|
2550
|
+
const inbound = new Set(def.edges
|
|
2551
|
+
.filter((e) => e.target.nodeId === selectedNodeId &&
|
|
2552
|
+
e.target.handle === h)
|
|
2553
|
+
.map((e) => e.target.handle));
|
|
2554
|
+
const hasDefault = !inbound.has(h) && nodeInputsDefaults[h] !== undefined;
|
|
2555
|
+
const defaultStr = hasDefault
|
|
2556
|
+
? safeToString(typeId, nodeInputsDefaults[h])
|
|
2557
|
+
: undefined;
|
|
2496
2558
|
const commonProps = {
|
|
2497
2559
|
style: { flex: 1 },
|
|
2498
2560
|
disabled: isLinked,
|
|
2499
2561
|
};
|
|
2500
2562
|
const current = nodeInputs[h];
|
|
2563
|
+
const hasValue = current !== undefined && current !== null;
|
|
2501
2564
|
const value = drafts[h] ?? safeToString(typeId, current);
|
|
2565
|
+
const displayValue = hasValue ? value : "";
|
|
2566
|
+
const placeholder = hasDefault ? defaultStr : undefined;
|
|
2502
2567
|
const onChangeText = (text) => setDrafts((d) => ({ ...d, [h]: text }));
|
|
2503
2568
|
const commit = () => {
|
|
2504
2569
|
const draft = drafts[h];
|
|
@@ -2511,6 +2576,11 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
|
|
|
2511
2576
|
const orig = originals[h] ?? safeToString(typeId, current);
|
|
2512
2577
|
setDrafts((d) => ({ ...d, [h]: orig }));
|
|
2513
2578
|
};
|
|
2579
|
+
const clearInput = () => {
|
|
2580
|
+
setInput(h, undefined);
|
|
2581
|
+
setDrafts((d) => ({ ...d, [h]: "" }));
|
|
2582
|
+
setOriginals((o) => ({ ...o, [h]: "" }));
|
|
2583
|
+
};
|
|
2514
2584
|
const isEnum = typeId?.startsWith("enum:");
|
|
2515
2585
|
const inIssues = selectedNodeHandleValidation.inputs.filter((m) => m.handle === h);
|
|
2516
2586
|
const hasValidation = inIssues.length > 0;
|
|
@@ -2518,25 +2588,43 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
|
|
|
2518
2588
|
const title = inIssues
|
|
2519
2589
|
.map((v) => `${v.code}: ${v.message}`)
|
|
2520
2590
|
.join("; ");
|
|
2521
|
-
return (jsxs("div", { className: "flex items-center gap-2 mb-1", children: [jsxs("label", { className: "w-32 flex flex-col", children: [jsx("span", { children: prettyHandle(h) }), jsx("span", { className: "text-gray-500 text-[11px]", children: typeId })] }), hasValidation && (jsx(IssueBadge, { level: hasErr ? "error" : "warning", size: 24, className: "ml-1 w-6 h-6", title: title })), isEnum ? (jsxs("select", { className: "border border-gray-300 rounded px-2 py-1 focus:outline-none focus:ring-2 focus:ring-blue-500
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2591
|
+
return (jsxs("div", { className: "flex items-center gap-2 mb-1", children: [jsxs("label", { className: "w-32 flex flex-col", children: [jsx("span", { children: prettyHandle(h) }), jsx("span", { className: "text-gray-500 text-[11px]", children: typeId })] }), hasValidation && (jsx(IssueBadge, { level: hasErr ? "error" : "warning", size: 24, className: "ml-1 w-6 h-6", title: title })), isEnum ? (jsxs("div", { className: "flex items-center gap-1 flex-1", children: [jsxs("select", { className: "border border-gray-300 rounded px-2 py-1 focus:outline-none focus:ring-2 focus:ring-blue-500 flex-1", value: current !== undefined && current !== null
|
|
2592
|
+
? String(current)
|
|
2593
|
+
: "", onChange: (e) => {
|
|
2594
|
+
const val = e.target.value;
|
|
2595
|
+
const raw = val === "" ? undefined : Number(val);
|
|
2596
|
+
setInput(h, raw);
|
|
2597
|
+
// keep drafts/originals in sync with label for display elsewhere
|
|
2598
|
+
const display = safeToString(typeId, raw);
|
|
2599
|
+
setDrafts((d) => ({ ...d, [h]: display }));
|
|
2600
|
+
setOriginals((o) => ({ ...o, [h]: display }));
|
|
2601
|
+
}, ...commonProps, children: [jsx("option", { value: "", children: placeholder
|
|
2602
|
+
? `Default: ${placeholder}`
|
|
2603
|
+
: "(select)" }), registry.enums
|
|
2604
|
+
.get(typeId)
|
|
2605
|
+
?.options.map((opt) => (jsx("option", { value: String(opt.value), children: opt.label }, opt.value)))] }), hasValue && !isLinked && (jsx("button", { className: "flex-shrink-0 p-1 hover:bg-gray-100 rounded text-gray-500 hover:text-gray-700", onClick: clearInput, title: "Clear input value", children: jsx(XCircleIcon, { size: 16 }) }))] })) : isLinked ? (jsx("div", { className: "flex items-center gap-1 flex-1", children: jsx("div", { className: "flex-1 min-w-0", children: (() => {
|
|
2606
|
+
const displayStr = safeToString(typeId, current);
|
|
2607
|
+
const isLong = displayStr.length > 50;
|
|
2608
|
+
const truncated = isLong
|
|
2609
|
+
? truncateValue(displayStr)
|
|
2610
|
+
: displayStr;
|
|
2611
|
+
return (jsxs("div", { className: "flex items-center gap-1", children: [jsx("span", { className: "truncate", children: truncated }), isLong && (jsx("button", { className: "flex-shrink-0 p-1 hover:bg-gray-100 rounded", onClick: () => copyToClipboard(displayStr), title: "Copy full value", children: jsx(CopyIcon, { size: 14 }) }))] }));
|
|
2612
|
+
})() }) })) : (jsxs("div", { className: "flex items-center gap-1 flex-1", children: [jsx("input", { className: "border border-gray-300 rounded px-2 py-1 focus:outline-none focus:ring-2 focus:ring-blue-500 flex-1", placeholder: placeholder
|
|
2613
|
+
? `Default: ${placeholder}`
|
|
2614
|
+
: undefined, value: displayValue, onChange: (e) => onChangeText(e.target.value), onBlur: commit, onKeyDown: (e) => {
|
|
2615
|
+
if (e.key === "Enter")
|
|
2616
|
+
commit();
|
|
2617
|
+
if (e.key === "Escape")
|
|
2618
|
+
revert();
|
|
2619
|
+
}, ...commonProps }), hasValue && !isLinked && (jsx("button", { className: "flex-shrink-0 p-1 hover:bg-gray-100 rounded text-gray-500 hover:text-gray-700", onClick: clearInput, title: "Clear input value", children: jsx(XCircleIcon, { size: 16 }) }))] }))] }, h));
|
|
2620
|
+
}))] }), jsxs("div", { children: [jsx("div", { className: "font-semibold mb-1", children: "Outputs" }), outputHandles.length === 0 ? (jsx("div", { className: "text-gray-500", children: "No outputs" })) : (outputHandles.map((h) => (jsxs("div", { className: "flex items-center gap-2 mb-1", children: [jsxs("label", { className: "w-20 flex flex-col", children: [jsx("span", { children: prettyHandle(h) }), jsx("span", { className: "text-gray-500 text-[11px]", children: outputTypesMap[selectedNodeId]?.[h] ?? "" })] }), jsx("div", { className: "flex-1 min-w-0", children: (() => {
|
|
2538
2621
|
const { typeId, value } = resolveOutputDisplay(nodeOutputs[h], effectiveHandles.outputs[h]);
|
|
2539
|
-
|
|
2622
|
+
const displayStr = safeToString(typeId, value);
|
|
2623
|
+
const isLong = displayStr.length > 50;
|
|
2624
|
+
const truncated = isLong
|
|
2625
|
+
? truncateValue(displayStr)
|
|
2626
|
+
: displayStr;
|
|
2627
|
+
return (jsxs("div", { className: "flex items-center gap-1", children: [jsx("span", { className: "truncate", children: truncated }), isLong && (jsx("button", { className: "flex-shrink-0 p-1 hover:bg-gray-100 rounded", onClick: () => copyToClipboard(displayStr), title: "Copy full value", children: jsx(CopyIcon, { size: 14 }) }))] }));
|
|
2540
2628
|
})() }), (() => {
|
|
2541
2629
|
const outIssues = selectedNodeHandleValidation.outputs.filter((m) => m.handle === h);
|
|
2542
2630
|
if (outIssues.length === 0)
|
|
@@ -2678,7 +2766,7 @@ function DefaultNodeHeader({ id, title, validation, right, showId, onInvalidate,
|
|
|
2678
2766
|
.join("; ") })), showId && jsxs("span", { className: "text-[10px] opacity-70", children: ["(", id, ")"] })] })] }));
|
|
2679
2767
|
}
|
|
2680
2768
|
function DefaultNodeContent({ data, isConnectable, }) {
|
|
2681
|
-
const { showValues, inputValues, outputValues, toString } = data;
|
|
2769
|
+
const { showValues, inputValues, inputDefaults, outputValues, toString } = data;
|
|
2682
2770
|
const inputEntries = data.inputHandles ?? [];
|
|
2683
2771
|
const outputEntries = data.outputHandles ?? [];
|
|
2684
2772
|
const status = data.status ?? { activeRuns: 0 };
|
|
@@ -2707,14 +2795,23 @@ function DefaultNodeContent({ data, isConnectable, }) {
|
|
|
2707
2795
|
if (!showValues)
|
|
2708
2796
|
return undefined;
|
|
2709
2797
|
if (kind === "input") {
|
|
2710
|
-
const
|
|
2711
|
-
|
|
2798
|
+
const value = inputValues?.[entry.id];
|
|
2799
|
+
const isDefault = value !== undefined &&
|
|
2800
|
+
inputDefaults?.[entry.id] !== undefined &&
|
|
2801
|
+
JSON.stringify(value) ===
|
|
2802
|
+
JSON.stringify(inputDefaults[entry.id]);
|
|
2803
|
+
const txt = toString(entry.typeId, value);
|
|
2804
|
+
const str = typeof txt === "string" ? txt : String(txt);
|
|
2805
|
+
return { text: str, isDefault };
|
|
2712
2806
|
}
|
|
2713
2807
|
const resolved = resolveOutputDisplay(outputValues?.[entry.id], entry.typeId);
|
|
2714
2808
|
const txt = toString(resolved.typeId, resolved.value);
|
|
2715
|
-
return
|
|
2809
|
+
return {
|
|
2810
|
+
text: typeof txt === "string" ? txt : String(txt),
|
|
2811
|
+
isDefault: false,
|
|
2812
|
+
};
|
|
2716
2813
|
})();
|
|
2717
|
-
return (jsxs("span", { className:
|
|
2814
|
+
return (jsxs("span", { className: `flex items-center gap-1 w-full ${valueText?.isDefault ? "text-gray-400" : ""}`, children: [kind === "output" ? (jsxs(Fragment, { children: [valueText !== undefined ? (jsx("span", { className: "opacity-60 truncate pl-1", style: { flex: 1, minWidth: 0, maxWidth: "100%" }, children: valueText.text })) : (jsx("span", { style: { flex: 1, minWidth: 0, maxWidth: "100%" } })), jsx("span", { className: "truncate shrink-0", style: valueText !== undefined ? { maxWidth: "40%" } : {}, children: prettyHandle(handleId) })] })) : (jsxs(Fragment, { children: [jsx("span", { className: "truncate shrink-0", style: valueText !== undefined ? { maxWidth: "40%" } : {}, children: prettyHandle(handleId) }), valueText !== undefined && (jsx("span", { className: `truncate pr-1 ${valueText.isDefault ? "text-gray-400" : "opacity-60"}`, style: { flex: 1, minWidth: 0, maxWidth: "100%" }, children: valueText.text }))] })), hasAny && (jsx(IssueBadge, { level: hasErr ? "error" : "warning", size: 12, className: "shrink-0", title: title }))] }));
|
|
2718
2815
|
} })] }));
|
|
2719
2816
|
}
|
|
2720
2817
|
|
|
@@ -3011,7 +3108,7 @@ function NodeContextMenu({ open, clientPos, nodeId, onClose, }) {
|
|
|
3011
3108
|
}
|
|
3012
3109
|
|
|
3013
3110
|
const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, getDefaultNodeSize }, ref) => {
|
|
3014
|
-
const { wb, registry, inputsMap, outputsMap, valuesTick, nodeStatus, edgeStatus, validationByNode, validationByEdge, uiVersion, } = useWorkbenchContext();
|
|
3111
|
+
const { wb, registry, inputsMap, inputDefaultsMap, outputsMap, valuesTick, nodeStatus, edgeStatus, validationByNode, validationByEdge, uiVersion, } = useWorkbenchContext();
|
|
3015
3112
|
const nodeValidation = validationByNode;
|
|
3016
3113
|
const edgeValidation = validationByEdge.errors;
|
|
3017
3114
|
// Keep stable references for nodes/edges to avoid unnecessary updates
|
|
@@ -3106,9 +3203,28 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
|
|
|
3106
3203
|
const { nodes, edges } = useMemo(() => {
|
|
3107
3204
|
const def = wb.export();
|
|
3108
3205
|
const sel = wb.getSelection();
|
|
3206
|
+
// Merge defaults with inputs for node display (defaults shown in lighter gray)
|
|
3207
|
+
const inputsWithDefaults = {};
|
|
3208
|
+
for (const n of def.nodes) {
|
|
3209
|
+
const nodeInputs = inputsMap[n.nodeId] ?? {};
|
|
3210
|
+
const nodeDefaults = inputDefaultsMap[n.nodeId] ?? {};
|
|
3211
|
+
const inbound = new Set(def.edges
|
|
3212
|
+
.filter((e) => e.target.nodeId === n.nodeId)
|
|
3213
|
+
.map((e) => e.target.handle));
|
|
3214
|
+
const merged = { ...nodeInputs };
|
|
3215
|
+
for (const [h, v] of Object.entries(nodeDefaults)) {
|
|
3216
|
+
if (!inbound.has(h) && merged[h] === undefined) {
|
|
3217
|
+
merged[h] = v;
|
|
3218
|
+
}
|
|
3219
|
+
}
|
|
3220
|
+
if (Object.keys(merged).length > 0) {
|
|
3221
|
+
inputsWithDefaults[n.nodeId] = merged;
|
|
3222
|
+
}
|
|
3223
|
+
}
|
|
3109
3224
|
const out = toReactFlow(def, wb.getPositions(), registry, {
|
|
3110
3225
|
showValues,
|
|
3111
|
-
inputs:
|
|
3226
|
+
inputs: inputsWithDefaults,
|
|
3227
|
+
inputDefaults: inputDefaultsMap,
|
|
3112
3228
|
outputs: outputsMap,
|
|
3113
3229
|
resolveNodeType,
|
|
3114
3230
|
toString,
|
|
@@ -3347,7 +3463,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
3347
3463
|
const off1 = wb.on("graphChanged", () => {
|
|
3348
3464
|
try {
|
|
3349
3465
|
const cur = wb.export();
|
|
3350
|
-
const inputs = runner.getInputs(
|
|
3466
|
+
const inputs = runner.getInputs();
|
|
3351
3467
|
onChange({ def: cur, inputs });
|
|
3352
3468
|
}
|
|
3353
3469
|
catch { }
|
|
@@ -3355,7 +3471,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
3355
3471
|
const off2 = runner.on("value", () => {
|
|
3356
3472
|
try {
|
|
3357
3473
|
const cur = wb.export();
|
|
3358
|
-
const inputs = runner.getInputs(
|
|
3474
|
+
const inputs = runner.getInputs();
|
|
3359
3475
|
onChange({ def: cur, inputs });
|
|
3360
3476
|
}
|
|
3361
3477
|
catch { }
|
|
@@ -3401,7 +3517,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
3401
3517
|
const downloadGraph = useCallback(() => {
|
|
3402
3518
|
try {
|
|
3403
3519
|
const def = wb.export();
|
|
3404
|
-
const inputs = runner.getInputs(
|
|
3520
|
+
const inputs = runner.getInputs();
|
|
3405
3521
|
const payload = { def, inputs };
|
|
3406
3522
|
const pretty = JSON.stringify(payload, null, 2);
|
|
3407
3523
|
const blob = new Blob([pretty], { type: "application/json" });
|