@bian-womp/spark-workbench 0.2.4 → 0.2.6

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
@@ -6,8 +6,8 @@ var React = require('react');
6
6
  var react = require('@xyflow/react');
7
7
  var jsxRuntime = require('react/jsx-runtime');
8
8
  var react$1 = require('@phosphor-icons/react');
9
- var isEqual = require('lodash/isEqual');
10
9
  var cx = require('classnames');
10
+ var isEqual = require('lodash/isEqual');
11
11
 
12
12
  class DefaultUIExtensionRegistry {
13
13
  constructor() {
@@ -1122,8 +1122,10 @@ function toReactFlow(def, positions, registry, opts) {
1122
1122
  const HEADER_SIZE = NODE_HEADER_HEIGHT_PX;
1123
1123
  const ROW_SIZE = NODE_ROW_HEIGHT_PX;
1124
1124
  const maxRows = Math.max(inputHandles.length, outputHandles.length);
1125
- const initialWidth = opts.showValues ? 320 : 240;
1126
- const initialHeight = HEADER_SIZE + maxRows * ROW_SIZE;
1125
+ // Allow external override to dictate initial size
1126
+ const overrideSize = opts.getDefaultNodeSize?.(n.typeId);
1127
+ const initialWidth = overrideSize?.width ?? (opts.showValues ? 320 : 240);
1128
+ const initialHeight = overrideSize?.height ?? HEADER_SIZE + maxRows * ROW_SIZE;
1127
1129
  // Precompute handle bounds so edges can render immediately without waiting for measurement
1128
1130
  const handles = [
1129
1131
  // Inputs on the left as targets
@@ -1392,8 +1394,8 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, children, }) {
1392
1394
  layers.push(layer);
1393
1395
  q.splice(0, q.length, ...next);
1394
1396
  }
1395
- const X = 960;
1396
- const Y = 480;
1397
+ const X = 720;
1398
+ const Y = 600;
1397
1399
  const pos = {};
1398
1400
  layers.forEach((layer, layerIndex) => {
1399
1401
  layer.forEach((id, itemIndex) => {
@@ -1987,7 +1989,7 @@ function NodeHandles({ data, isConnectable, inputClassName = "!w-2 !h-2 !bg-gray
1987
1989
  const y = placed?.y;
1988
1990
  const cls = getClassName?.({ kind: "output", id: h.id, type: "source" }) ??
1989
1991
  outputClassName;
1990
- return (jsxRuntime.jsxs(React.Fragment, { children: [jsxRuntime.jsx(react.Handle, { id: h.id, type: "source", position: position, isConnectable: isConnectable, className: cls, style: y !== undefined ? { top: y } : undefined }), renderLabel && (jsxRuntime.jsx("div", { className: labelClassName + " right-2", style: {
1992
+ return (jsxRuntime.jsxs(React.Fragment, { children: [jsxRuntime.jsx(react.Handle, { id: h.id, type: "source", position: position, isConnectable: isConnectable, className: `${cls} wb-nodrag wb-nowheel`, style: y !== undefined ? { top: y } : undefined }), renderLabel && (jsxRuntime.jsx("div", { className: labelClassName + " right-2", style: {
1991
1993
  top: (y ?? 0) - 8,
1992
1994
  left: "50%",
1993
1995
  textAlign: "right",
@@ -2000,7 +2002,7 @@ function NodeHandles({ data, isConnectable, inputClassName = "!w-2 !h-2 !bg-gray
2000
2002
 
2001
2003
  const DefaultNode = React.memo(function DefaultNode({ id, data, selected, isConnectable, }) {
2002
2004
  const updateNodeInternals = react.useUpdateNodeInternals();
2003
- const { typeId, showValues, inputValues, outputValues, toString } = data;
2005
+ const { typeId, showValues } = data;
2004
2006
  const inputEntries = data.inputHandles ?? [];
2005
2007
  const outputEntries = data.outputHandles ?? [];
2006
2008
  React.useEffect(() => {
@@ -2018,14 +2020,11 @@ const DefaultNode = React.memo(function DefaultNode({ id, data, selected, isConn
2018
2020
  outputs: [],
2019
2021
  issues: [],
2020
2022
  };
2021
- const hasError = !!status.lastError;
2022
- const isRunning = !!status.activeRuns;
2023
2023
  const containerBorder = getNodeBorderClassNames({
2024
2024
  selected,
2025
2025
  status,
2026
2026
  validation,
2027
2027
  });
2028
- const pct = Math.round(Math.max(0, Math.min(1, Number(status.progress) || 0)) * 100);
2029
2028
  return (jsxRuntime.jsxs("div", { className: cx("rounded-lg bg-white/70 !dark:bg-stone-900", containerBorder), style: {
2030
2029
  position: "relative",
2031
2030
  minWidth: typeof data.renderWidth === "number" ? data.renderWidth : undefined,
@@ -2033,11 +2032,24 @@ const DefaultNode = React.memo(function DefaultNode({ id, data, selected, isConn
2033
2032
  }, children: [jsxRuntime.jsxs("div", { className: "flex items-center justify-center px-2 border-b border-solid border-gray-500 dark:border-gray-400 text-gray-600 dark:text-gray-300", style: {
2034
2033
  maxHeight: NODE_HEADER_HEIGHT_PX,
2035
2034
  minHeight: NODE_HEADER_HEIGHT_PX,
2036
- }, children: [jsxRuntime.jsx("strong", { className: "flex-1 h-full text-sm", style: { lineHeight: `${NODE_HEADER_HEIGHT_PX}px` }, children: typeId }), jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [hasError && (jsxRuntime.jsx("span", { title: String(status.lastError?.message ?? status.lastError), children: jsxRuntime.jsx(react$1.XCircleIcon, { size: 12, weight: "fill", className: "text-red-500" }) })), validation.issues && validation.issues.length > 0 && (jsxRuntime.jsx(IssueBadge, { level: validation.issues.some((i) => i.level === "error")
2035
+ }, children: [jsxRuntime.jsx("strong", { className: "flex-1 h-full text-sm", style: { lineHeight: `${NODE_HEADER_HEIGHT_PX}px` }, children: typeId }), jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [validation.issues && validation.issues.length > 0 && (jsxRuntime.jsx(IssueBadge, { level: validation.issues.some((i) => i.level === "error")
2037
2036
  ? "error"
2038
2037
  : "warning", size: 12, className: "w-3 h-3", title: validation.issues
2039
2038
  .map((v) => `${v.code}: ${v.message}`)
2040
- .join("; ") })), jsxRuntime.jsxs("span", { className: "text-[10px] opacity-70", children: ["(", id, ")"] })] })] }), jsxRuntime.jsx("div", { className: cx("h-px", (isRunning || pct > 0) && "bg-blue-200 dark:bg-blue-900"), children: jsxRuntime.jsx("div", { className: cx("h-px transition-all", (isRunning || pct > 0) && "bg-blue-500"), style: { width: isRunning || pct > 0 ? `${pct}%` : 0 } }) }), jsxRuntime.jsx(NodeHandles, { data: data, isConnectable: isConnectable, getClassName: ({ kind, id }) => {
2039
+ .join("; ") })), jsxRuntime.jsxs("span", { className: "text-[10px] opacity-70", children: ["(", id, ")"] })] })] }), jsxRuntime.jsx(DefaultNodeContent, { data: data, isConnectable: isConnectable })] }));
2040
+ });
2041
+ DefaultNode.displayName = "DefaultNode";
2042
+ function DefaultNodeContent({ data, isConnectable, }) {
2043
+ const { showValues, inputValues, outputValues, toString } = data;
2044
+ const inputEntries = data.inputHandles ?? [];
2045
+ const outputEntries = data.outputHandles ?? [];
2046
+ const status = data.status ?? { activeRuns: 0 };
2047
+ const validation = data.validation ?? {
2048
+ inputs: [],
2049
+ outputs: []};
2050
+ const isRunning = !!status.activeRuns;
2051
+ const pct = Math.round(Math.max(0, Math.min(1, Number(status.progress) || 0)) * 100);
2052
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: cx("h-px", (isRunning || pct > 0) && "bg-blue-200 dark:bg-blue-900"), children: jsxRuntime.jsx("div", { className: cx("h-px transition-all", (isRunning || pct > 0) && "bg-blue-500"), style: { width: isRunning || pct > 0 ? `${pct}%` : 0 } }) }), jsxRuntime.jsx(NodeHandles, { data: data, isConnectable: isConnectable, getClassName: ({ kind, id }) => {
2041
2053
  const vIssues = (kind === "input" ? validation.inputs : validation.outputs).filter((v) => v.handle === id);
2042
2054
  const hasAny = vIssues.length > 0;
2043
2055
  const hasErr = vIssues.some((v) => v.level === "error");
@@ -2053,7 +2065,6 @@ const DefaultNode = React.memo(function DefaultNode({ id, data, selected, isConn
2053
2065
  const title = vIssues
2054
2066
  .map((v) => `${v.code}: ${v.message}`)
2055
2067
  .join("; ");
2056
- // Compose label with truncated value to prevent layout growth
2057
2068
  const valueText = (() => {
2058
2069
  if (!showValues)
2059
2070
  return undefined;
@@ -2067,8 +2078,7 @@ const DefaultNode = React.memo(function DefaultNode({ id, data, selected, isConn
2067
2078
  })();
2068
2079
  return (jsxRuntime.jsxs("span", { className: "flex items-center gap-1 w-full", children: [kind === "output" ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [valueText !== undefined && (jsxRuntime.jsx("span", { className: "opacity-60 truncate pl-1", style: { flex: 1, minWidth: 0, maxWidth: "100%" }, children: valueText })), jsxRuntime.jsx("span", { className: "truncate shrink-0", style: { maxWidth: "40%" }, children: id })] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("span", { className: "truncate shrink-0", style: { maxWidth: "40%" }, children: id }), valueText !== undefined && (jsxRuntime.jsx("span", { className: "opacity-60 truncate pr-1", style: { flex: 1, minWidth: 0, maxWidth: "100%" }, children: valueText }))] })), hasAny && (jsxRuntime.jsx(IssueBadge, { level: hasErr ? "error" : "warning", size: 12, className: "shrink-0", title: title }))] }));
2069
2080
  } })] }));
2070
- });
2071
- DefaultNode.displayName = "DefaultNode";
2081
+ }
2072
2082
 
2073
2083
  function DefaultContextMenu({ open, clientPos, onAdd, onClose, }) {
2074
2084
  const { registry } = useWorkbenchContext();
@@ -2227,7 +2237,7 @@ function NodeContextMenu({ open, clientPos, nodeId, onClose, }) {
2227
2237
  }, children: [jsxRuntime.jsxs("div", { className: "px-2 py-1 font-semibold text-gray-700", children: ["Node (", nodeId, ")"] }), jsxRuntime.jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handleDelete, children: "Delete" }), jsxRuntime.jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handleDuplicate, children: "Duplicate" }), canRunPull && (jsxRuntime.jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handleRunPull, children: "Run (pull)" })), jsxRuntime.jsx("div", { className: "h-px bg-gray-200 my-1" }), jsxRuntime.jsx("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: handleCopyId, children: "Copy Node ID" })] }));
2228
2238
  }
2229
2239
 
2230
- const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement }, ref) => {
2240
+ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, getDefaultNodeSize }, ref) => {
2231
2241
  const { wb, registry, inputsMap, outputsMap, valuesTick, nodeStatus, edgeStatus, validationByNode, validationByEdge, } = useWorkbenchContext();
2232
2242
  const nodeValidation = validationByNode;
2233
2243
  const edgeValidation = validationByEdge.errors;
@@ -2330,6 +2340,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement }, r
2330
2340
  edgeValidation,
2331
2341
  selectedNodeIds: new Set(sel.nodes),
2332
2342
  selectedEdgeIds: new Set(sel.edges),
2343
+ getDefaultNodeSize,
2333
2344
  });
2334
2345
  // Retain references for unchanged items
2335
2346
  const stableNodes = retainStabilityById(prevNodesRef.current, out.nodes, isSameNode);
@@ -2456,7 +2467,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement }, r
2456
2467
  const addNodeAt = (typeId, pos) => {
2457
2468
  wb.addNode({ typeId, position: pos });
2458
2469
  };
2459
- return (jsxRuntime.jsx("div", { className: "w-full h-full", onContextMenu: onContextMenu, children: jsxRuntime.jsxs(react.ReactFlow, { nodes: throttled.nodes, edges: throttled.edges, nodeTypes: nodeTypes, onlyRenderVisibleElements: true, selectionOnDrag: true, onConnect: onConnect, onEdgesChange: onEdgesChange, onEdgesDelete: onEdgesDelete, onNodesDelete: onNodesDelete, onNodesChange: onNodesChange, deleteKeyCode: ["Backspace", "Delete"], fitView: true, onInit: (inst) => (rfInstanceRef.current = inst), children: [jsxRuntime.jsx(react.Background, {}), jsxRuntime.jsx(react.MiniMap, {}), jsxRuntime.jsx(react.Controls, {}), jsxRuntime.jsx(DefaultContextMenu, { open: menuOpen, clientPos: menuPos, onAdd: addNodeAt, onClose: () => setMenuOpen(false) }), jsxRuntime.jsx(NodeContextMenu, { open: nodeMenuOpen, clientPos: nodeMenuPos, nodeId: nodeAtMenu, onClose: () => setNodeMenuOpen(false) })] }) }));
2470
+ return (jsxRuntime.jsx("div", { className: "w-full h-full", onContextMenu: onContextMenu, children: jsxRuntime.jsx(react.ReactFlowProvider, { children: jsxRuntime.jsxs(react.ReactFlow, { nodes: throttled.nodes, edges: throttled.edges, nodeTypes: nodeTypes, onlyRenderVisibleElements: true, selectionOnDrag: true, onInit: (inst) => (rfInstanceRef.current = inst), onConnect: onConnect, onEdgesChange: onEdgesChange, onEdgesDelete: onEdgesDelete, onNodesDelete: onNodesDelete, onNodesChange: onNodesChange, deleteKeyCode: ["Backspace", "Delete"], proOptions: { hideAttribution: true }, noDragClassName: "wb-nodrag", noWheelClassName: "wb-nowheel", noPanClassName: "wb-nopan", fitView: true, children: [jsxRuntime.jsx(react.Background, { variant: react.BackgroundVariant.Dots, gap: 12, size: 1 }), jsxRuntime.jsx(react.MiniMap, {}), jsxRuntime.jsx(react.Controls, {}), jsxRuntime.jsx(DefaultContextMenu, { open: menuOpen, clientPos: menuPos, onAdd: addNodeAt, onClose: () => setMenuOpen(false) }), jsxRuntime.jsx(NodeContextMenu, { open: nodeMenuOpen, clientPos: nodeMenuPos, nodeId: nodeAtMenu, onClose: () => setNodeMenuOpen(false) })] }) }) }));
2460
2471
  });
2461
2472
 
2462
2473
  function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, example, onExampleChange, engine, onEngineChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, overrides, onInit, onChange, }) {
@@ -2910,7 +2921,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
2910
2921
  catch (err) {
2911
2922
  alert(String(err?.message ?? err));
2912
2923
  }
2913
- }, disabled: !engine, children: "Start" })), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded px-2 py-1.5", onClick: runAutoLayout, children: "Auto Layout" }), jsxRuntime.jsx("button", { className: "ml-2 border border-gray-300 rounded px-2 py-1.5", onClick: () => canvasRef.current?.fitView?.(), title: "Fit View", children: "Fit View" }), jsxRuntime.jsx("button", { className: "ml-2 border border-gray-300 rounded px-2 py-1.5", onClick: downloadGraph, children: "Download Graph" }), jsxRuntime.jsxs("label", { className: "ml-2 flex items-center gap-1", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: debug, onChange: (e) => onDebugChange(e.target.checked) }), jsxRuntime.jsx("span", { children: "Debug events" })] }), jsxRuntime.jsxs("label", { className: "ml-2 flex items-center gap-1", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: showValues, onChange: (e) => onShowValuesChange(e.target.checked) }), jsxRuntime.jsx("span", { children: "Show values in nodes" })] })] }), jsxRuntime.jsxs("div", { className: "flex flex-1 min-h-0", children: [jsxRuntime.jsx("div", { className: "flex-1 min-w-0", children: jsxRuntime.jsx(WorkbenchCanvas, { ref: canvasRef, showValues: showValues, toString: toString, toElement: toElement }) }), jsxRuntime.jsx(Inspector, { setInput: setInput, debug: debug, autoScroll: autoScroll, hideWorkbench: hideWorkbench, onAutoScrollChange: onAutoScrollChange, onHideWorkbenchChange: onHideWorkbenchChange, toString: toString, toElement: toElement, contextPanel: overrides?.contextPanel })] })] }));
2924
+ }, disabled: !engine, children: "Start" })), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded px-2 py-1.5", onClick: runAutoLayout, children: "Auto Layout" }), jsxRuntime.jsx("button", { className: "ml-2 border border-gray-300 rounded px-2 py-1.5", onClick: () => canvasRef.current?.fitView?.(), title: "Fit View", children: "Fit View" }), jsxRuntime.jsx("button", { className: "ml-2 border border-gray-300 rounded px-2 py-1.5", onClick: downloadGraph, children: "Download Graph" }), jsxRuntime.jsxs("label", { className: "ml-2 flex items-center gap-1", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: debug, onChange: (e) => onDebugChange(e.target.checked) }), jsxRuntime.jsx("span", { children: "Debug events" })] }), jsxRuntime.jsxs("label", { className: "ml-2 flex items-center gap-1", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: showValues, onChange: (e) => onShowValuesChange(e.target.checked) }), jsxRuntime.jsx("span", { children: "Show values in nodes" })] })] }), jsxRuntime.jsxs("div", { className: "flex flex-1 min-h-0", children: [jsxRuntime.jsx("div", { className: "flex-1 min-w-0", 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, toElement: toElement, contextPanel: overrides?.contextPanel })] })] }));
2914
2925
  }
2915
2926
  function WorkbenchStudio({ engine, onEngineChange, example, onExampleChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, autoScroll, onAutoScrollChange, overrides, onInit, onChange, }) {
2916
2927
  const [registry, setRegistry] = React.useState(sparkGraph.createSimpleGraphRegistry());
@@ -2942,6 +2953,8 @@ function WorkbenchStudio({ engine, onEngineChange, example, onExampleChange, bac
2942
2953
 
2943
2954
  exports.AbstractWorkbench = AbstractWorkbench;
2944
2955
  exports.CLIWorkbench = CLIWorkbench;
2956
+ exports.DefaultNode = DefaultNode;
2957
+ exports.DefaultNodeContent = DefaultNodeContent;
2945
2958
  exports.DefaultUIExtensionRegistry = DefaultUIExtensionRegistry;
2946
2959
  exports.InMemoryWorkbench = InMemoryWorkbench;
2947
2960
  exports.Inspector = Inspector;