@bian-womp/spark-workbench 0.2.9 → 0.2.10

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
@@ -169,6 +169,7 @@ class InMemoryWorkbench extends AbstractWorkbench {
169
169
  change: { type: "addNode", nodeId: id },
170
170
  });
171
171
  this.refreshValidation();
172
+ return id;
172
173
  }
173
174
  removeNode(nodeId) {
174
175
  this.def.nodes = this.def.nodes.filter((n) => n.nodeId !== nodeId);
@@ -193,6 +194,7 @@ class InMemoryWorkbench extends AbstractWorkbench {
193
194
  change: { type: "connect", edgeId: id },
194
195
  });
195
196
  this.refreshValidation();
197
+ return id;
196
198
  }
197
199
  disconnect(edgeId) {
198
200
  this.def.edges = this.def.edges.filter((e) => e.id !== edgeId);
@@ -2083,7 +2085,7 @@ function DefaultNodeHeader({ id, title, status, validation, right, onInvalidate,
2083
2085
  .join("; ") })), jsxRuntime.jsxs("span", { className: "text-[10px] opacity-70", children: ["(", id, ")"] })] })] }));
2084
2086
  }
2085
2087
  function DefaultNodeContent({ id, data, isConnectable, }) {
2086
- const ctx = useWorkbenchContext();
2088
+ useWorkbenchContext();
2087
2089
  const { showValues, inputValues, outputValues, toString } = data;
2088
2090
  const inputEntries = data.inputHandles ?? [];
2089
2091
  const outputEntries = data.outputHandles ?? [];
@@ -2093,98 +2095,6 @@ function DefaultNodeContent({ id, data, isConnectable, }) {
2093
2095
  outputs: []};
2094
2096
  const isRunning = !!status.activeRuns;
2095
2097
  const pct = Math.round(Math.max(0, Math.min(1, Number(status.progress) || 0)) * 100);
2096
- const handleBake = React.useCallback(async (handleId) => {
2097
- try {
2098
- const typeId = ctx.outputTypesMap?.[id]?.[handleId];
2099
- const rawValue = ctx.outputsMap?.[id]?.[handleId];
2100
- if (!typeId || rawValue === undefined)
2101
- return;
2102
- const unwrap = (v) => sparkGraph.isTypedOutput(v) ? sparkGraph.getTypedOutputValue(v) : v;
2103
- const clone = (v) => typeof structuredClone === "function"
2104
- ? structuredClone(v)
2105
- : JSON.parse(JSON.stringify(v));
2106
- const coerceIfNeeded = async (fromType, toType, value) => {
2107
- if (!toType || toType === fromType || !ctx.runner?.coerce)
2108
- return value;
2109
- try {
2110
- return await ctx.runner.coerce(fromType, toType, value);
2111
- }
2112
- catch {
2113
- return value;
2114
- }
2115
- };
2116
- const positions = ctx.wb.getPositions();
2117
- const pos = positions[id] || { x: 0, y: 0 };
2118
- const isArray = typeId.endsWith("[]");
2119
- const baseTypeId = isArray ? typeId.slice(0, -2) : typeId;
2120
- const tArr = isArray ? ctx.registry?.types.get(typeId) : undefined;
2121
- const tElem = ctx.registry?.types.get(baseTypeId);
2122
- const singleTarget = !isArray ? tElem?.bakeTarget : undefined;
2123
- const arrTarget = isArray ? tArr?.bakeTarget : undefined;
2124
- const elemTarget = isArray ? tElem?.bakeTarget : undefined;
2125
- const makeTargetInfo = (bt) => {
2126
- if (!bt)
2127
- return undefined;
2128
- const node = ctx.registry?.nodes.get(String(bt.nodeTypeId));
2129
- const inType = sparkGraph.getInputTypeId(node?.inputs, String(bt.inputHandle || "Value"));
2130
- return { bt, inType };
2131
- };
2132
- // Plan and execute
2133
- if (singleTarget) {
2134
- const info = makeTargetInfo(singleTarget);
2135
- const v = unwrap(rawValue);
2136
- const coerced = await coerceIfNeeded(typeId, info?.inType, v);
2137
- ctx.wb.addNode({
2138
- nodeId: undefined,
2139
- typeId: String(singleTarget.nodeTypeId),
2140
- position: { x: pos.x + 180, y: pos.y },
2141
- params: {},
2142
- initialInputs: {
2143
- [String(singleTarget.inputHandle || "Value")]: clone(coerced),
2144
- },
2145
- });
2146
- return;
2147
- }
2148
- if (isArray && arrTarget) {
2149
- const info = makeTargetInfo(arrTarget);
2150
- const v = unwrap(rawValue);
2151
- const coerced = await coerceIfNeeded(typeId, info?.inType, v);
2152
- ctx.wb.addNode({
2153
- nodeId: undefined,
2154
- typeId: String(arrTarget.nodeTypeId),
2155
- position: { x: pos.x + 180, y: pos.y },
2156
- params: {},
2157
- initialInputs: {
2158
- [String(arrTarget.inputHandle || "Value")]: clone(coerced),
2159
- },
2160
- });
2161
- return;
2162
- }
2163
- if (isArray && elemTarget && Array.isArray(rawValue)) {
2164
- const info = makeTargetInfo(elemTarget);
2165
- const items = rawValue.map(unwrap);
2166
- const coercedItems = await Promise.all(items.map((v) => coerceIfNeeded(baseTypeId, info?.inType, v)));
2167
- const COLS = 4;
2168
- const DX = 180;
2169
- const DY = 160;
2170
- coercedItems.forEach((cv, idx) => {
2171
- const col = idx % COLS;
2172
- const row = Math.floor(idx / COLS);
2173
- ctx.wb.addNode({
2174
- nodeId: undefined,
2175
- typeId: String(elemTarget.nodeTypeId),
2176
- position: { x: pos.x + (col + 1) * DX, y: pos.y + row * DY },
2177
- params: {},
2178
- initialInputs: {
2179
- [String(elemTarget.inputHandle || "Value")]: clone(cv),
2180
- },
2181
- });
2182
- });
2183
- return;
2184
- }
2185
- }
2186
- catch { }
2187
- }, [ctx, id]);
2188
2098
  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 }) => {
2189
2099
  const vIssues = (kind === "input" ? validation.inputs : validation.outputs).filter((v) => v.handle === id);
2190
2100
  const hasAny = vIssues.length > 0;
@@ -2212,26 +2122,7 @@ function DefaultNodeContent({ id, data, isConnectable, }) {
2212
2122
  const txt = toString(resolved.typeId, resolved.value);
2213
2123
  return typeof txt === "string" ? txt : String(txt);
2214
2124
  })();
2215
- const tId = ctx.outputTypesMap?.[id]?.[handleId];
2216
- let canBake = false;
2217
- if (tId?.endsWith("[]")) {
2218
- const base = tId.slice(0, -2);
2219
- const tArr = ctx.registry?.types.get(tId);
2220
- const tElem = ctx.registry?.types.get(base);
2221
- const arrTarget = tArr?.bakeTarget;
2222
- const elemTarget = tElem?.bakeTarget;
2223
- canBake = !!((arrTarget && ctx.registry?.nodes?.has?.(arrTarget.nodeTypeId)) ||
2224
- (elemTarget && ctx.registry?.nodes?.has?.(elemTarget.nodeTypeId)));
2225
- }
2226
- else if (tId) {
2227
- const t = ctx.registry?.types.get(tId);
2228
- const target = t?.bakeTarget;
2229
- canBake = !!(target && ctx.registry?.nodes?.has?.(target.nodeTypeId));
2230
- }
2231
- return (jsxRuntime.jsxs("span", { className: "flex items-center gap-1 w-full", children: [kind === "output" ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [canBake && (jsxRuntime.jsx("button", { onClick: (e) => {
2232
- e.stopPropagation();
2233
- handleBake(handleId);
2234
- }, title: "Bake value", className: "pointer-events-auto border border-gray-300 rounded px-1 py-0.5 text-[10px] bg-white/80 hover:bg-white mr-2", children: "Bake" })), 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: handleId })] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("span", { className: "truncate shrink-0", style: { maxWidth: "40%" }, children: handleId }), 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 }))] }));
2125
+ 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: handleId })] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("span", { className: "truncate shrink-0", style: { maxWidth: "40%" }, children: handleId }), 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 }))] }));
2235
2126
  } })] }));
2236
2127
  }
2237
2128
 
@@ -2322,7 +2213,7 @@ function DefaultContextMenu({ open, clientPos, onAdd, onClose, }) {
2322
2213
  }
2323
2214
 
2324
2215
  function NodeContextMenu({ open, clientPos, nodeId, onClose, }) {
2325
- const { wb, runner, engineKind } = useWorkbenchContext();
2216
+ const { wb, runner, engineKind, registry, outputsMap, outputTypesMap } = useWorkbenchContext();
2326
2217
  const ref = React.useRef(null);
2327
2218
  // outside click + ESC
2328
2219
  React.useEffect(() => {
@@ -2357,39 +2248,181 @@ function NodeContextMenu({ open, clientPos, nodeId, onClose, }) {
2357
2248
  const x = Math.min(clientPos.x, (typeof window !== "undefined" ? window.innerWidth : 0) -
2358
2249
  (MENU_MIN_WIDTH + PADDING));
2359
2250
  const y = Math.min(clientPos.y, (typeof window !== "undefined" ? window.innerHeight : 0) - 240);
2251
+ // Bake helpers
2252
+ const getBakeableOutputs = () => {
2253
+ try {
2254
+ const def = wb.export();
2255
+ const node = def.nodes.find((n) => n.nodeId === nodeId);
2256
+ if (!node)
2257
+ return [];
2258
+ const desc = registry.nodes.get(node.typeId);
2259
+ const handles = Object.keys(desc?.outputs || {});
2260
+ const out = [];
2261
+ for (const h of handles) {
2262
+ const tId = outputTypesMap?.[nodeId]?.[h];
2263
+ if (!tId)
2264
+ continue;
2265
+ if (tId.endsWith("[]")) {
2266
+ const base = tId.slice(0, -2);
2267
+ const tArr = registry.types.get(tId);
2268
+ const tElem = registry.types.get(base);
2269
+ const arrT = tArr?.bakeTarget;
2270
+ const elemT = tElem?.bakeTarget;
2271
+ if ((arrT && registry.nodes.has(arrT.nodeTypeId)) ||
2272
+ (elemT && registry.nodes.has(elemT.nodeTypeId)))
2273
+ out.push(h);
2274
+ }
2275
+ else {
2276
+ const t = registry.types.get(tId);
2277
+ const bt = t?.bakeTarget;
2278
+ if (bt && registry.nodes.has(bt.nodeTypeId))
2279
+ out.push(h);
2280
+ }
2281
+ }
2282
+ return out;
2283
+ }
2284
+ catch {
2285
+ return [];
2286
+ }
2287
+ };
2288
+ const doBake = async (handleId) => {
2289
+ try {
2290
+ const typeId = outputTypesMap?.[nodeId]?.[handleId];
2291
+ const raw = outputsMap?.[nodeId]?.[handleId];
2292
+ if (!typeId || raw === undefined)
2293
+ return;
2294
+ const unwrap = (v) => sparkGraph.isTypedOutput(v) ? sparkGraph.getTypedOutputValue(v) : v;
2295
+ const clone = (v) => typeof structuredClone === "function"
2296
+ ? structuredClone(v)
2297
+ : JSON.parse(JSON.stringify(v));
2298
+ const coerceIfNeeded = async (fromType, toType, value) => {
2299
+ if (!toType || toType === fromType || !runner?.coerce)
2300
+ return value;
2301
+ try {
2302
+ return await runner.coerce(fromType, toType, value);
2303
+ }
2304
+ catch {
2305
+ return value;
2306
+ }
2307
+ };
2308
+ const pos = wb.getPositions?.()[nodeId] || { x: 0, y: 0 };
2309
+ const isArray = typeId.endsWith("[]");
2310
+ const baseTypeId = isArray ? typeId.slice(0, -2) : typeId;
2311
+ const tArr = isArray ? registry.types.get(typeId) : undefined;
2312
+ const tElem = registry.types.get(baseTypeId);
2313
+ const singleTarget = !isArray ? tElem?.bakeTarget : undefined;
2314
+ const arrTarget = isArray ? tArr?.bakeTarget : undefined;
2315
+ const elemTarget = isArray ? tElem?.bakeTarget : undefined;
2316
+ if (singleTarget) {
2317
+ const nodeDesc = registry.nodes.get(String(singleTarget.nodeTypeId));
2318
+ const inType = sparkGraph.getInputTypeId(nodeDesc?.inputs, String(singleTarget.inputHandle || "Value"));
2319
+ const coerced = await coerceIfNeeded(typeId, inType, unwrap(raw));
2320
+ const newId = wb.addNode({
2321
+ typeId: String(singleTarget.nodeTypeId),
2322
+ position: { x: pos.x + 180, y: pos.y },
2323
+ params: {},
2324
+ });
2325
+ runner.update(wb.export());
2326
+ await runner.whenIdle();
2327
+ runner.setInputs(newId, {
2328
+ [String(singleTarget.inputHandle || "Value")]: coerced,
2329
+ });
2330
+ return;
2331
+ }
2332
+ if (isArray && arrTarget) {
2333
+ const nodeDesc = registry.nodes.get(String(arrTarget.nodeTypeId));
2334
+ const inType = sparkGraph.getInputTypeId(nodeDesc?.inputs, String(arrTarget.inputHandle || "Value"));
2335
+ const coerced = await coerceIfNeeded(typeId, inType, unwrap(raw));
2336
+ const newId = `n${Math.random().toString(36).slice(2, 8)}`;
2337
+ wb.addNode({
2338
+ nodeId: newId,
2339
+ typeId: String(arrTarget.nodeTypeId),
2340
+ position: { x: pos.x + 180, y: pos.y },
2341
+ params: {},
2342
+ });
2343
+ runner.update(wb.export());
2344
+ await runner.whenIdle();
2345
+ runner.setInputs(newId, {
2346
+ [String(arrTarget.inputHandle || "Value")]: coerced,
2347
+ });
2348
+ return;
2349
+ }
2350
+ if (isArray && elemTarget && Array.isArray(raw)) {
2351
+ const nodeDesc = registry.nodes.get(String(elemTarget.nodeTypeId));
2352
+ const inType = sparkGraph.getInputTypeId(nodeDesc?.inputs, String(elemTarget.inputHandle || "Value"));
2353
+ const items = raw.map(unwrap);
2354
+ const coercedItems = await Promise.all(items.map((v) => coerceIfNeeded(baseTypeId, inType, v)));
2355
+ const COLS = 4;
2356
+ const DX = 180;
2357
+ const DY = 160;
2358
+ for (let idx = 0; idx < coercedItems.length; idx++) {
2359
+ const cv = coercedItems[idx];
2360
+ const col = idx % COLS;
2361
+ const row = Math.floor(idx / COLS);
2362
+ const newId = wb.addNode({
2363
+ typeId: String(elemTarget.nodeTypeId),
2364
+ position: { x: pos.x + (col + 1) * DX, y: pos.y + row * DY },
2365
+ params: {},
2366
+ initialInputs: {
2367
+ [String(elemTarget.inputHandle || "Value")]: clone(cv),
2368
+ },
2369
+ });
2370
+ runner.update(wb.export());
2371
+ await runner.whenIdle();
2372
+ runner.setInputs(newId, {
2373
+ [String(elemTarget.inputHandle || "Value")]: cv,
2374
+ });
2375
+ }
2376
+ return;
2377
+ }
2378
+ }
2379
+ catch { }
2380
+ };
2360
2381
  // actions
2361
- const handleDelete = () => {
2382
+ const handleDelete = React.useCallback(() => {
2362
2383
  wb.removeNode(nodeId);
2363
2384
  onClose();
2364
- };
2365
- const handleDuplicate = () => {
2385
+ }, [nodeId, wb, onClose]);
2386
+ const handleDuplicate = React.useCallback(() => {
2366
2387
  const def = wb.export();
2367
2388
  const n = def.nodes.find((n) => n.nodeId === nodeId);
2368
2389
  if (!n)
2369
2390
  return onClose();
2370
2391
  const pos = wb.getPositions?.()[nodeId] || { x: 0, y: 0 };
2371
- wb.addNode({ typeId: n.typeId, params: n.params, position: { x: pos.x + 24, y: pos.y + 24 } });
2392
+ wb.addNode({
2393
+ typeId: n.typeId,
2394
+ params: n.params,
2395
+ position: { x: pos.x + 24, y: pos.y + 24 },
2396
+ });
2372
2397
  onClose();
2373
- };
2374
- const handleCopyId = async () => {
2398
+ }, [nodeId, wb, onClose]);
2399
+ React.useCallback(async (handleId) => {
2400
+ await doBake(handleId);
2401
+ onClose();
2402
+ }, [doBake, onClose]);
2403
+ const handleCopyId = React.useCallback(async () => {
2375
2404
  try {
2376
2405
  await navigator.clipboard.writeText(nodeId);
2377
2406
  }
2378
2407
  catch { }
2379
2408
  onClose();
2380
- };
2381
- const canRunPull = engineKind()?.toString() === "pull";
2382
- const handleRunPull = async () => {
2409
+ }, [nodeId, onClose]);
2410
+ const handleRunPull = React.useCallback(async () => {
2383
2411
  try {
2384
2412
  await runner.computeNode(nodeId);
2385
2413
  }
2386
2414
  catch { }
2387
2415
  onClose();
2388
- };
2416
+ }, [nodeId, runner, onClose]);
2417
+ const canRunPull = engineKind()?.toString() === "pull";
2418
+ const outs = getBakeableOutputs();
2389
2419
  return (jsxRuntime.jsxs("div", { ref: ref, tabIndex: -1, className: "fixed z-[1000] bg-white border border-gray-300 rounded-lg shadow-lg p-1 min-w-[180px] text-sm text-gray-700", style: { left: x, top: y }, onClick: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), onWheel: (e) => e.stopPropagation(), onContextMenu: (e) => {
2390
2420
  e.preventDefault();
2391
2421
  e.stopPropagation();
2392
- }, 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" })] }));
2422
+ }, 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" }), outs.length > 0 && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: "px-2 py-1 font-semibold text-gray-700", children: "Bake" }), outs.map((h) => (jsxRuntime.jsxs("button", { className: "block w-full text-left px-2 py-1 hover:bg-gray-100", onClick: async () => {
2423
+ await doBake(h);
2424
+ onClose();
2425
+ }, children: ["Bake: ", h] }, h))), 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" })] }));
2393
2426
  }
2394
2427
 
2395
2428
  const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, getDefaultNodeSize }, ref) => {
@@ -2619,10 +2652,21 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
2619
2652
  setNodeMenuOpen(false);
2620
2653
  }
2621
2654
  };
2622
- const addNodeAt = (typeId, pos) => {
2655
+ const addNodeAt = React.useCallback((typeId, pos) => {
2623
2656
  wb.addNode({ typeId, position: pos });
2624
- };
2625
- 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, 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, { id: "workbench-canvas-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) })] }) }) }));
2657
+ }, [wb]);
2658
+ React.useCallback((inst) => {
2659
+ rfInstanceRef.current = inst;
2660
+ }, []);
2661
+ const onCloseMenu = React.useCallback(() => {
2662
+ setMenuOpen(false);
2663
+ }, []);
2664
+ const onCloseNodeMenu = React.useCallback(() => {
2665
+ setNodeMenuOpen(false);
2666
+ }, []);
2667
+ 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, selectionOnDrag: true, onInit: (inst) => {
2668
+ rfInstanceRef.current = inst;
2669
+ }, 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, { id: "workbench-canvas-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: onCloseMenu }), jsxRuntime.jsx(NodeContextMenu, { open: nodeMenuOpen, clientPos: nodeMenuPos, nodeId: nodeAtMenu, onClose: onCloseNodeMenu })] }) }) }));
2626
2670
  });
2627
2671
 
2628
2672
  function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, example, onExampleChange, engine, onEngineChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, overrides, onInit, onChange, }) {