@bian-womp/spark-workbench 0.2.31 → 0.2.32

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 CHANGED
@@ -477,7 +477,7 @@ class LocalGraphRunner extends AbstractGraphRunner {
477
477
  this.engine.on("error", (e) => this.emit("error", e));
478
478
  this.engine.on("invalidate", (e) => this.emit("invalidate", e));
479
479
  this.engine.on("stats", (e) => this.emit("stats", e));
480
- this.engine.launch();
480
+ this.engine.launch(opts.invalidate);
481
481
  this.runningKind = opts.engine;
482
482
  this.emit("status", { running: true, engine: this.runningKind });
483
483
  for (const [nodeId, map] of Object.entries(this.stagedInputs)) {
@@ -710,7 +710,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
710
710
  this.listenersBound = true;
711
711
  }
712
712
  this.engine = eng;
713
- this.engine.launch();
713
+ this.engine.launch(opts.invalidate);
714
714
  this.runningKind = "push";
715
715
  this.emit("status", { running: true, engine: this.runningKind });
716
716
  for (const [nodeId, map] of Object.entries(this.stagedInputs)) {
@@ -754,9 +754,39 @@ class RemoteGraphRunner extends AbstractGraphRunner {
754
754
  }
755
755
  }
756
756
  async applySnapshotFull(payload) {
757
+ // Hydrate local cache first so UI can display values immediately
758
+ this.hydrateCacheFromSnapshot(payload);
759
+ // Then sync with backend
757
760
  const runner = await this.ensureRemoteRunner();
758
761
  await runner.applySnapshotFull(payload);
759
762
  }
763
+ /**
764
+ * Hydrates the local valueCache from a snapshot and emits value events.
765
+ * This ensures the UI can display inputs/outputs immediately without waiting
766
+ * for value events from the remote backend.
767
+ */
768
+ hydrateCacheFromSnapshot(snapshot) {
769
+ // Hydrate inputs
770
+ for (const [nodeId, map] of Object.entries(snapshot.inputs || {})) {
771
+ for (const [handle, value] of Object.entries(map || {})) {
772
+ this.valueCache.set(`${nodeId}.${handle}`, {
773
+ io: "input",
774
+ value,
775
+ });
776
+ this.emit("value", { nodeId, handle, value, io: "input" });
777
+ }
778
+ }
779
+ // Hydrate outputs
780
+ for (const [nodeId, map] of Object.entries(snapshot.outputs || {})) {
781
+ for (const [handle, value] of Object.entries(map || {})) {
782
+ this.valueCache.set(`${nodeId}.${handle}`, {
783
+ io: "output",
784
+ value,
785
+ });
786
+ this.emit("value", { nodeId, handle, value, io: "output" });
787
+ }
788
+ }
789
+ }
760
790
  setEnvironment(env, opts) {
761
791
  const t = this.transport;
762
792
  if (!t)
@@ -3659,12 +3689,17 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
3659
3689
  : undefined, children: [jsxRuntime.jsx("option", { value: "local", children: "Local" }), jsxRuntime.jsx("option", { value: "remote-http", children: "Remote (HTTP)" }), jsxRuntime.jsx("option", { value: "remote-ws", children: "Remote (WebSocket)" })] }), backendKind === "remote-http" && !!onHttpBaseUrlChange && (jsxRuntime.jsx("input", { className: "ml-2 border border-gray-300 rounded px-2 py-1 w-72", placeholder: "http://127.0.0.1:18080", value: httpBaseUrl, onChange: (e) => onHttpBaseUrlChange(e.target.value) })), backendKind === "remote-ws" && !!onWsUrlChange && (jsxRuntime.jsx("input", { className: "ml-2 border border-gray-300 rounded px-2 py-1 w-72", placeholder: "ws://127.0.0.1:18081", value: wsUrl, onChange: (e) => onWsUrlChange(e.target.value) })), jsxRuntime.jsxs("select", { className: "border border-gray-300 rounded px-2 py-1", value: runner.getRunningEngine() ?? engine ?? "", onChange: (e) => {
3660
3690
  const kind = e.target.value || undefined;
3661
3691
  onEngineChange?.(kind);
3662
- }, children: [jsxRuntime.jsx("option", { value: "", children: "Select Engine\u2026" }), jsxRuntime.jsx("option", { value: "push", children: "Push" }), jsxRuntime.jsx("option", { value: "batched", children: "Batched" }), jsxRuntime.jsx("option", { value: "pull", children: "Pull" }), jsxRuntime.jsx("option", { value: "hybrid", children: "Hybrid" }), jsxRuntime.jsx("option", { value: "step", children: "Step" })] }), runner.getRunningEngine() === "step" && (jsxRuntime.jsx("button", { className: "ml-2 border border-gray-300 rounded p-1", onClick: () => runner.step(), disabled: !runner.isRunning(), title: "Step", children: jsxRuntime.jsx(react$1.PlayPauseIcon, { size: 24 }) })), runner.getRunningEngine() === "batched" && (jsxRuntime.jsx("button", { className: "ml-2 border border-gray-300 rounded p-1", onClick: () => runner.flush(), disabled: !runner.isRunning(), title: "Flush", children: jsxRuntime.jsx(react$1.LightningIcon, { size: 24 }) })), runner.isRunning() ? (jsxRuntime.jsxs("button", { className: "border rounded px-2 py-1.5 text-red-700 border-red-600 flex items-center gap-1", onClick: () => runner.dispose(), title: "Stop engine", children: [jsxRuntime.jsx(react$1.StopIcon, { size: 16, weight: "fill" }), jsxRuntime.jsx("span", { className: "font-medium ml-1", children: "Stop" })] })) : (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", onClick: () => {
3692
+ }, children: [jsxRuntime.jsx("option", { value: "", children: "Select Engine\u2026" }), jsxRuntime.jsx("option", { value: "push", children: "Push" }), jsxRuntime.jsx("option", { value: "batched", children: "Batched" }), jsxRuntime.jsx("option", { value: "pull", children: "Pull" }), jsxRuntime.jsx("option", { value: "hybrid", children: "Hybrid" }), jsxRuntime.jsx("option", { value: "step", children: "Step" })] }), runner.getRunningEngine() === "step" && (jsxRuntime.jsx("button", { className: "ml-2 border border-gray-300 rounded p-1", onClick: () => runner.step(), disabled: !runner.isRunning(), title: "Step", children: jsxRuntime.jsx(react$1.PlayPauseIcon, { size: 24 }) })), runner.getRunningEngine() === "batched" && (jsxRuntime.jsx("button", { className: "ml-2 border border-gray-300 rounded p-1", onClick: () => runner.flush(), disabled: !runner.isRunning(), title: "Flush", children: jsxRuntime.jsx(react$1.LightningIcon, { size: 24 }) })), runner.isRunning() ? (jsxRuntime.jsxs("button", { className: "border rounded px-2 py-1.5 text-red-700 border-red-600 flex items-center gap-1", onClick: () => runner.dispose(), title: "Stop engine", children: [jsxRuntime.jsx(react$1.StopIcon, { size: 16, weight: "fill" }), jsxRuntime.jsx("span", { className: "font-medium ml-1", children: "Stop" })] })) : (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", onClick: (evt) => {
3663
3693
  const kind = engine;
3664
3694
  if (!kind)
3665
3695
  return alert("Select an engine first.");
3696
+ if (evt.shiftKey && !confirm("Invalidate and re-run graph?"))
3697
+ return;
3666
3698
  try {
3667
- runner.launch(wb.export(), { engine: kind });
3699
+ runner.launch(wb.export(), {
3700
+ engine: kind,
3701
+ invalidate: evt.shiftKey,
3702
+ });
3668
3703
  }
3669
3704
  catch (err) {
3670
3705
  const message = err instanceof Error ? err.message : String(err);