@bian-womp/spark-workbench 0.1.16 → 0.1.18
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 +190 -17
- package/lib/cjs/index.cjs.map +1 -1
- package/lib/cjs/src/index.d.ts +1 -0
- package/lib/cjs/src/index.d.ts.map +1 -1
- package/lib/cjs/src/misc/DebugEvents.d.ts.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 +6 -2
- package/lib/cjs/src/misc/WorkbenchCanvas.d.ts.map +1 -1
- package/lib/cjs/src/misc/WorkbenchStudio.d.ts.map +1 -1
- package/lib/cjs/src/misc/mapping.d.ts +1 -0
- package/lib/cjs/src/misc/mapping.d.ts.map +1 -1
- package/lib/cjs/src/misc/value.d.ts +3 -0
- package/lib/cjs/src/misc/value.d.ts.map +1 -1
- package/lib/esm/index.js +188 -19
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/src/index.d.ts +1 -0
- package/lib/esm/src/index.d.ts.map +1 -1
- package/lib/esm/src/misc/DebugEvents.d.ts.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 +6 -2
- package/lib/esm/src/misc/WorkbenchCanvas.d.ts.map +1 -1
- package/lib/esm/src/misc/WorkbenchStudio.d.ts.map +1 -1
- package/lib/esm/src/misc/mapping.d.ts +1 -0
- package/lib/esm/src/misc/mapping.d.ts.map +1 -1
- package/lib/esm/src/misc/value.d.ts +3 -0
- package/lib/esm/src/misc/value.d.ts.map +1 -1
- package/package.json +4 -4
package/lib/cjs/index.cjs
CHANGED
|
@@ -827,6 +827,90 @@ function formatDeclaredTypeSignature(declared) {
|
|
|
827
827
|
return declared.join(" | ");
|
|
828
828
|
return declared ?? "";
|
|
829
829
|
}
|
|
830
|
+
// Pre-format common structures for display; return undefined to defer to caller
|
|
831
|
+
function preformatValueForDisplay(typeId, value, registry) {
|
|
832
|
+
if (value === undefined || value === null)
|
|
833
|
+
return "";
|
|
834
|
+
// Unwrap typed outputs
|
|
835
|
+
if (sparkGraph.isTypedOutput(value)) {
|
|
836
|
+
return preformatValueForDisplay(String(value.__spark_type), value.__spark_value, registry);
|
|
837
|
+
}
|
|
838
|
+
// Enums
|
|
839
|
+
if (typeId && typeId.includes("enum:") && registry) {
|
|
840
|
+
const n = Number(value);
|
|
841
|
+
const label = registry.enums.get(typeId)?.valueToLabel.get(n);
|
|
842
|
+
if (label)
|
|
843
|
+
return label;
|
|
844
|
+
}
|
|
845
|
+
// Use deep summarization for strings, arrays and nested objects to avoid huge HTML payloads
|
|
846
|
+
const summarized = summarizeDeep(value);
|
|
847
|
+
if (typeof summarized === "string")
|
|
848
|
+
return summarized;
|
|
849
|
+
// Resource-like objects with url/title (after summarization)
|
|
850
|
+
if (summarized && typeof summarized === "object") {
|
|
851
|
+
const urlMaybe = summarized.url;
|
|
852
|
+
if (typeof urlMaybe === "string") {
|
|
853
|
+
const title = summarized.title || "";
|
|
854
|
+
const shortUrl = urlMaybe.length > 32 ? urlMaybe.slice(0, 32) + "…" : urlMaybe;
|
|
855
|
+
return title ? `${title} (${shortUrl})` : shortUrl;
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
return undefined;
|
|
859
|
+
}
|
|
860
|
+
function summarizeDeep(value) {
|
|
861
|
+
// Strings: summarize data URLs and trim extremely long strings
|
|
862
|
+
if (typeof value === "string") {
|
|
863
|
+
if (value.startsWith("data:")) {
|
|
864
|
+
try {
|
|
865
|
+
const semi = value.indexOf(";");
|
|
866
|
+
const comma = value.indexOf(",");
|
|
867
|
+
const mime = value.slice(5, semi > 0 ? semi : undefined).toUpperCase();
|
|
868
|
+
const b64 = comma >= 0 ? value.slice(comma + 1) : "";
|
|
869
|
+
const bytes = Math.floor((b64.length * 3) / 4);
|
|
870
|
+
return `${mime} Data (${bytes} bytes)`;
|
|
871
|
+
}
|
|
872
|
+
catch {
|
|
873
|
+
return value.length > 64 ? value.slice(0, 64) + "…" : value;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
return value.length > 512 ? value.slice(0, 512) + "…" : value;
|
|
877
|
+
}
|
|
878
|
+
// Typed output wrapper
|
|
879
|
+
if (sparkGraph.isTypedOutput(value)) {
|
|
880
|
+
return summarizeDeep(value.__spark_value);
|
|
881
|
+
}
|
|
882
|
+
// Arrays
|
|
883
|
+
if (Array.isArray(value)) {
|
|
884
|
+
return value.map((v) => summarizeDeep(v));
|
|
885
|
+
}
|
|
886
|
+
// Objects
|
|
887
|
+
if (value && typeof value === "object") {
|
|
888
|
+
const obj = value;
|
|
889
|
+
const out = {};
|
|
890
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
891
|
+
// Special-case any 'url' field
|
|
892
|
+
if (typeof v === "string" &&
|
|
893
|
+
k.toLowerCase() === "url" &&
|
|
894
|
+
v.startsWith("data:")) {
|
|
895
|
+
try {
|
|
896
|
+
const semi = v.indexOf(";");
|
|
897
|
+
const comma = v.indexOf(",");
|
|
898
|
+
const mime = v.slice(5, semi > 0 ? semi : undefined).toUpperCase();
|
|
899
|
+
const b64 = comma >= 0 ? v.slice(comma + 1) : "";
|
|
900
|
+
const bytes = Math.floor((b64.length * 3) / 4);
|
|
901
|
+
out[k] = `${mime} Data (${bytes} bytes)`;
|
|
902
|
+
continue;
|
|
903
|
+
}
|
|
904
|
+
catch {
|
|
905
|
+
// fallthrough
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
out[k] = summarizeDeep(v);
|
|
909
|
+
}
|
|
910
|
+
return out;
|
|
911
|
+
}
|
|
912
|
+
return value;
|
|
913
|
+
}
|
|
830
914
|
|
|
831
915
|
function toReactFlow(def, positions, registry, opts) {
|
|
832
916
|
const nodeHandleMap = {};
|
|
@@ -893,6 +977,7 @@ function toReactFlow(def, positions, registry, opts) {
|
|
|
893
977
|
: undefined,
|
|
894
978
|
animated: isRunning,
|
|
895
979
|
style,
|
|
980
|
+
label: e.typeId || undefined,
|
|
896
981
|
};
|
|
897
982
|
});
|
|
898
983
|
return { nodes, edges };
|
|
@@ -1025,8 +1110,8 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, children, }) {
|
|
|
1025
1110
|
layers.push(layer);
|
|
1026
1111
|
q.splice(0, q.length, ...next);
|
|
1027
1112
|
}
|
|
1028
|
-
const X =
|
|
1029
|
-
const Y =
|
|
1113
|
+
const X = 480;
|
|
1114
|
+
const Y = 240;
|
|
1030
1115
|
const pos = {};
|
|
1031
1116
|
layers.forEach((layer, layerIndex) => {
|
|
1032
1117
|
layer.forEach((id, itemIndex) => {
|
|
@@ -1396,7 +1481,8 @@ function DebugEvents({ autoScroll, onAutoScrollChange, hideWorkbench, onHideWork
|
|
|
1396
1481
|
}, [rows, autoScroll]);
|
|
1397
1482
|
const renderPayload = (v) => {
|
|
1398
1483
|
try {
|
|
1399
|
-
|
|
1484
|
+
const summarized = summarizeDeep(v);
|
|
1485
|
+
return JSON.stringify(summarized, null, 0);
|
|
1400
1486
|
}
|
|
1401
1487
|
catch {
|
|
1402
1488
|
return String(v);
|
|
@@ -1408,9 +1494,18 @@ function DebugEvents({ autoScroll, onAutoScrollChange, hideWorkbench, onHideWork
|
|
|
1408
1494
|
function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHideWorkbenchChange, toString, toElement, setInput, }) {
|
|
1409
1495
|
const safeToString = (typeId, value) => {
|
|
1410
1496
|
try {
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1497
|
+
if (typeof toString === "function") {
|
|
1498
|
+
// Special-case data URLs for readability
|
|
1499
|
+
if (typeof value === "string" && value.startsWith("data:image/")) {
|
|
1500
|
+
const comma = value.indexOf(",");
|
|
1501
|
+
const b64 = comma >= 0 ? value.slice(comma + 1) : "";
|
|
1502
|
+
const bytes = Math.floor((b64.length * 3) / 4);
|
|
1503
|
+
const fmt = value.slice(5, value.indexOf(";")) || "image";
|
|
1504
|
+
return `${fmt.toUpperCase()} Data (${bytes} bytes)`;
|
|
1505
|
+
}
|
|
1506
|
+
return toString(typeId, value);
|
|
1507
|
+
}
|
|
1508
|
+
return String(value ?? "");
|
|
1414
1509
|
}
|
|
1415
1510
|
catch {
|
|
1416
1511
|
return String(value ?? "");
|
|
@@ -1521,7 +1616,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
|
|
|
1521
1616
|
const title = inIssues
|
|
1522
1617
|
.map((v) => `${v.code}: ${v.message}`)
|
|
1523
1618
|
.join("; ");
|
|
1524
|
-
return (jsxRuntime.jsxs("div", { className: "flex items-center gap-2 mb-1", children: [jsxRuntime.jsxs("label", { className: "w-
|
|
1619
|
+
return (jsxRuntime.jsxs("div", { className: "flex items-center gap-2 mb-1", children: [jsxRuntime.jsxs("label", { className: "w-32", children: [h, jsxRuntime.jsx("span", { className: "text-gray-500 ml-1 text-[11px]", children: selectedDesc?.inputs?.[h] })] }), hasValidation && (jsxRuntime.jsx(IssueBadge, { level: hasErr ? "error" : "warning", size: 24, className: "ml-1 w-6 h-6", title: title })), isEnum ? (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: current !== undefined && current !== null
|
|
1525
1620
|
? String(current)
|
|
1526
1621
|
: "", onChange: (e) => {
|
|
1527
1622
|
const val = e.target.value;
|
|
@@ -1566,7 +1661,7 @@ const DefaultNode = React.memo(function DefaultNode({ id, data, selected, isConn
|
|
|
1566
1661
|
const ROW_SIZE = 22;
|
|
1567
1662
|
const maxRows = Math.max(inputEntries.length, outputEntries.length);
|
|
1568
1663
|
const minHeight = HEADER_SIZE + maxRows * ROW_SIZE;
|
|
1569
|
-
const minWidth = data.showValues ? 320 :
|
|
1664
|
+
const minWidth = data.showValues ? 320 : 240;
|
|
1570
1665
|
const topFor = (i) => HEADER_SIZE + i * ROW_SIZE + ROW_SIZE / 2;
|
|
1571
1666
|
const hasError = !!status.lastError;
|
|
1572
1667
|
const hasValidationError = validation.issues.some((i) => i.level === "error");
|
|
@@ -1599,7 +1694,13 @@ const DefaultNode = React.memo(function DefaultNode({ id, data, selected, isConn
|
|
|
1599
1694
|
const title = vIssues
|
|
1600
1695
|
.map((v) => `${v.code}: ${v.message}`)
|
|
1601
1696
|
.join("; ");
|
|
1602
|
-
return (jsxRuntime.jsxs(React.Fragment, { children: [jsxRuntime.jsx(ReactFlow.Handle, { id: entry.id, type: "target", position: ReactFlow.Position.Left, isConnectable: isConnectable, className: cx("!w-3 !h-3 !bg-white !dark:bg-stone-900 !border-gray-500 dark:!border-gray-400", hasAny && (hasErr ? "!border-red-500" : "!border-amber-500")), style: { left: -5, top: topFor(i) } }), jsxRuntime.jsxs("div", { className: "absolute left-2 text-[11px] text-gray-700 dark:text-gray-300 pointer-events-none", style: {
|
|
1697
|
+
return (jsxRuntime.jsxs(React.Fragment, { children: [jsxRuntime.jsx(ReactFlow.Handle, { id: entry.id, type: "target", position: ReactFlow.Position.Left, isConnectable: isConnectable, className: cx("!w-3 !h-3 !bg-white !dark:bg-stone-900 !border-gray-500 dark:!border-gray-400", hasAny && (hasErr ? "!border-red-500" : "!border-amber-500")), style: { left: -5, top: topFor(i) } }), jsxRuntime.jsxs("div", { className: "absolute left-2 text-[11px] text-gray-700 dark:text-gray-300 pointer-events-none", style: {
|
|
1698
|
+
top: topFor(i) - 8,
|
|
1699
|
+
right: "50%",
|
|
1700
|
+
whiteSpace: "nowrap",
|
|
1701
|
+
overflow: "hidden",
|
|
1702
|
+
textOverflow: "ellipsis",
|
|
1703
|
+
}, title: `${entry.id}: ${entry.typeId}`, children: [entry.id, hasAny && (jsxRuntime.jsx(IssueBadge, { level: hasErr ? "error" : "warning", size: 12, className: "ml-1", title: title })), showValues && (jsxRuntime.jsx("span", { className: "ml-1 opacity-60", children: toString(entry.typeId, inputValues?.[entry.id]) }))] })] }, `in-${entry.id}`));
|
|
1603
1704
|
}), outputEntries.map((entry, i) => {
|
|
1604
1705
|
const vIssues = validation.outputs.filter((v) => v.handle === entry.id);
|
|
1605
1706
|
const hasAny = vIssues.length > 0;
|
|
@@ -1607,10 +1708,15 @@ const DefaultNode = React.memo(function DefaultNode({ id, data, selected, isConn
|
|
|
1607
1708
|
const title = vIssues
|
|
1608
1709
|
.map((v) => `${v.code}: ${v.message}`)
|
|
1609
1710
|
.join("; ");
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1711
|
+
const resolved = resolveOutputDisplay(outputValues?.[entry.id], entry.typeId);
|
|
1712
|
+
return (jsxRuntime.jsxs(React.Fragment, { children: [jsxRuntime.jsx(ReactFlow.Handle, { id: entry.id, type: "source", position: ReactFlow.Position.Right, isConnectable: isConnectable, className: cx("!w-3 !h-3 !bg-white !dark:bg-stone-900 !border-gray-500 dark:!border-gray-400 !rounded-none", hasAny && (hasErr ? "!border-red-500" : "!border-amber-500")), style: { right: -5, top: topFor(i) } }), jsxRuntime.jsxs("div", { className: "absolute right-2 text-[11px] text-gray-700 dark:text-gray-300 pointer-events-none", style: {
|
|
1713
|
+
top: topFor(i) - 8,
|
|
1714
|
+
textAlign: "right",
|
|
1715
|
+
left: "50%",
|
|
1716
|
+
whiteSpace: "nowrap",
|
|
1717
|
+
overflow: "hidden",
|
|
1718
|
+
textOverflow: "ellipsis",
|
|
1719
|
+
}, title: `${entry.id}: ${entry.typeId}`, children: [entry.id, resolved.typeId && (jsxRuntime.jsxs("span", { className: "ml-1 opacity-60", children: ["(", resolved.typeId, ")"] })), hasAny && (jsxRuntime.jsx(IssueBadge, { level: hasErr ? "error" : "warning", size: 12, className: "ml-1", title: title })), showValues && (jsxRuntime.jsx("span", { className: "ml-1 opacity-60", children: toString(resolved.typeId, resolved.value) }))] })] }, `out-${entry.id}`));
|
|
1614
1720
|
})] }));
|
|
1615
1721
|
});
|
|
1616
1722
|
DefaultNode.displayName = "DefaultNode";
|
|
@@ -1749,11 +1855,21 @@ function NodeContextMenu({ open, clientPos, nodeId, onClose, }) {
|
|
|
1749
1855
|
}, 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" })] }));
|
|
1750
1856
|
}
|
|
1751
1857
|
|
|
1752
|
-
|
|
1858
|
+
const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement }, ref) => {
|
|
1753
1859
|
const { wb, registry, inputsMap, outputsMap, valuesTick, nodeStatus, edgeStatus, validationByNode, validationByEdge, } = useWorkbenchContext();
|
|
1754
1860
|
const ioValues = { inputs: inputsMap, outputs: outputsMap };
|
|
1755
1861
|
const nodeValidation = validationByNode;
|
|
1756
1862
|
const edgeValidation = validationByEdge.errors;
|
|
1863
|
+
// Expose imperative API
|
|
1864
|
+
const rfInstanceRef = React.useRef(null);
|
|
1865
|
+
React.useImperativeHandle(ref, () => ({
|
|
1866
|
+
fitView: () => {
|
|
1867
|
+
try {
|
|
1868
|
+
rfInstanceRef.current?.fitView({ padding: 0.2 });
|
|
1869
|
+
}
|
|
1870
|
+
catch { }
|
|
1871
|
+
},
|
|
1872
|
+
}));
|
|
1757
1873
|
const { onConnect, onNodesChange, onEdgesChange, onEdgesDelete, onNodesDelete, onSelectionChange, } = useWorkbenchBridge(wb);
|
|
1758
1874
|
const { nodeTypes, resolveNodeType } = React.useMemo(() => {
|
|
1759
1875
|
// Build nodeTypes map using UI extension registry
|
|
@@ -1829,8 +1945,8 @@ function WorkbenchCanvas({ showValues, toString, toElement, }) {
|
|
|
1829
1945
|
const addNodeAt = (typeId, pos) => {
|
|
1830
1946
|
wb.addNode({ typeId, position: pos });
|
|
1831
1947
|
};
|
|
1832
|
-
return (jsxRuntime.jsx("div", { className: "w-full h-full", onContextMenu: onContextMenu, children: jsxRuntime.jsxs(ReactFlow, { nodes: nodes, edges: edges, nodeTypes: nodeTypes, selectionOnDrag: true, onConnect: onConnect, onEdgesChange: onEdgesChange, onEdgesDelete: onEdgesDelete, onNodesDelete: onNodesDelete, onNodesChange: onNodesChange, onSelectionChange: onSelectionChange, deleteKeyCode: ["Backspace", "Delete"], fitView: true, children: [jsxRuntime.jsx(ReactFlow.Background, {}), jsxRuntime.jsx(ReactFlow.MiniMap, {}), jsxRuntime.jsx(ReactFlow.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) })] }) }));
|
|
1833
|
-
}
|
|
1948
|
+
return (jsxRuntime.jsx("div", { className: "w-full h-full", onContextMenu: onContextMenu, children: jsxRuntime.jsxs(ReactFlow, { nodes: nodes, edges: edges, nodeTypes: nodeTypes, selectionOnDrag: true, onConnect: onConnect, onEdgesChange: onEdgesChange, onEdgesDelete: onEdgesDelete, onNodesDelete: onNodesDelete, onNodesChange: onNodesChange, onSelectionChange: onSelectionChange, deleteKeyCode: ["Backspace", "Delete"], fitView: true, onInit: (inst) => (rfInstanceRef.current = inst), children: [jsxRuntime.jsx(ReactFlow.Background, {}), jsxRuntime.jsx(ReactFlow.MiniMap, {}), jsxRuntime.jsx(ReactFlow.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) })] }) }));
|
|
1949
|
+
});
|
|
1834
1950
|
|
|
1835
1951
|
function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, example, onExampleChange, engine, onEngineChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, overrides, }) {
|
|
1836
1952
|
const { wb, runner, registry, def, selectedNodeId, runAutoLayout } = useWorkbenchContext();
|
|
@@ -1880,6 +1996,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
1880
1996
|
}, [overrides, defaultExamples]);
|
|
1881
1997
|
const lastAutoLaunched = React.useRef(undefined);
|
|
1882
1998
|
const autoLayoutRan = React.useRef(false);
|
|
1999
|
+
const canvasRef = React.useRef(null);
|
|
1883
2000
|
const applyExample = React.useCallback(async (key) => {
|
|
1884
2001
|
if (runner.isRunning()) {
|
|
1885
2002
|
alert(`Stop engine before switching example.`);
|
|
@@ -1900,6 +2017,27 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
1900
2017
|
setExampleState(key);
|
|
1901
2018
|
onExampleChange?.(key);
|
|
1902
2019
|
}, [runner, wb, onExampleChange, runAutoLayout, examples, setRegistry]);
|
|
2020
|
+
const downloadGraph = React.useCallback(() => {
|
|
2021
|
+
try {
|
|
2022
|
+
const def = wb.export();
|
|
2023
|
+
const pretty = JSON.stringify(def, null, 2);
|
|
2024
|
+
const blob = new Blob([pretty], { type: "application/json" });
|
|
2025
|
+
const url = URL.createObjectURL(blob);
|
|
2026
|
+
const a = document.createElement("a");
|
|
2027
|
+
const d = new Date();
|
|
2028
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
2029
|
+
const ts = `${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}`;
|
|
2030
|
+
a.href = url;
|
|
2031
|
+
a.download = `spark-graph-${ts}.json`;
|
|
2032
|
+
document.body.appendChild(a);
|
|
2033
|
+
a.click();
|
|
2034
|
+
a.remove();
|
|
2035
|
+
URL.revokeObjectURL(url);
|
|
2036
|
+
}
|
|
2037
|
+
catch (err) {
|
|
2038
|
+
alert(String(err?.message ?? err));
|
|
2039
|
+
}
|
|
2040
|
+
}, [wb]);
|
|
1903
2041
|
const hydrateFromBackend = React.useCallback(async (kind, base) => {
|
|
1904
2042
|
try {
|
|
1905
2043
|
const transport = kind === "remote-http"
|
|
@@ -2106,6 +2244,37 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
2106
2244
|
const baseToString = React.useCallback((typeId, value) => {
|
|
2107
2245
|
if (value === undefined || value === null)
|
|
2108
2246
|
return "";
|
|
2247
|
+
// Normalize typed wrapper
|
|
2248
|
+
if (sparkGraph.isTypedOutput(value)) {
|
|
2249
|
+
return baseToString(String(value.__spark_type), value.__spark_value);
|
|
2250
|
+
}
|
|
2251
|
+
const pre = preformatValueForDisplay(typeId, value, registry);
|
|
2252
|
+
if (pre !== undefined)
|
|
2253
|
+
return pre;
|
|
2254
|
+
if (typeof value === "object" &&
|
|
2255
|
+
value !== null &&
|
|
2256
|
+
typeof value.url === "string") {
|
|
2257
|
+
const title = value.title || "";
|
|
2258
|
+
const url = String(value.url || "");
|
|
2259
|
+
if (url.startsWith("data:image/")) {
|
|
2260
|
+
try {
|
|
2261
|
+
const semi = url.indexOf(";");
|
|
2262
|
+
const comma = url.indexOf(",");
|
|
2263
|
+
const mime = url
|
|
2264
|
+
.slice(5, semi > 0 ? semi : undefined)
|
|
2265
|
+
.toUpperCase();
|
|
2266
|
+
const b64 = comma >= 0 ? url.slice(comma + 1) : "";
|
|
2267
|
+
const bytes = Math.floor((b64.length * 3) / 4);
|
|
2268
|
+
return title
|
|
2269
|
+
? `${title} (${mime} ${bytes} bytes)`
|
|
2270
|
+
: `${mime} Data (${bytes} bytes)`;
|
|
2271
|
+
}
|
|
2272
|
+
catch {
|
|
2273
|
+
return title || url.slice(0, 32) + (url.length > 32 ? "…" : "");
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
return title || url.slice(0, 32) + (url.length > 32 ? "…" : "");
|
|
2277
|
+
}
|
|
2109
2278
|
if (typeId && typeId.includes("enum:")) {
|
|
2110
2279
|
const n = Number(value);
|
|
2111
2280
|
const label = registry.enums.get(typeId)?.valueToLabel.get(n);
|
|
@@ -2167,7 +2336,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
2167
2336
|
catch (err) {
|
|
2168
2337
|
alert(String(err?.message ?? err));
|
|
2169
2338
|
}
|
|
2170
|
-
}, disabled: !engine, children: "Start" })), jsxRuntime.jsx("button", { onClick: runAutoLayout, children: "Auto Layout" }), 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, { 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 })] })] }));
|
|
2339
|
+
}, disabled: !engine, children: "Start" })), jsxRuntime.jsx("button", { onClick: runAutoLayout, children: "Auto Layout" }), jsxRuntime.jsx("button", { className: "ml-2", onClick: () => canvasRef.current?.fitView?.(), title: "Fit View", children: "Fit View" }), jsxRuntime.jsx("button", { className: "ml-2", 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 })] })] }));
|
|
2171
2340
|
}
|
|
2172
2341
|
function WorkbenchStudio({ engine, onEngineChange, example, onExampleChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, autoScroll, onAutoScrollChange, overrides, }) {
|
|
2173
2342
|
const [registry, setRegistry] = React.useState(sparkGraph.createSimpleGraphRegistry());
|
|
@@ -2203,7 +2372,11 @@ exports.WorkbenchCanvas = WorkbenchCanvas;
|
|
|
2203
2372
|
exports.WorkbenchContext = WorkbenchContext;
|
|
2204
2373
|
exports.WorkbenchProvider = WorkbenchProvider;
|
|
2205
2374
|
exports.WorkbenchStudio = WorkbenchStudio;
|
|
2375
|
+
exports.formatDeclaredTypeSignature = formatDeclaredTypeSignature;
|
|
2206
2376
|
exports.getNodeBorderClassNames = getNodeBorderClassNames;
|
|
2377
|
+
exports.preformatValueForDisplay = preformatValueForDisplay;
|
|
2378
|
+
exports.resolveOutputDisplay = resolveOutputDisplay;
|
|
2379
|
+
exports.summarizeDeep = summarizeDeep;
|
|
2207
2380
|
exports.toReactFlow = toReactFlow;
|
|
2208
2381
|
exports.useQueryParamBoolean = useQueryParamBoolean;
|
|
2209
2382
|
exports.useQueryParamString = useQueryParamString;
|