@bian-womp/spark-workbench 0.2.53 → 0.2.54

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
@@ -114,6 +114,13 @@ class InMemoryWorkbench extends AbstractWorkbench {
114
114
  const { positions } = await this.layout.layout(this.def);
115
115
  this.positions = positions;
116
116
  }
117
+ const defNodeIds = new Set(this.def.nodes.map((n) => n.nodeId));
118
+ const defEdgeIds = new Set(this.def.edges.map((e) => e.id));
119
+ const filteredPositions = Object.fromEntries(Object.entries(this.positions).filter(([id]) => defNodeIds.has(id)));
120
+ const filteredNodes = this.selection.nodes.filter((id) => defNodeIds.has(id));
121
+ const filteredEdges = this.selection.edges.filter((id) => defEdgeIds.has(id));
122
+ this.positions = filteredPositions;
123
+ this.selection = { nodes: filteredNodes, edges: filteredEdges };
117
124
  this.emit("graphChanged", { def: this.def });
118
125
  this.refreshValidation();
119
126
  }
@@ -230,29 +237,32 @@ class InMemoryWorkbench extends AbstractWorkbench {
230
237
  });
231
238
  }
232
239
  // Position and selection APIs for React Flow bridge
233
- setPosition(nodeId, pos) {
240
+ setPosition(nodeId, pos, opts) {
234
241
  this.positions[nodeId] = pos;
235
242
  this.emit("graphUiChanged", {
236
243
  def: this.def,
237
244
  change: { type: "moveNode", nodeId, pos },
245
+ commit: !!opts?.commit === true,
238
246
  });
239
247
  }
240
- setPositions(map) {
248
+ setPositions(map, opts) {
241
249
  this.positions = { ...map };
242
250
  this.emit("graphUiChanged", {
243
251
  def: this.def,
244
252
  change: { type: "moveNodes" },
253
+ commit: opts?.commit,
245
254
  });
246
255
  }
247
256
  getPositions() {
248
257
  return { ...this.positions };
249
258
  }
250
- setSelection(sel) {
259
+ setSelection(sel, opts) {
251
260
  this.selection = { nodes: [...sel.nodes], edges: [...sel.edges] };
252
261
  this.emit("selectionChanged", this.selection);
253
262
  this.emit("graphUiChanged", {
254
263
  def: this.def,
255
264
  change: { type: "selection" },
265
+ commit: opts?.commit,
256
266
  });
257
267
  }
258
268
  getSelection() {
@@ -261,21 +271,31 @@ class InMemoryWorkbench extends AbstractWorkbench {
261
271
  edges: [...this.selection.edges],
262
272
  };
263
273
  }
264
- setViewport(viewport) {
274
+ setViewport(viewport, opts) {
265
275
  this.viewport = { ...viewport };
276
+ this.emit("graphUiChanged", {
277
+ def: this.def,
278
+ change: { type: "viewport" },
279
+ commit: opts?.commit,
280
+ });
266
281
  }
267
282
  getViewport() {
268
283
  return this.viewport ? { ...this.viewport } : null;
269
284
  }
270
285
  getUIState() {
286
+ const defNodeIds = new Set(this.def.nodes.map((n) => n.nodeId));
287
+ const defEdgeIds = new Set(this.def.edges.map((e) => e.id));
288
+ const filteredPositions = Object.fromEntries(Object.entries(this.positions).filter(([id]) => defNodeIds.has(id)));
289
+ const filteredNodes = this.selection.nodes.filter((id) => defNodeIds.has(id));
290
+ const filteredEdges = this.selection.edges.filter((id) => defEdgeIds.has(id));
271
291
  return {
272
- positions: Object.keys(this.positions).length > 0
273
- ? { ...this.positions }
292
+ positions: Object.keys(filteredPositions).length > 0
293
+ ? filteredPositions
274
294
  : undefined,
275
- selection: this.selection.nodes.length > 0 || this.selection.edges.length > 0
295
+ selection: filteredNodes.length > 0 || filteredEdges.length > 0
276
296
  ? {
277
- nodes: [...this.selection.nodes],
278
- edges: [...this.selection.edges],
297
+ nodes: filteredNodes,
298
+ edges: filteredEdges,
279
299
  }
280
300
  : undefined,
281
301
  viewport: this.viewport ? { ...this.viewport } : undefined,
@@ -842,10 +862,13 @@ class RemoteGraphRunner extends AbstractGraphRunner {
842
862
  // Auto-fetch registry on first connection (only once)
843
863
  if (!this.registryFetched && !this.registryFetching) {
844
864
  // Log loading state (UI can listen to transport status for loading indication)
845
- console.info("Loading registry from remote...");
846
- this.fetchRegistry(client).catch((err) => {
865
+ console.info("[RemoteGraphRunner] Loading registry from remote...");
866
+ this.fetchRegistry(client)
867
+ .then(() => {
868
+ console.info("[RemoteGraphRunner] Loaded registry from remote");
869
+ })
870
+ .catch((err) => {
847
871
  console.error("[RemoteGraphRunner] Failed to fetch registry:", err);
848
- // Error handling is done inside fetchRegistry, but we catch unhandled rejections
849
872
  });
850
873
  }
851
874
  // Clear promise on success
@@ -1479,10 +1502,10 @@ function useWorkbenchBridge(wb) {
1479
1502
  });
1480
1503
  }, [wb]);
1481
1504
  const onNodesChange = React.useCallback((changes) => {
1482
- // Apply position updates
1505
+ // Apply position updates continuously, but mark commit only on drag end
1483
1506
  changes.forEach((c) => {
1484
1507
  if (c.type === "position" && c.position) {
1485
- wb.setPosition(c.id, c.position);
1508
+ wb.setPosition(c.id, c.position, { commit: !c.dragging });
1486
1509
  }
1487
1510
  });
1488
1511
  // Derive next node selection from change set
@@ -1956,6 +1979,99 @@ function getHandleClassName(args) {
1956
1979
  return cx("!w-3 !h-3 !bg-white !dark:bg-stone-900", borderColor, kind === "output" && "!rounded-none");
1957
1980
  }
1958
1981
 
1982
+ function generateTimestamp() {
1983
+ const d = new Date();
1984
+ const pad = (n) => String(n).padStart(2, "0");
1985
+ return `${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}`;
1986
+ }
1987
+ function downloadJSON(payload, filename) {
1988
+ const pretty = JSON.stringify(payload, null, 2);
1989
+ const blob = new Blob([pretty], { type: "application/json" });
1990
+ const url = URL.createObjectURL(blob);
1991
+ const a = document.createElement("a");
1992
+ a.href = url;
1993
+ a.download = filename;
1994
+ document.body.appendChild(a);
1995
+ a.click();
1996
+ a.remove();
1997
+ URL.revokeObjectURL(url);
1998
+ }
1999
+ function isSnapshotPayload(parsed) {
2000
+ return (parsed !== null &&
2001
+ typeof parsed === "object" &&
2002
+ ("def" in parsed ||
2003
+ "inputs" in parsed ||
2004
+ "outputs" in parsed ||
2005
+ "environment" in parsed));
2006
+ }
2007
+ async function download(wb, runner) {
2008
+ try {
2009
+ const def = wb.export();
2010
+ const uiState = wb.getUIState();
2011
+ let snapshot;
2012
+ if (runner.isRunning()) {
2013
+ const fullSnapshot = await runner.snapshotFull();
2014
+ snapshot = {
2015
+ ...fullSnapshot,
2016
+ def,
2017
+ extData: {
2018
+ ...(fullSnapshot.extData || {}),
2019
+ ui: uiState,
2020
+ },
2021
+ };
2022
+ }
2023
+ else {
2024
+ const inputs = runner.getInputs(def);
2025
+ snapshot = {
2026
+ def,
2027
+ inputs,
2028
+ outputs: {},
2029
+ environment: {},
2030
+ extData: { ui: uiState },
2031
+ };
2032
+ }
2033
+ downloadJSON(snapshot, `spark-snapshot-${generateTimestamp()}.json`);
2034
+ }
2035
+ catch (err) {
2036
+ const message = err instanceof Error ? err.message : String(err);
2037
+ throw new Error(`Failed to download snapshot: ${message}`);
2038
+ }
2039
+ }
2040
+ async function upload(parsed, wb, runner) {
2041
+ if (!isSnapshotPayload(parsed)) {
2042
+ throw new Error("Invalid snapshot format - expected RuntimeSnapshotFull");
2043
+ }
2044
+ const def = parsed.def;
2045
+ const environment = parsed.environment || {};
2046
+ const inputs = parsed.inputs || {};
2047
+ const outputs = parsed.outputs || {};
2048
+ const extData = parsed.extData || {};
2049
+ if (!def) {
2050
+ throw new Error("Graph definition is empty");
2051
+ }
2052
+ await wb.load(def);
2053
+ if (extData.ui && typeof extData.ui === "object") {
2054
+ wb.setUIState(extData.ui);
2055
+ }
2056
+ if (runner.isRunning()) {
2057
+ await runner.applySnapshotFull({
2058
+ def,
2059
+ environment,
2060
+ inputs,
2061
+ outputs,
2062
+ extData,
2063
+ });
2064
+ }
2065
+ else {
2066
+ runner.build(wb.export());
2067
+ if (inputs && typeof inputs === "object") {
2068
+ for (const [nodeId, map] of Object.entries(inputs)) {
2069
+ runner.setInputs(nodeId, map);
2070
+ }
2071
+ }
2072
+ }
2073
+ }
2074
+
1959
2075
  const WorkbenchContext = React.createContext(null);
1960
2076
  function useWorkbenchContext() {
1961
2077
  const ctx = React.useContext(WorkbenchContext);
@@ -2141,7 +2257,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
2141
2257
  }
2142
2258
  curX += maxWidth + H_GAP;
2143
2259
  }
2144
- wb.setPositions(pos);
2260
+ wb.setPositions(pos, { commit: true });
2145
2261
  }, [wb, registry, overrides?.getDefaultNodeSize]);
2146
2262
  const updateEdgeType = React.useCallback((edgeId, typeId) => wb.updateEdgeType(edgeId, typeId), [wb]);
2147
2263
  const triggerExternal = React.useCallback((nodeId, event) => runner.triggerExternal(nodeId, event), [runner]);
@@ -3663,7 +3779,7 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
3663
3779
  const onMoveEnd = React.useCallback(() => {
3664
3780
  if (rfInstanceRef.current) {
3665
3781
  const viewport = rfInstanceRef.current.getViewport();
3666
- wb.setViewport({ x: viewport.x, y: viewport.y, zoom: viewport.zoom });
3782
+ wb.setViewport({ x: viewport.x, y: viewport.y, zoom: viewport.zoom }, { commit: true });
3667
3783
  }
3668
3784
  }, [wb]);
3669
3785
  const viewportRef = React.useRef(null);
@@ -3830,9 +3946,20 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
3830
3946
  }
3831
3947
  catch { }
3832
3948
  });
3949
+ const off3 = wb.on("graphUiChanged", (evt) => {
3950
+ if (!evt.commit)
3951
+ return;
3952
+ try {
3953
+ const cur = wb.export();
3954
+ const inputs = runner.getInputs(cur);
3955
+ onChange({ def: cur, inputs });
3956
+ }
3957
+ catch { }
3958
+ });
3833
3959
  return () => {
3834
3960
  off1();
3835
3961
  off2();
3962
+ off3();
3836
3963
  };
3837
3964
  }, [wb, runner, onChange]);
3838
3965
  const applyExample = React.useCallback(async (key) => {
@@ -3868,24 +3995,9 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
3868
3995
  setRegistry,
3869
3996
  backendKind,
3870
3997
  ]);
3871
- const downloadGraph = React.useCallback(() => {
3998
+ const download$1 = React.useCallback(async () => {
3872
3999
  try {
3873
- const def = wb.export();
3874
- const inputs = runner.getInputs(def);
3875
- const payload = { def, inputs };
3876
- const pretty = JSON.stringify(payload, null, 2);
3877
- const blob = new Blob([pretty], { type: "application/json" });
3878
- const url = URL.createObjectURL(blob);
3879
- const a = document.createElement("a");
3880
- const d = new Date();
3881
- const pad = (n) => String(n).padStart(2, "0");
3882
- const ts = `${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}`;
3883
- a.href = url;
3884
- a.download = `spark-graph-${ts}.json`;
3885
- document.body.appendChild(a);
3886
- a.click();
3887
- a.remove();
3888
- URL.revokeObjectURL(url);
4000
+ await download(wb, runner);
3889
4001
  }
3890
4002
  catch (err) {
3891
4003
  const message = err instanceof Error ? err.message : String(err);
@@ -3899,51 +4011,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
3899
4011
  return;
3900
4012
  const text = await file.text();
3901
4013
  const parsed = JSON.parse(text);
3902
- // Support both Graph and Snapshot payloads
3903
- const isSnapshot = parsed &&
3904
- typeof parsed === "object" &&
3905
- (parsed.def || parsed.inputs || parsed.outputs || parsed.environment);
3906
- if (isSnapshot) {
3907
- const def = parsed.def;
3908
- const positions = parsed.positions || {};
3909
- const environment = parsed.environment || {};
3910
- const inputs = parsed.inputs || {};
3911
- if (def && runner.isRunning()) {
3912
- // Remote exact restore path
3913
- await runner.applySnapshotFull({
3914
- def,
3915
- environment,
3916
- inputs,
3917
- outputs: parsed.outputs || {},
3918
- });
3919
- await wb.load(def);
3920
- if (positions && typeof positions === "object")
3921
- wb.setPositions(positions);
3922
- }
3923
- else if (!runner.isRunning()) {
3924
- alert("Engine is not running");
3925
- }
3926
- else {
3927
- alert("Graph definition is empty");
3928
- }
3929
- }
3930
- else {
3931
- const def = parsed?.def ?? parsed;
3932
- const inputs = parsed?.inputs ?? {};
3933
- await wb.load(def);
3934
- try {
3935
- runner.build(wb.export());
3936
- }
3937
- catch { }
3938
- if (inputs && typeof inputs === "object") {
3939
- for (const [nodeId, map] of Object.entries(inputs)) {
3940
- try {
3941
- runner.setInputs(nodeId, map);
3942
- }
3943
- catch { }
3944
- }
3945
- }
3946
- }
4014
+ await upload(parsed, wb, runner);
3947
4015
  }
3948
4016
  catch (err) {
3949
4017
  const message = err instanceof Error ? err.message : String(err);
@@ -4199,36 +4267,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
4199
4267
  // Normal change when not running
4200
4268
  onEngineChange?.(kind);
4201
4269
  }
4202
- }, 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" })] }), engineKind === "step" && (jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: () => runner.step(), disabled: !isGraphRunning, title: "Step", children: jsxRuntime.jsx(react$1.PlayPauseIcon, { size: 24 }) })), engineKind === "batched" && (jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: () => runner.flush(), disabled: !isGraphRunning, title: "Flush", children: jsxRuntime.jsx(react$1.LightningIcon, { size: 24 }) })), renderStartStopButton(), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: runAutoLayout, children: jsxRuntime.jsx(react$1.TreeStructureIcon, { size: 24 }) }), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: () => canvasRef.current?.fitView?.(), title: "Fit View", children: jsxRuntime.jsx(react$1.CornersOutIcon, { size: 24 }) }), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: downloadGraph, children: jsxRuntime.jsx(react$1.DownloadSimpleIcon, { size: 24 }) }), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: async () => {
4203
- try {
4204
- const def = wb.export();
4205
- const positions = wb.getPositions();
4206
- const snapshot = await runner.snapshotFull();
4207
- const payload = {
4208
- ...snapshot,
4209
- def,
4210
- positions,
4211
- schemaVersion: 1,
4212
- };
4213
- const pretty = JSON.stringify(payload, null, 2);
4214
- const blob = new Blob([pretty], { type: "application/json" });
4215
- const url = URL.createObjectURL(blob);
4216
- const a = document.createElement("a");
4217
- const d = new Date();
4218
- const pad = (n) => String(n).padStart(2, "0");
4219
- const ts = `${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}`;
4220
- a.href = url;
4221
- a.download = `spark-snapshot-${ts}.json`;
4222
- document.body.appendChild(a);
4223
- a.click();
4224
- a.remove();
4225
- URL.revokeObjectURL(url);
4226
- }
4227
- catch (err) {
4228
- const message = err instanceof Error ? err.message : String(err);
4229
- alert(message);
4230
- }
4231
- }, children: jsxRuntime.jsx(react$1.DownloadIcon, { size: 24 }) }), jsxRuntime.jsx("input", { ref: uploadInputRef, type: "file", accept: "application/json,.json", className: "hidden", onChange: onUploadPicked }), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: triggerUpload, children: jsxRuntime.jsx(react$1.UploadIcon, { size: 24 }) }), jsxRuntime.jsxs("label", { className: "flex items-center gap-1", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: debug, onChange: (e) => onDebugChange(e.target.checked) }), jsxRuntime.jsx(react$1.BugBeetleIcon, { size: 24, weight: debug ? "fill" : undefined })] }), jsxRuntime.jsxs("label", { className: "flex items-center gap-1", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: showValues, onChange: (e) => onShowValuesChange(e.target.checked) }), jsxRuntime.jsx(react$1.ListBulletsIcon, { size: 24, weight: showValues ? "fill" : undefined })] })] }), 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, contextPanel: overrides?.contextPanel })] })] }));
4270
+ }, 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" })] }), engineKind === "step" && (jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: () => runner.step(), disabled: !isGraphRunning, title: "Step", children: jsxRuntime.jsx(react$1.PlayPauseIcon, { size: 24 }) })), engineKind === "batched" && (jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: () => runner.flush(), disabled: !isGraphRunning, title: "Flush", children: jsxRuntime.jsx(react$1.LightningIcon, { size: 24 }) })), renderStartStopButton(), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: runAutoLayout, children: jsxRuntime.jsx(react$1.TreeStructureIcon, { size: 24 }) }), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: () => canvasRef.current?.fitView?.(), title: "Fit View", children: jsxRuntime.jsx(react$1.CornersOutIcon, { size: 24 }) }), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: download$1, children: jsxRuntime.jsx(react$1.DownloadIcon, { size: 24 }) }), jsxRuntime.jsx("input", { ref: uploadInputRef, type: "file", accept: "application/json,.json", className: "hidden", onChange: onUploadPicked }), jsxRuntime.jsx("button", { className: "border border-gray-300 rounded p-1", onClick: triggerUpload, children: jsxRuntime.jsx(react$1.UploadIcon, { size: 24 }) }), jsxRuntime.jsxs("label", { className: "flex items-center gap-1", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: debug, onChange: (e) => onDebugChange(e.target.checked) }), jsxRuntime.jsx(react$1.BugBeetleIcon, { size: 24, weight: debug ? "fill" : undefined })] }), jsxRuntime.jsxs("label", { className: "flex items-center gap-1", children: [jsxRuntime.jsx("input", { type: "checkbox", checked: showValues, onChange: (e) => onShowValuesChange(e.target.checked) }), jsxRuntime.jsx(react$1.ListBulletsIcon, { size: 24, weight: showValues ? "fill" : undefined })] })] }), 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, contextPanel: overrides?.contextPanel })] })] }));
4232
4271
  }
4233
4272
  function WorkbenchStudio({ engine, onEngineChange, example, onExampleChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, autoScroll, onAutoScrollChange, backendOptions, overrides, onInit, onChange, }) {
4234
4273
  const [registry, setRegistry] = React.useState(sparkGraph.createSimpleGraphRegistry());
@@ -4320,6 +4359,7 @@ exports.WorkbenchProvider = WorkbenchProvider;
4320
4359
  exports.WorkbenchStudio = WorkbenchStudio;
4321
4360
  exports.computeEffectiveHandles = computeEffectiveHandles;
4322
4361
  exports.countVisibleHandles = countVisibleHandles;
4362
+ exports.download = download;
4323
4363
  exports.estimateNodeSize = estimateNodeSize;
4324
4364
  exports.formatDataUrlAsLabel = formatDataUrlAsLabel;
4325
4365
  exports.formatDeclaredTypeSignature = formatDeclaredTypeSignature;
@@ -4331,6 +4371,7 @@ exports.prettyHandle = prettyHandle;
4331
4371
  exports.resolveOutputDisplay = resolveOutputDisplay;
4332
4372
  exports.summarizeDeep = summarizeDeep;
4333
4373
  exports.toReactFlow = toReactFlow;
4374
+ exports.upload = upload;
4334
4375
  exports.useQueryParamBoolean = useQueryParamBoolean;
4335
4376
  exports.useQueryParamString = useQueryParamString;
4336
4377
  exports.useThrottledValue = useThrottledValue;