@bian-womp/spark-workbench 0.2.8 → 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.
Files changed (39) hide show
  1. package/lib/cjs/index.cjs +231 -29
  2. package/lib/cjs/index.cjs.map +1 -1
  3. package/lib/cjs/src/core/AbstractWorkbench.d.ts +2 -2
  4. package/lib/cjs/src/core/AbstractWorkbench.d.ts.map +1 -1
  5. package/lib/cjs/src/core/InMemoryWorkbench.d.ts +2 -2
  6. package/lib/cjs/src/core/InMemoryWorkbench.d.ts.map +1 -1
  7. package/lib/cjs/src/core/contracts.d.ts +2 -2
  8. package/lib/cjs/src/core/contracts.d.ts.map +1 -1
  9. package/lib/cjs/src/misc/DefaultNode.d.ts +18 -1
  10. package/lib/cjs/src/misc/DefaultNode.d.ts.map +1 -1
  11. package/lib/cjs/src/misc/NodeContextMenu.d.ts.map +1 -1
  12. package/lib/cjs/src/misc/WorkbenchCanvas.d.ts.map +1 -1
  13. package/lib/cjs/src/misc/WorkbenchStudio.d.ts.map +1 -1
  14. package/lib/cjs/src/misc/mapping.d.ts +1 -0
  15. package/lib/cjs/src/misc/mapping.d.ts.map +1 -1
  16. package/lib/cjs/src/runtime/IGraphRunner.d.ts +1 -0
  17. package/lib/cjs/src/runtime/IGraphRunner.d.ts.map +1 -1
  18. package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts +1 -0
  19. package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
  20. package/lib/esm/index.js +231 -30
  21. package/lib/esm/index.js.map +1 -1
  22. package/lib/esm/src/core/AbstractWorkbench.d.ts +2 -2
  23. package/lib/esm/src/core/AbstractWorkbench.d.ts.map +1 -1
  24. package/lib/esm/src/core/InMemoryWorkbench.d.ts +2 -2
  25. package/lib/esm/src/core/InMemoryWorkbench.d.ts.map +1 -1
  26. package/lib/esm/src/core/contracts.d.ts +2 -2
  27. package/lib/esm/src/core/contracts.d.ts.map +1 -1
  28. package/lib/esm/src/misc/DefaultNode.d.ts +18 -1
  29. package/lib/esm/src/misc/DefaultNode.d.ts.map +1 -1
  30. package/lib/esm/src/misc/NodeContextMenu.d.ts.map +1 -1
  31. package/lib/esm/src/misc/WorkbenchCanvas.d.ts.map +1 -1
  32. package/lib/esm/src/misc/WorkbenchStudio.d.ts.map +1 -1
  33. package/lib/esm/src/misc/mapping.d.ts +1 -0
  34. package/lib/esm/src/misc/mapping.d.ts.map +1 -1
  35. package/lib/esm/src/runtime/IGraphRunner.d.ts +1 -0
  36. package/lib/esm/src/runtime/IGraphRunner.d.ts.map +1 -1
  37. package/lib/esm/src/runtime/RemoteGraphRunner.d.ts +1 -0
  38. package/lib/esm/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
  39. package/package.json +4 -4
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);
@@ -548,6 +550,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
548
550
  displayName: d.displayName,
549
551
  options: d.options,
550
552
  opts: d.opts,
553
+ bakeTarget: d.bakeTarget,
551
554
  });
552
555
  }
553
556
  else if (d.kind === "register-type") {
@@ -556,6 +559,7 @@ class RemoteGraphRunner extends AbstractGraphRunner {
556
559
  id: d.id,
557
560
  displayName: d.displayName,
558
561
  validate: (_v) => true,
562
+ bakeTarget: d.bakeTarget,
559
563
  });
560
564
  }
561
565
  }
@@ -678,6 +682,15 @@ class RemoteGraphRunner extends AbstractGraphRunner {
678
682
  catch { }
679
683
  });
680
684
  }
685
+ async coerce(from, to, value) {
686
+ const runner = await this.ensureRemoteRunner();
687
+ try {
688
+ return await runner.coerce(from, to, value);
689
+ }
690
+ catch {
691
+ return value;
692
+ }
693
+ }
681
694
  getOutputs(def) {
682
695
  const out = {};
683
696
  const cache = this.valueCache;
@@ -1108,6 +1121,15 @@ function toReactFlow(def, positions, registry, opts) {
1108
1121
  const EDGE_STYLE_ERROR = { stroke: "#ef4444", strokeWidth: 2 };
1109
1122
  const EDGE_STYLE_RUNNING = { stroke: "#3b82f6" };
1110
1123
  const nodeHandleMap = {};
1124
+ // Precompute which inputs are connected per node
1125
+ const connectedInputs = {};
1126
+ for (const e of def.edges) {
1127
+ const nid = e.target.nodeId;
1128
+ const hid = e.target.handle;
1129
+ if (!connectedInputs[nid])
1130
+ connectedInputs[nid] = new Set();
1131
+ connectedInputs[nid].add(hid);
1132
+ }
1111
1133
  const nodes = def.nodes.map((n) => {
1112
1134
  const desc = registry.nodes.get(n.typeId);
1113
1135
  const inputHandles = Object.entries(desc?.inputs ?? {})
@@ -1156,6 +1178,10 @@ function toReactFlow(def, positions, registry, opts) {
1156
1178
  params: n.params,
1157
1179
  inputHandles,
1158
1180
  outputHandles,
1181
+ inputConnected: Object.fromEntries(inputHandles.map((h) => [
1182
+ h.id,
1183
+ !!connectedInputs[n.nodeId]?.has(h.id),
1184
+ ])),
1159
1185
  handleLayout: [
1160
1186
  ...inputHandles.map((h, i) => ({
1161
1187
  id: h.id,
@@ -2029,17 +2055,37 @@ const DefaultNode = React.memo(function DefaultNode({ id, data, selected, isConn
2029
2055
  position: "relative",
2030
2056
  minWidth: typeof data.renderWidth === "number" ? data.renderWidth : undefined,
2031
2057
  minHeight: typeof data.renderHeight === "number" ? data.renderHeight : undefined,
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: {
2033
- maxHeight: NODE_HEADER_HEIGHT_PX,
2034
- minHeight: NODE_HEADER_HEIGHT_PX,
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")
2036
- ? "error"
2037
- : "warning", size: 12, className: "w-3 h-3", title: validation.issues
2038
- .map((v) => `${v.code}: ${v.message}`)
2039
- .join("; ") })), jsxRuntime.jsxs("span", { className: "text-[10px] opacity-70", children: ["(", id, ")"] })] })] }), jsxRuntime.jsx(DefaultNodeContent, { data: data, isConnectable: isConnectable })] }));
2058
+ }, children: [jsxRuntime.jsx(DefaultNodeHeader, { id: id, title: typeId, status: status, validation: validation }), jsxRuntime.jsx(DefaultNodeContent, { id: id, data: data, isConnectable: isConnectable })] }));
2040
2059
  });
2041
2060
  DefaultNode.displayName = "DefaultNode";
2042
- function DefaultNodeContent({ data, isConnectable, }) {
2061
+ function DefaultNodeHeader({ id, title, status, validation, right, onInvalidate, }) {
2062
+ const ctx = useWorkbenchContext();
2063
+ const handleInvalidate = React.useCallback(() => {
2064
+ try {
2065
+ if (onInvalidate)
2066
+ return onInvalidate();
2067
+ const kind = ctx.engineKind?.();
2068
+ if (kind === "pull")
2069
+ ctx.runner.computeNode(id);
2070
+ else
2071
+ ctx.triggerExternal?.(id, { type: "invalidate" });
2072
+ }
2073
+ catch { }
2074
+ }, [ctx, id, onInvalidate]);
2075
+ return (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: {
2076
+ maxHeight: NODE_HEADER_HEIGHT_PX,
2077
+ minHeight: NODE_HEADER_HEIGHT_PX,
2078
+ }, children: [jsxRuntime.jsx("strong", { className: "flex-1 h-full text-sm", style: { lineHeight: `${NODE_HEADER_HEIGHT_PX}px` }, children: title }), jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [jsxRuntime.jsx("button", { className: "w-4 h-4 border border-gray-400 rounded text-[10px] leading-3 flex items-center justify-center", title: "Invalidate and re-run", onClick: (e) => {
2079
+ e.stopPropagation();
2080
+ handleInvalidate();
2081
+ }, children: "\u21BB" }), right, validation.issues && validation.issues.length > 0 && (jsxRuntime.jsx(IssueBadge, { level: validation.issues.some((i) => i.level === "error")
2082
+ ? "error"
2083
+ : "warning", size: 12, className: "w-3 h-3", title: validation.issues
2084
+ .map((v) => `${v.code}: ${v.message}`)
2085
+ .join("; ") })), jsxRuntime.jsxs("span", { className: "text-[10px] opacity-70", children: ["(", id, ")"] })] })] }));
2086
+ }
2087
+ function DefaultNodeContent({ id, data, isConnectable, }) {
2088
+ useWorkbenchContext();
2043
2089
  const { showValues, inputValues, outputValues, toString } = data;
2044
2090
  const inputEntries = data.inputHandles ?? [];
2045
2091
  const outputEntries = data.outputHandles ?? [];
@@ -2054,12 +2100,12 @@ function DefaultNodeContent({ data, isConnectable, }) {
2054
2100
  const hasAny = vIssues.length > 0;
2055
2101
  const hasErr = vIssues.some((v) => v.level === "error");
2056
2102
  return cx("!w-3 !h-3 !bg-white !dark:bg-stone-900 !border-gray-500 dark:!border-gray-400", kind === "output" && "!rounded-none", hasAny && (hasErr ? "!border-red-500" : "!border-amber-500"));
2057
- }, renderLabel: ({ kind, id }) => {
2103
+ }, renderLabel: ({ kind, id: handleId }) => {
2058
2104
  const entries = kind === "input" ? inputEntries : outputEntries;
2059
- const entry = entries.find((e) => e.id === id);
2105
+ const entry = entries.find((e) => e.id === handleId);
2060
2106
  if (!entry)
2061
- return id;
2062
- const vIssues = (kind === "input" ? validation.inputs : validation.outputs).filter((v) => v.handle === id);
2107
+ return handleId;
2108
+ const vIssues = (kind === "input" ? validation.inputs : validation.outputs).filter((v) => v.handle === handleId);
2063
2109
  const hasAny = vIssues.length > 0;
2064
2110
  const hasErr = vIssues.some((v) => v.level === "error");
2065
2111
  const title = vIssues
@@ -2076,7 +2122,7 @@ function DefaultNodeContent({ data, isConnectable, }) {
2076
2122
  const txt = toString(resolved.typeId, resolved.value);
2077
2123
  return typeof txt === "string" ? txt : String(txt);
2078
2124
  })();
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 }))] }));
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 }))] }));
2080
2126
  } })] }));
2081
2127
  }
2082
2128
 
@@ -2167,7 +2213,7 @@ function DefaultContextMenu({ open, clientPos, onAdd, onClose, }) {
2167
2213
  }
2168
2214
 
2169
2215
  function NodeContextMenu({ open, clientPos, nodeId, onClose, }) {
2170
- const { wb, runner, engineKind } = useWorkbenchContext();
2216
+ const { wb, runner, engineKind, registry, outputsMap, outputTypesMap } = useWorkbenchContext();
2171
2217
  const ref = React.useRef(null);
2172
2218
  // outside click + ESC
2173
2219
  React.useEffect(() => {
@@ -2202,39 +2248,181 @@ function NodeContextMenu({ open, clientPos, nodeId, onClose, }) {
2202
2248
  const x = Math.min(clientPos.x, (typeof window !== "undefined" ? window.innerWidth : 0) -
2203
2249
  (MENU_MIN_WIDTH + PADDING));
2204
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
+ };
2205
2381
  // actions
2206
- const handleDelete = () => {
2382
+ const handleDelete = React.useCallback(() => {
2207
2383
  wb.removeNode(nodeId);
2208
2384
  onClose();
2209
- };
2210
- const handleDuplicate = () => {
2385
+ }, [nodeId, wb, onClose]);
2386
+ const handleDuplicate = React.useCallback(() => {
2211
2387
  const def = wb.export();
2212
2388
  const n = def.nodes.find((n) => n.nodeId === nodeId);
2213
2389
  if (!n)
2214
2390
  return onClose();
2215
2391
  const pos = wb.getPositions?.()[nodeId] || { x: 0, y: 0 };
2216
- 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
+ });
2217
2397
  onClose();
2218
- };
2219
- 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 () => {
2220
2404
  try {
2221
2405
  await navigator.clipboard.writeText(nodeId);
2222
2406
  }
2223
2407
  catch { }
2224
2408
  onClose();
2225
- };
2226
- const canRunPull = engineKind()?.toString() === "pull";
2227
- const handleRunPull = async () => {
2409
+ }, [nodeId, onClose]);
2410
+ const handleRunPull = React.useCallback(async () => {
2228
2411
  try {
2229
2412
  await runner.computeNode(nodeId);
2230
2413
  }
2231
2414
  catch { }
2232
2415
  onClose();
2233
- };
2416
+ }, [nodeId, runner, onClose]);
2417
+ const canRunPull = engineKind()?.toString() === "pull";
2418
+ const outs = getBakeableOutputs();
2234
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) => {
2235
2420
  e.preventDefault();
2236
2421
  e.stopPropagation();
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" })] }));
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" })] }));
2238
2426
  }
2239
2427
 
2240
2428
  const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, getDefaultNodeSize }, ref) => {
@@ -2464,10 +2652,21 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement, get
2464
2652
  setNodeMenuOpen(false);
2465
2653
  }
2466
2654
  };
2467
- const addNodeAt = (typeId, pos) => {
2655
+ const addNodeAt = React.useCallback((typeId, pos) => {
2468
2656
  wb.addNode({ typeId, position: pos });
2469
- };
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, 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 })] }) }) }));
2471
2670
  });
2472
2671
 
2473
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, }) {
@@ -2641,6 +2840,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
2641
2840
  r.registerEnum({
2642
2841
  id: t.id,
2643
2842
  options: t.options,
2843
+ bakeTarget: t.bakeTarget,
2644
2844
  });
2645
2845
  }
2646
2846
  else {
@@ -2648,6 +2848,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
2648
2848
  id: t.id,
2649
2849
  displayName: t.displayName,
2650
2850
  validate: (_v) => true,
2851
+ bakeTarget: t.bakeTarget,
2651
2852
  });
2652
2853
  }
2653
2854
  }
@@ -2955,6 +3156,7 @@ exports.AbstractWorkbench = AbstractWorkbench;
2955
3156
  exports.CLIWorkbench = CLIWorkbench;
2956
3157
  exports.DefaultNode = DefaultNode;
2957
3158
  exports.DefaultNodeContent = DefaultNodeContent;
3159
+ exports.DefaultNodeHeader = DefaultNodeHeader;
2958
3160
  exports.DefaultUIExtensionRegistry = DefaultUIExtensionRegistry;
2959
3161
  exports.InMemoryWorkbench = InMemoryWorkbench;
2960
3162
  exports.Inspector = Inspector;