@bian-womp/spark-workbench 0.1.11 → 0.1.12

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
@@ -343,19 +343,9 @@ class GraphRunner {
343
343
  if (!this.runtime)
344
344
  return;
345
345
  // Prevent mid-run churn while wiring changes are applied
346
- try {
347
- this.runtime.pause();
348
- }
349
- catch {
350
- console.error("Failed to pause runtime");
351
- }
346
+ this.runtime.pause();
352
347
  this.runtime.update(def, this.registry);
353
- try {
354
- this.runtime.resume();
355
- }
356
- catch {
357
- console.error("Failed to resume runtime");
358
- }
348
+ this.runtime.resume();
359
349
  this.emit("invalidate", { reason: "graph-updated" });
360
350
  return;
361
351
  }
@@ -457,6 +447,51 @@ class GraphRunner {
457
447
  this.emit("value", { nodeId, handle, value, io: "input" });
458
448
  }
459
449
  }
450
+ // Batch update multiple inputs on a node and trigger a single run
451
+ setInputs(nodeId, inputs) {
452
+ if (!inputs)
453
+ return;
454
+ if (!this.stagedInputs[nodeId])
455
+ this.stagedInputs[nodeId] = {};
456
+ Object.assign(this.stagedInputs[nodeId], inputs);
457
+ // Local running: pause, set all inputs, resume, schedule a single recompute
458
+ if (this.backend.kind === "local" && this.engine && this.runtime) {
459
+ this.runtime.pause();
460
+ try {
461
+ for (const [handle, value] of Object.entries(inputs)) {
462
+ this.engine.setInput(nodeId, handle, value);
463
+ }
464
+ }
465
+ finally {
466
+ this.runtime.resume();
467
+ try {
468
+ this.runtime.__unsafe_scheduleInputsChanged(nodeId);
469
+ }
470
+ catch { }
471
+ }
472
+ }
473
+ // Remote running: forward inputs individually (no batch API available)
474
+ else if (this.engine && this.backend.kind !== "local") {
475
+ // Prefer batch if supported by remote engine
476
+ if (this.engine instanceof sparkRemote.RemoteEngine) {
477
+ this.engine.setInputs(nodeId, inputs);
478
+ }
479
+ else {
480
+ console.warn("Remote engine does not support setInputs");
481
+ for (const [handle, value] of Object.entries(inputs)) {
482
+ this.engine.setInput(nodeId, handle, value);
483
+ }
484
+ }
485
+ }
486
+ // Not running: emit value events so UI reflects staged values
487
+ else if (!this.engine) {
488
+ // Not running: emit a single synthetic value event per handle; UI will coalesce
489
+ console.warn("Remote engine does not exists");
490
+ for (const [handle, value] of Object.entries(inputs)) {
491
+ this.emit("value", { nodeId, handle, value, io: "input" });
492
+ }
493
+ }
494
+ }
460
495
  async step() {
461
496
  if (this.backend.kind !== "local")
462
497
  return; // unsupported remotely
@@ -817,7 +852,7 @@ function toReactFlow(def, positions, registry, opts) {
817
852
  })
818
853
  .map((e) => {
819
854
  const st = opts.edgeStatus?.[e.id];
820
- const isRunning = !!st?.running;
855
+ const isRunning = !!st?.activeRuns;
821
856
  const hasError = !!st?.lastError;
822
857
  const isInvalidEdge = !!opts.edgeValidation?.[e.id];
823
858
  const style = hasError || isInvalidEdge
@@ -848,7 +883,7 @@ function getNodeBorderClassNames(args) {
848
883
  const hasError = !!status.lastError;
849
884
  const hasValidationError = issues.some((i) => i?.level === "error");
850
885
  const hasValidationWarning = !hasValidationError && issues.length > 0;
851
- const isRunning = !!status.running;
886
+ const isRunning = !!status.activeRuns;
852
887
  const isInvalid = !!status.invalidated && !isRunning && !hasError;
853
888
  const borderWidth = selected ? "border-2" : "border";
854
889
  const borderStyle = isInvalid ? "border-dashed" : "border-solid";
@@ -1007,15 +1042,18 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, children, }) {
1007
1042
  return;
1008
1043
  if (s.kind === "node-start") {
1009
1044
  const id = s.nodeId;
1010
- setNodeStatus((prev) => ({
1011
- ...prev,
1012
- [id]: {
1013
- ...(prev[id] ?? {}),
1014
- running: true,
1015
- progress: 0,
1016
- invalidated: false,
1017
- },
1018
- }));
1045
+ setNodeStatus((prev) => {
1046
+ const active = Math.max(1, (prev[id]?.activeRuns ?? 0) + 1);
1047
+ return {
1048
+ ...prev,
1049
+ [id]: {
1050
+ ...(prev[id] ?? {}),
1051
+ activeRuns: active,
1052
+ progress: 0,
1053
+ invalidated: false,
1054
+ },
1055
+ };
1056
+ });
1019
1057
  }
1020
1058
  else if (s.kind === "node-progress") {
1021
1059
  const id = s.nodeId;
@@ -1023,31 +1061,45 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, children, }) {
1023
1061
  ...prev,
1024
1062
  [id]: {
1025
1063
  ...(prev[id] ?? {}),
1026
- running: true,
1027
1064
  progress: Number(s.progress) || 0,
1028
1065
  },
1029
1066
  }));
1030
1067
  }
1031
1068
  else if (s.kind === "node-done") {
1032
1069
  const id = s.nodeId;
1033
- setNodeStatus((prev) => ({
1034
- ...prev,
1035
- [id]: { ...(prev[id] ?? {}), running: false },
1036
- }));
1070
+ setNodeStatus((prev) => {
1071
+ const current = prev[id]?.activeRuns ?? 1;
1072
+ const nextActive = Math.max(0, current - 1);
1073
+ return {
1074
+ ...prev,
1075
+ [id]: {
1076
+ ...(prev[id] ?? {}),
1077
+ activeRuns: nextActive,
1078
+ },
1079
+ };
1080
+ });
1037
1081
  }
1038
1082
  else if (s.kind === "edge-start") {
1039
1083
  const id = s.edgeId;
1040
- setEdgeStatus((prev) => ({
1041
- ...prev,
1042
- [id]: { ...(prev[id] ?? {}), running: true },
1043
- }));
1084
+ setEdgeStatus((prev) => {
1085
+ const current = prev[id]?.activeRuns ?? 1;
1086
+ const nextActive = Math.max(0, current + 1);
1087
+ return {
1088
+ ...prev,
1089
+ [id]: { ...(prev[id] ?? {}), activeRuns: nextActive },
1090
+ };
1091
+ });
1044
1092
  }
1045
1093
  else if (s.kind === "edge-done") {
1046
1094
  const id = s.edgeId;
1047
- setEdgeStatus((prev) => ({
1048
- ...prev,
1049
- [id]: { ...(prev[id] ?? {}), running: false },
1050
- }));
1095
+ setEdgeStatus((prev) => {
1096
+ const current = prev[id]?.activeRuns ?? 1;
1097
+ const nextActive = Math.max(0, current - 1);
1098
+ return {
1099
+ ...prev,
1100
+ [id]: { ...(prev[id] ?? {}), activeRuns: nextActive },
1101
+ };
1102
+ });
1051
1103
  }
1052
1104
  return add("runner", "stats")(s);
1053
1105
  });
@@ -1426,7 +1478,7 @@ const DefaultNode = React.memo(function DefaultNode({ id, data, selected, isConn
1426
1478
  const hasError = !!status.lastError;
1427
1479
  const hasValidationError = validation.issues.some((i) => i.level === "error");
1428
1480
  const hasValidationWarning = !hasValidationError && validation.issues.length > 0;
1429
- const isRunning = !!status.running;
1481
+ const isRunning = !!status.activeRuns;
1430
1482
  const isInvalid = !!status.invalidated && !isRunning && !hasError;
1431
1483
  // Border color encodes severity; thickness encodes selection; style (dashed) encodes invalidated
1432
1484
  const borderWidth = selected ? "border-2" : "border";