@bian-womp/spark-workbench 0.2.1 → 0.2.2

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
@@ -202,6 +202,20 @@ class InMemoryWorkbench extends AbstractWorkbench {
202
202
  });
203
203
  this.emit("validationChanged", this.validate());
204
204
  }
205
+ updateEdgeType(edgeId, typeId) {
206
+ const e = this.def.edges.find((x) => x.id === edgeId);
207
+ if (!e)
208
+ return;
209
+ if (!typeId)
210
+ delete e.typeId;
211
+ else
212
+ e.typeId = typeId;
213
+ this.emit("graphChanged", {
214
+ def: this.def,
215
+ change: { type: "updateEdgeType", edgeId, typeId },
216
+ });
217
+ this.refreshValidation();
218
+ }
205
219
  updateParams(nodeId, params) {
206
220
  const n = this.def.nodes.find((n) => n.nodeId === nodeId);
207
221
  if (!n)
@@ -780,23 +794,6 @@ function useWorkbenchBridge(wb) {
780
794
  }
781
795
  }
782
796
  }
783
- else if (type === "selectNodes") {
784
- const ids = change.ids;
785
- const selected = change.selected;
786
- if (Array.isArray(ids) && typeof selected === "boolean") {
787
- for (const id of ids) {
788
- if (selected) {
789
- if (!nextNodeIds.has(id)) {
790
- nextNodeIds.add(id);
791
- selectionChanged = true;
792
- }
793
- }
794
- else if (nextNodeIds.delete(id)) {
795
- selectionChanged = true;
796
- }
797
- }
798
- }
799
- }
800
797
  else if (type === "remove") {
801
798
  const id = change.id;
802
799
  if (nextNodeIds.delete(id))
@@ -804,7 +801,10 @@ function useWorkbenchBridge(wb) {
804
801
  }
805
802
  }
806
803
  if (selectionChanged) {
807
- wb.setSelection({ nodes: Array.from(nextNodeIds), edges: current.edges });
804
+ wb.setSelection({
805
+ nodes: Array.from(nextNodeIds),
806
+ edges: current.edges,
807
+ });
808
808
  }
809
809
  }, [wb]);
810
810
  const onEdgesDelete = React.useCallback((edges) => edges.forEach((e) => wb.disconnect(e.id)), [wb]);
@@ -829,23 +829,6 @@ function useWorkbenchBridge(wb) {
829
829
  }
830
830
  }
831
831
  }
832
- else if (type === "selectEdges") {
833
- const ids = change.ids;
834
- const selected = change.selected;
835
- if (Array.isArray(ids) && typeof selected === "boolean") {
836
- for (const id of ids) {
837
- if (selected) {
838
- if (!nextEdgeIds.has(id)) {
839
- nextEdgeIds.add(id);
840
- selectionChanged = true;
841
- }
842
- }
843
- else if (nextEdgeIds.delete(id)) {
844
- selectionChanged = true;
845
- }
846
- }
847
- }
848
- }
849
832
  else if (type === "remove") {
850
833
  const id = change.id;
851
834
  if (nextEdgeIds.delete(id))
@@ -853,7 +836,10 @@ function useWorkbenchBridge(wb) {
853
836
  }
854
837
  }
855
838
  if (selectionChanged) {
856
- wb.setSelection({ nodes: current.nodes, edges: Array.from(nextEdgeIds) });
839
+ wb.setSelection({
840
+ nodes: current.nodes,
841
+ edges: Array.from(nextEdgeIds),
842
+ });
857
843
  }
858
844
  }, [wb]);
859
845
  const onNodesDelete = React.useCallback((nodes) => {
@@ -910,7 +896,9 @@ function useThrottledValue(value, intervalMs) {
910
896
  const lastSetAtRef = React.useRef(0);
911
897
  const timeoutRef = React.useRef(null);
912
898
  React.useEffect(() => {
913
- const now = (typeof performance !== "undefined" && performance.now) ? performance.now() : Date.now();
899
+ const now = typeof performance !== "undefined" && performance.now
900
+ ? performance.now()
901
+ : Date.now();
914
902
  const elapsed = now - lastSetAtRef.current;
915
903
  if (elapsed >= intervalMs) {
916
904
  lastSetAtRef.current = now;
@@ -921,7 +909,10 @@ function useThrottledValue(value, intervalMs) {
921
909
  window.clearTimeout(timeoutRef.current);
922
910
  }
923
911
  timeoutRef.current = window.setTimeout(() => {
924
- lastSetAtRef.current = (typeof performance !== "undefined" && performance.now) ? performance.now() : Date.now();
912
+ lastSetAtRef.current =
913
+ typeof performance !== "undefined" && performance.now
914
+ ? performance.now()
915
+ : Date.now();
925
916
  setThrottled(value);
926
917
  timeoutRef.current = null;
927
918
  }, Math.max(0, intervalMs - elapsed));
@@ -1400,6 +1391,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, children, }) {
1400
1391
  });
1401
1392
  wb.setPositions(pos);
1402
1393
  }, [wb]);
1394
+ const updateEdgeType = React.useCallback((edgeId, typeId) => wb.updateEdgeType(edgeId, typeId), [wb]);
1403
1395
  // Subscribe to runner/workbench events
1404
1396
  React.useEffect(() => {
1405
1397
  const add = (source, type) => (payload) => setEvents((prev) => {
@@ -1410,8 +1402,15 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, children, }) {
1410
1402
  if (changeType === "moveNode" || changeType === "moveNodes")
1411
1403
  return prev;
1412
1404
  }
1405
+ const nextNo = prev.length > 0 ? (prev[0]?.no ?? 0) + 1 : 1;
1413
1406
  const next = [
1414
- { at: Date.now(), source, type, payload: structuredClone(payload) },
1407
+ {
1408
+ no: nextNo,
1409
+ at: Date.now(),
1410
+ source,
1411
+ type,
1412
+ payload: structuredClone(payload),
1413
+ },
1415
1414
  ...prev,
1416
1415
  ];
1417
1416
  return next.length > 200 ? next.slice(0, 200) : next;
@@ -1726,6 +1725,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, children, }) {
1726
1725
  step,
1727
1726
  flush,
1728
1727
  runAutoLayout,
1728
+ updateEdgeType,
1729
1729
  }), [
1730
1730
  wb,
1731
1731
  runner,
@@ -1752,6 +1752,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, children, }) {
1752
1752
  step,
1753
1753
  flush,
1754
1754
  runAutoLayout,
1755
+ wb,
1755
1756
  ]);
1756
1757
  return (jsxRuntime.jsx(WorkbenchContext.Provider, { value: value, children: children }));
1757
1758
  }
@@ -1787,7 +1788,7 @@ function DebugEvents({ autoScroll, onAutoScrollChange, hideWorkbench, onHideWork
1787
1788
  return String(v);
1788
1789
  }
1789
1790
  };
1790
- return (jsxRuntime.jsxs("div", { className: "flex flex-col h-full min-h-0", children: [jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-1", children: [jsxRuntime.jsx("div", { className: "font-semibold", children: "Events" }), jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [jsxRuntime.jsxs("label", { className: "flex items-center gap-1 text-xs text-gray-700", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: hideWorkbench, onChange: (e) => onHideWorkbenchChange?.(e.target.checked) }), jsxRuntime.jsx("span", { children: "Hide workbench" })] }), jsxRuntime.jsxs("label", { className: "flex items-center gap-1 text-xs text-gray-700", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: autoScroll, onChange: (e) => onAutoScrollChange?.(e.target.checked) }), jsxRuntime.jsx("span", { children: "Auto scroll" })] }), jsxRuntime.jsx("button", { onClick: clearEvents, className: "text-xs px-2 py-0.5 border border-gray-300 rounded", children: "Clear" })] })] }), jsxRuntime.jsx("div", { ref: scrollRef, className: "flex-1 overflow-auto text-[11px] leading-4 divide-y divide-gray-200", children: rows.map((ev, idx) => (jsxRuntime.jsxs("div", { className: "opacity-85 odd:bg-gray-50 px-2 py-1", children: [jsxRuntime.jsxs("div", { className: "flex items-baseline gap-2", children: [jsxRuntime.jsx("span", { className: "w-8 shrink-0 text-right text-gray-500 select-none", children: idx + 1 }), jsxRuntime.jsxs("span", { className: "text-gray-500", children: [new Date(ev.at).toLocaleTimeString(), " \u00B7 ", ev.source, ":", ev.type] })] }), jsxRuntime.jsx("pre", { className: "m-0 whitespace-pre-wrap ml-10", children: renderPayload(ev.payload) })] }, `${ev.at}:${idx}`))) })] }));
1791
+ return (jsxRuntime.jsxs("div", { className: "flex flex-col h-full min-h-0", children: [jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-1", children: [jsxRuntime.jsx("div", { className: "font-semibold", children: "Events" }), jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [jsxRuntime.jsxs("label", { className: "flex items-center gap-1 text-xs text-gray-700", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: hideWorkbench, onChange: (e) => onHideWorkbenchChange?.(e.target.checked) }), jsxRuntime.jsx("span", { children: "Hide workbench" })] }), jsxRuntime.jsxs("label", { className: "flex items-center gap-1 text-xs text-gray-700", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: autoScroll, onChange: (e) => onAutoScrollChange?.(e.target.checked) }), jsxRuntime.jsx("span", { children: "Auto scroll" })] }), jsxRuntime.jsx("button", { onClick: clearEvents, className: "text-xs px-2 py-0.5 border border-gray-300 rounded", children: "Clear" })] })] }), jsxRuntime.jsx("div", { ref: scrollRef, className: "flex-1 overflow-auto text-[11px] leading-4 divide-y divide-gray-200", children: rows.map((ev) => (jsxRuntime.jsxs("div", { className: "opacity-85 odd:bg-gray-50 px-2 py-1", children: [jsxRuntime.jsxs("div", { className: "flex items-baseline gap-2", children: [jsxRuntime.jsx("span", { className: "w-12 shrink-0 text-right text-gray-500 select-none", children: ev.no }), jsxRuntime.jsxs("span", { className: "text-gray-500", children: [new Date(ev.at).toLocaleTimeString(), " \u00B7 ", ev.source, ":", ev.type] })] }), jsxRuntime.jsx("pre", { className: "m-0 whitespace-pre-wrap ml-12", children: renderPayload(ev.payload) })] }, `${ev.at}:${ev.no}`))) })] }));
1791
1792
  }
1792
1793
 
1793
1794
  function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHideWorkbenchChange, toString, toElement, contextPanel, setInput, }) {
@@ -1802,7 +1803,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
1802
1803
  return String(value ?? "");
1803
1804
  }
1804
1805
  };
1805
- const { registry, def, selectedNodeId, selectedEdgeId, inputsMap, outputsMap, outputTypesMap, nodeStatus, validationByNode, validationByEdge, validationGlobal, valuesTick, } = useWorkbenchContext();
1806
+ const { registry, def, selectedNodeId, selectedEdgeId, inputsMap, outputsMap, outputTypesMap, nodeStatus, validationByNode, validationByEdge, validationGlobal, valuesTick, updateEdgeType, } = useWorkbenchContext();
1806
1807
  const nodeValidationIssues = validationByNode.issues;
1807
1808
  const edgeValidationIssues = validationByEdge.issues;
1808
1809
  const nodeValidationHandles = validationByNode;
@@ -1879,7 +1880,11 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
1879
1880
  setOriginals(nextOriginals);
1880
1881
  }, [selectedNodeId, selectedDesc, valuesTick]);
1881
1882
  const widthClass = debug ? "w-[480px]" : "w-[320px]";
1882
- return (jsxRuntime.jsxs("div", { className: `${widthClass} border-l border-gray-300 p-3 flex flex-col h-full min-h-0 overflow-hidden`, children: [contextPanel && (jsxRuntime.jsx("div", { className: "mb-2", children: contextPanel })), jsxRuntime.jsx("div", { className: "font-semibold mb-2", children: "Inspector" }), jsxRuntime.jsxs("div", { className: "text-xs text-gray-500 mb-2", children: ["valuesTick: ", valuesTick] }), jsxRuntime.jsx("div", { className: "flex-1 overflow-auto", children: !selectedNode && !selectedEdge ? (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("div", { className: "text-gray-500", children: "Select a node or edge." }), globalValidationIssues && globalValidationIssues.length > 0 && (jsxRuntime.jsxs("div", { className: "mt-2 text-xs bg-red-50 border border-red-200 rounded px-2 py-1", children: [jsxRuntime.jsx("div", { className: "font-semibold mb-1", children: "Validation" }), jsxRuntime.jsx("ul", { className: "list-disc ml-4", children: globalValidationIssues.map((m, i) => (jsxRuntime.jsxs("li", { className: "flex items-center gap-1", children: [jsxRuntime.jsx(IssueBadge, { level: m.level, size: 24, className: "w-6 h-6" }), jsxRuntime.jsx("span", { children: `${m.code}: ${m.message}` })] }, i))) })] }))] })) : selectedEdge ? (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsxs("div", { className: "mb-2", children: [jsxRuntime.jsxs("div", { children: ["Edge: ", selectedEdge.id] }), jsxRuntime.jsxs("div", { children: [selectedEdge.source.nodeId, ".", selectedEdge.source.handle, " \u2192", " ", selectedEdge.target.nodeId, ".", selectedEdge.target.handle] }), jsxRuntime.jsxs("div", { children: ["Type: ", selectedEdge.typeId] })] }), selectedEdgeValidation.length > 0 && (jsxRuntime.jsxs("div", { className: "mt-2 text-xs bg-red-50 border border-red-200 rounded px-2 py-1", children: [jsxRuntime.jsx("div", { className: "font-semibold mb-1", children: "Validation" }), jsxRuntime.jsx("ul", { className: "list-disc ml-4", children: selectedEdgeValidation.map((m, i) => (jsxRuntime.jsxs("li", { className: "flex items-center gap-1", children: [jsxRuntime.jsx(IssueBadge, { level: m.level, size: 24, className: "w-6 h-6" }), jsxRuntime.jsx("span", { children: `${m.code}: ${m.message}` })] }, i))) })] }))] })) : (jsxRuntime.jsxs("div", { children: [selectedNode && (jsxRuntime.jsxs("div", { className: "mb-2", children: [jsxRuntime.jsxs("div", { children: ["Node: ", selectedNode.nodeId] }), jsxRuntime.jsxs("div", { children: ["Type: ", selectedNode.typeId] }), !!selectedNodeStatus?.lastError && (jsxRuntime.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 ??
1883
+ return (jsxRuntime.jsxs("div", { className: `${widthClass} border-l border-gray-300 p-3 flex flex-col h-full min-h-0 overflow-hidden`, children: [contextPanel && jsxRuntime.jsx("div", { className: "mb-2", children: contextPanel }), jsxRuntime.jsx("div", { className: "font-semibold mb-2", children: "Inspector" }), jsxRuntime.jsxs("div", { className: "text-xs text-gray-500 mb-2", children: ["valuesTick: ", valuesTick] }), jsxRuntime.jsx("div", { className: "flex-1 overflow-auto", children: !selectedNode && !selectedEdge ? (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("div", { className: "text-gray-500", children: "Select a node or edge." }), globalValidationIssues && globalValidationIssues.length > 0 && (jsxRuntime.jsxs("div", { className: "mt-2 text-xs bg-red-50 border border-red-200 rounded px-2 py-1", children: [jsxRuntime.jsx("div", { className: "font-semibold mb-1", children: "Validation" }), jsxRuntime.jsx("ul", { className: "list-disc ml-4", children: globalValidationIssues.map((m, i) => (jsxRuntime.jsxs("li", { className: "flex items-center gap-1", children: [jsxRuntime.jsx(IssueBadge, { level: m.level, size: 24, className: "w-6 h-6" }), jsxRuntime.jsx("span", { children: `${m.code}: ${m.message}` })] }, i))) })] }))] })) : selectedEdge ? (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsxs("div", { className: "mb-2", children: [jsxRuntime.jsxs("div", { children: ["Edge: ", selectedEdge.id] }), jsxRuntime.jsxs("div", { children: [selectedEdge.source.nodeId, ".", selectedEdge.source.handle, " \u2192", " ", selectedEdge.target.nodeId, ".", selectedEdge.target.handle] }), jsxRuntime.jsxs("div", { className: "flex items-center gap-2 mt-1", children: [jsxRuntime.jsxs("label", { className: "w-20 flex flex-col", children: [jsxRuntime.jsx("span", { children: "Type" }), jsxRuntime.jsx("span", { className: "text-gray-500 text-[11px]", children: "DataTypeId" })] }), jsxRuntime.jsxs("select", { className: "border border-gray-300 rounded px-2 py-1 focus:outline-none focus:ring-2 focus:ring-blue-500 w-full", value: selectedEdge.typeId ?? "", onChange: (e) => {
1884
+ const v = e.target.value;
1885
+ const next = v === "" ? undefined : v;
1886
+ updateEdgeType(selectedEdge.id, next);
1887
+ }, children: [jsxRuntime.jsx("option", { value: "", children: "(infer from source)" }), Array.from(registry.types.keys()).map((tid) => (jsxRuntime.jsx("option", { value: tid, children: tid }, tid)))] })] })] }), selectedEdgeValidation.length > 0 && (jsxRuntime.jsxs("div", { className: "mt-2 text-xs bg-red-50 border border-red-200 rounded px-2 py-1", children: [jsxRuntime.jsx("div", { className: "font-semibold mb-1", children: "Validation" }), jsxRuntime.jsx("ul", { className: "list-disc ml-4", children: selectedEdgeValidation.map((m, i) => (jsxRuntime.jsxs("li", { className: "flex items-center gap-1", children: [jsxRuntime.jsx(IssueBadge, { level: m.level, size: 24, className: "w-6 h-6" }), jsxRuntime.jsx("span", { children: `${m.code}: ${m.message}` })] }, i))) })] }))] })) : (jsxRuntime.jsxs("div", { children: [selectedNode && (jsxRuntime.jsxs("div", { className: "mb-2", children: [jsxRuntime.jsxs("div", { children: ["Node: ", selectedNode.nodeId] }), jsxRuntime.jsxs("div", { children: ["Type: ", selectedNode.typeId] }), !!selectedNodeStatus?.lastError && (jsxRuntime.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 ??
1883
1888
  selectedNodeStatus.lastError) }))] })), jsxRuntime.jsxs("div", { className: "mb-2", children: [jsxRuntime.jsx("div", { className: "font-semibold mb-1", children: "Inputs" }), inputHandles.length === 0 ? (jsxRuntime.jsx("div", { className: "text-gray-500", children: "No inputs" })) : (inputHandles.map((h) => {
1884
1889
  const typeId = sparkGraph.getInputTypeId(selectedDesc?.inputs, h);
1885
1890
  const isLinked = def.edges.some((e) => e.target.nodeId === selectedNodeId &&