@tangle-network/sandbox-ui 0.113.7 → 0.115.0

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 (107) hide show
  1. package/README.md +40 -0
  2. package/dist/WorkflowGraph-B-NsdYz_.js +1961 -0
  3. package/dist/assets.d.ts +214 -206
  4. package/dist/assets.js +1057 -908
  5. package/dist/auth-CWe17Hly.js +37 -0
  6. package/dist/auth.d.ts +2 -30
  7. package/dist/auth.js +2 -15
  8. package/dist/chat-BOvFAe6p.js +903 -0
  9. package/dist/chat.d.ts +2 -230
  10. package/dist/chat.js +2 -34
  11. package/dist/connectors.d.ts +52 -49
  12. package/dist/connectors.js +595 -643
  13. package/dist/dashboard-CZ3VgNPR.js +4969 -0
  14. package/dist/dashboard.d.ts +4 -1297
  15. package/dist/dashboard.js +4 -152
  16. package/dist/editor.d.ts +2 -1
  17. package/dist/editor.js +2 -31
  18. package/dist/files.d.ts +2 -6
  19. package/dist/files.js +2 -33
  20. package/dist/globals.css +75 -3
  21. package/dist/hooks.d.ts +146 -148
  22. package/dist/hooks.js +222 -55
  23. package/dist/index-CKRaj1pM.d.ts +776 -0
  24. package/dist/index-CXX5IpH2.d.ts +2 -0
  25. package/dist/index-CesPlOyx.d.ts +367 -0
  26. package/dist/index-DPePq7c8.d.ts +1340 -0
  27. package/dist/index-DiyLLZuJ.d.ts +230 -0
  28. package/dist/index-DvmZLMT1.d.ts +30 -0
  29. package/dist/index-PRM636Ex.d.ts +86 -0
  30. package/dist/index.d.ts +21 -33
  31. package/dist/index.js +21 -701
  32. package/dist/info-panel-C9cqDzOL.js +594 -0
  33. package/dist/integrations.d.ts +133 -141
  34. package/dist/integrations.js +491 -673
  35. package/dist/markdown.d.ts +2 -1
  36. package/dist/markdown.js +2 -13
  37. package/dist/model-I-NIxeM-.js +1250 -0
  38. package/dist/model-brand-B9A4H9pJ.d.ts +98 -0
  39. package/dist/model-brand-hMyDTUqB.js +624 -0
  40. package/dist/motion-DkyBRJb9.js +37 -0
  41. package/dist/openui.d.ts +2 -1
  42. package/dist/openui.js +2 -7
  43. package/dist/pages.d.ts +307 -300
  44. package/dist/pages.js +4404 -4181
  45. package/dist/primitives-Dh1vG6J3.js +113 -0
  46. package/dist/primitives.d.ts +2 -74
  47. package/dist/primitives.js +2 -204
  48. package/dist/provider-logo-DjqmConF.js +311 -0
  49. package/dist/run.d.ts +2 -1
  50. package/dist/run.js +2 -23
  51. package/dist/sdk-hooks.d.ts +3 -4
  52. package/dist/sdk-hooks.js +3 -31
  53. package/dist/sidecar-DgH_vQ6m.d.ts +9 -0
  54. package/dist/stores.d.ts +14 -15
  55. package/dist/stores.js +13 -81
  56. package/dist/styles.css +75 -3
  57. package/dist/template-card-L-4gmjx2.d.ts +90 -0
  58. package/dist/terminal-CN9ZgHRt.js +197 -0
  59. package/dist/terminal.d.ts +60 -60
  60. package/dist/terminal.js +2 -10
  61. package/dist/tokens.css +51 -0
  62. package/dist/types.d.ts +4 -11
  63. package/dist/types.js +0 -1
  64. package/dist/use-pty-session-C8cE0adT.js +669 -0
  65. package/dist/use-sidecar-auth-CCTJcrq7.d.ts +145 -0
  66. package/dist/use-sidecar-auth-DkdcufOz.js +742 -0
  67. package/dist/utils-DzWbktWG.js +2 -0
  68. package/dist/utils.d.ts +2 -1
  69. package/dist/utils.js +2 -37
  70. package/dist/workbench-ztU44Jt2.js +8455 -0
  71. package/dist/workbench.d.ts +2 -310
  72. package/dist/workbench.js +2 -44
  73. package/dist/workflows.d.ts +341 -339
  74. package/dist/workflows.js +74 -109
  75. package/dist/workspace-B6AtI6ld.js +2510 -0
  76. package/dist/workspace.d.ts +2 -761
  77. package/dist/workspace.js +2 -51
  78. package/package.json +46 -40
  79. package/dist/WorkflowGraph-2QOORYIR.js +0 -1722
  80. package/dist/chunk-2BUPSB7O.js +0 -0
  81. package/dist/chunk-3KBNPQQF.js +0 -2697
  82. package/dist/chunk-3LIXVEEE.js +0 -279
  83. package/dist/chunk-72UF2H5H.js +0 -707
  84. package/dist/chunk-AI25G5MX.js +0 -944
  85. package/dist/chunk-ANSC5AFC.js +0 -314
  86. package/dist/chunk-AZ3AWMTM.js +0 -8
  87. package/dist/chunk-BGVEHENC.js +0 -10
  88. package/dist/chunk-CZ7CSFO4.js +0 -39
  89. package/dist/chunk-DYXXYGS4.js +0 -4700
  90. package/dist/chunk-EI44GEQ5.js +0 -6
  91. package/dist/chunk-IACRDXER.js +0 -219
  92. package/dist/chunk-IOB2PW5Z.js +0 -55
  93. package/dist/chunk-M4K7ZGR2.js +0 -24
  94. package/dist/chunk-MYZAW4RX.js +0 -665
  95. package/dist/chunk-OMUIPJQZ.js +0 -555
  96. package/dist/chunk-RQZRXKMR.js +0 -398
  97. package/dist/chunk-SOKKTB7W.js +0 -230
  98. package/dist/chunk-TADGS4Q7.js +0 -1010
  99. package/dist/chunk-UCQNOUN3.js +0 -7964
  100. package/dist/chunk-ULPLRBMF.js +0 -34
  101. package/dist/chunk-WID73FPH.js +0 -89
  102. package/dist/chunk-X3YXP7FG.js +0 -38
  103. package/dist/logo-DiAjq759.d.ts +0 -15
  104. package/dist/model-brand-BNnaUKKZ.d.ts +0 -99
  105. package/dist/ports-list-CDIvTNnX.d.ts +0 -17
  106. package/dist/sdk-hooks-Djkjq4cp.d.ts +0 -145
  107. package/dist/template-card-Bn-Bw5xC.d.ts +0 -87
@@ -1,1722 +0,0 @@
1
- import {
2
- ProviderIcon
3
- } from "./chunk-3LIXVEEE.js";
4
- import {
5
- ACTION_OUTPUT_ROWS,
6
- AGENT_BODY_ROWS,
7
- COMPACT_NODE_SIZE,
8
- COMPACT_TILE,
9
- OUTPUT_PREVIEW_CHARS,
10
- STRUCTURAL_OUTPUT_ROWS,
11
- buildWorkflowGraph,
12
- clampPreview,
13
- fmtCost,
14
- fmtDuration,
15
- fmtTokens,
16
- isStructuralKind,
17
- providerLabel,
18
- shortModel,
19
- triggerNodeIndex,
20
- wfEdgeId,
21
- worstSeverity
22
- } from "./chunk-AI25G5MX.js";
23
- import {
24
- ModelBrandStack,
25
- modelBrandFor
26
- } from "./chunk-RQZRXKMR.js";
27
- import "./chunk-EI44GEQ5.js";
28
- import "./chunk-CZ7CSFO4.js";
29
-
30
- // src/workflows/WorkflowGraph.tsx
31
- import {
32
- Background,
33
- BaseEdge,
34
- Controls,
35
- EdgeLabelRenderer,
36
- getSmoothStepPath,
37
- getViewportForBounds,
38
- Handle,
39
- MarkerType,
40
- Panel,
41
- Position as Position2,
42
- ReactFlow,
43
- useEdgesState,
44
- useNodesState
45
- } from "@xyflow/react";
46
- import "@xyflow/react/dist/style.css";
47
- import { Maximize2, Minimize2, Plus, X } from "lucide-react";
48
- import {
49
- createContext,
50
- useCallback,
51
- useContext,
52
- useEffect,
53
- useMemo,
54
- useRef,
55
- useState
56
- } from "react";
57
-
58
- // src/workflows/flow-graph.ts
59
- import { Position } from "@xyflow/react";
60
- var WF_EDGE_TYPE = "wfEdge";
61
- var SIDE_POSITION = {
62
- left: Position.Left,
63
- right: Position.Right,
64
- top: Position.Top,
65
- bottom: Position.Bottom
66
- };
67
- function isNodeStateMap(arg) {
68
- const values = Object.values(arg);
69
- return values.length > 0 && values.every(
70
- (v) => v !== null && typeof v === "object" && "status" in v
71
- );
72
- }
73
- var FLOW_GRAPH_OPTION_KEYS = [
74
- "nodeState",
75
- "direction",
76
- "compact",
77
- "wrap",
78
- "edges",
79
- "reserveEdgeInsert"
80
- ];
81
- function normalizeFlowGraphOptions(arg) {
82
- if (!arg) return {};
83
- if (FLOW_GRAPH_OPTION_KEYS.some((key) => key in arg)) {
84
- return arg;
85
- }
86
- return isNodeStateMap(arg) ? { nodeState: arg } : {};
87
- }
88
- function buildFlowGraph(yaml, optionsOrNodeState) {
89
- const options = normalizeFlowGraphOptions(optionsOrNodeState);
90
- const nodeState = options.nodeState;
91
- const direction = options.direction ?? "LR";
92
- const compact = options.compact ?? false;
93
- const graph = buildWorkflowGraph(yaml, {
94
- reserveRunState: nodeState !== void 0,
95
- direction,
96
- compact,
97
- ...options.wrap ? { wrap: true } : {},
98
- ...options.edges ? { edges: options.edges } : {},
99
- ...options.reserveEdgeInsert ? { reserveEdgeInsert: true } : {}
100
- });
101
- const isLR = direction === "LR";
102
- const sourcePosition = isLR ? Position.Right : Position.Bottom;
103
- const targetPosition = isLR ? Position.Left : Position.Top;
104
- return {
105
- error: graph.error,
106
- nodes: graph.nodes.map(
107
- (n) => ({
108
- id: n.id,
109
- type: "wfNode",
110
- position: n.position,
111
- width: n.width,
112
- height: n.height,
113
- // A node is a `do` entry, and removing one is a list edit — never a
114
- // canvas gesture. This matters most on an EDITABLE canvas, where the
115
- // delete key is armed for edges: React Flow deletes a selected node
116
- // together with every edge touching it, so a node left deletable would
117
- // vanish from the canvas AND report each of its edges as removed,
118
- // asking the host to drop declared edges nobody touched.
119
- deletable: false,
120
- // Fix the DOM box to the laid-out size so the card can't grow past its
121
- // reserved space and overlap a neighbour (see model.ts nodeHeight).
122
- style: { width: n.width, height: n.height },
123
- // A folded layout names each node's own sides (a mirrored row, a corner
124
- // that leaves downward); a straight one names none and takes the
125
- // direction's answer for every node.
126
- sourcePosition: n.sourceSide ? SIDE_POSITION[n.sourceSide] : sourcePosition,
127
- targetPosition: n.targetSide ? SIDE_POSITION[n.targetSide] : targetPosition,
128
- data: nodeState?.[n.id] ? { ...n.data, state: nodeState[n.id] } : n.data
129
- })
130
- ),
131
- edges: graph.edges.map((e) => {
132
- const decorated = e.whenLabel !== void 0 || e.backEdge === true;
133
- return {
134
- id: e.id,
135
- source: e.source,
136
- target: e.target,
137
- type: decorated ? WF_EDGE_TYPE : "smoothstep",
138
- data: {
139
- kind: e.kind,
140
- ...e.whenLabel !== void 0 ? { whenLabel: e.whenLabel } : {},
141
- ...e.backEdge === true ? { backEdge: true } : {}
142
- }
143
- };
144
- })
145
- };
146
- }
147
- function sameRunState(a, b) {
148
- if (a === b) return true;
149
- if (!a || !b) return false;
150
- const aKeys = Object.keys(a);
151
- const bKeys = Object.keys(b);
152
- if (aKeys.length !== bKeys.length) return false;
153
- return aKeys.every((k) => a[k] === b[k]);
154
- }
155
- function mergeRunState(prev, baseDataById, nodeState) {
156
- return prev.map((n) => {
157
- const base = baseDataById.get(n.id) ?? n.data;
158
- const s = nodeState?.[n.id];
159
- const nextData = s ? { ...base, state: s } : base;
160
- return sameRunState(n.data.state, nextData.state) ? n : { ...n, data: nextData };
161
- });
162
- }
163
- function indexProblems(problems) {
164
- const byNode = /* @__PURE__ */ new Map();
165
- const byEdge = /* @__PURE__ */ new Map();
166
- const named = (value) => typeof value === "string" && value !== "";
167
- for (const problem of problems ?? []) {
168
- const into = problem.anchor === "node" ? named(problem.node) ? byNode : null : problem.anchor === "edge" && named(problem.from) && named(problem.to) ? byEdge : null;
169
- if (!into) continue;
170
- const key = problem.anchor === "node" ? problem.node : wfEdgeId(problem.from, problem.to);
171
- const at = into.get(key);
172
- if (at) at.push(problem);
173
- else into.set(key, [problem]);
174
- }
175
- return { byNode, byEdge };
176
- }
177
- var INSERT_CLEARANCE = 8;
178
- var CLUSTER_HALF_SIZE = {
179
- /** The insert control alone: a 20-unit square. */
180
- control: { width: 10, height: 10 },
181
- /**
182
- * A problem chip — and, since the two stack in one column, the chip WITH the
183
- * control under it, which is the taller of the two and so the one the bound
184
- * has to cover. Measured on the rendered page at 160 x 38.5 flow units (the
185
- * chip's `max-w-40` cap, over chip + gap + control), and rounded outward: a
186
- * bound that understates is a bound that lets the cluster lap over a card.
187
- */
188
- chip: { width: 80, height: 20 }
189
- };
190
- var INSERT_MAX_CARDS_CROSSED = 2;
191
- function clearOfNodeBoxes(point, boxes, direction, half) {
192
- const isLR = direction === "LR";
193
- const base = isLR ? point.y : point.x;
194
- const main = isLR ? point.x : point.y;
195
- const padMain = isLR ? half.width : half.height;
196
- const padCross = isLR ? half.height : half.width;
197
- const column = boxes.filter((b) => {
198
- const lo = (isLR ? b.x : b.y) - padMain;
199
- const size = (isLR ? b.width : b.height) + padMain * 2;
200
- return main >= lo && main <= lo + size;
201
- });
202
- if (column.length === 0) return 0;
203
- const walk = (sign) => {
204
- let at = base;
205
- for (let crossed = 0; crossed <= INSERT_MAX_CARDS_CROSSED; crossed += 1) {
206
- const hit = column.find((b) => {
207
- const lo2 = (isLR ? b.y : b.x) - padCross;
208
- const size2 = (isLR ? b.height : b.width) + padCross * 2;
209
- return at >= lo2 && at <= lo2 + size2;
210
- });
211
- if (!hit) return at - base;
212
- if (crossed === INSERT_MAX_CARDS_CROSSED) return null;
213
- const lo = isLR ? hit.y : hit.x;
214
- const size = isLR ? hit.height : hit.width;
215
- at = sign > 0 ? lo + size + padCross + INSERT_CLEARANCE : lo - padCross - INSERT_CLEARANCE;
216
- }
217
- return null;
218
- };
219
- const up = walk(-1);
220
- const down = walk(1);
221
- if (up === null && down === null) return null;
222
- if (up === null) return down;
223
- if (down === null) return up;
224
- return Math.abs(up) <= Math.abs(down) ? up : down;
225
- }
226
-
227
- // src/workflows/node-output.tsx
228
- import { jsx, jsxs } from "react/jsx-runtime";
229
- var ERROR_TEXT = { color: "var(--surface-danger-text)" };
230
- var MAX_JSON_ENTRIES = 6;
231
- function isStructureOnly(line) {
232
- if (/^(```|~~~)/.test(line)) return true;
233
- const isBreak = /^([-*_])\1{2,}$/.test(line);
234
- if (/^[\s|:-]+$/.test(line) && line.includes("|")) return line.includes("-");
235
- return isBreak;
236
- }
237
- function condenseText(text) {
238
- const isListItem = (line) => /^\s*([-*+]\s|\d+[.)]\s)/.test(line);
239
- const lines = text.split("\n").map((line) => ({
240
- // A list item keeps its boundary when the lines are joined: run three
241
- // bullets together with plain spaces and they read as one long sentence,
242
- // which is the "word dump" all over again.
243
- item: isListItem(line),
244
- text: line.replace(/^\s{0,3}#{1,6}\s+/, "").replace(/^\s{0,3}>\s?/, "").replace(/^\s*[-*+]\s+(\[[ xX]\]\s+)?/, "").replace(/^\s*\d+[.)]\s+/, "").trim()
245
- })).filter(({ text: line }) => line !== "" && !isStructureOnly(line));
246
- return lines.map(
247
- ({ item, text: line }, i) => i > 0 && item ? `\xB7 ${line}` : line
248
- ).join(" ").replace(
249
- /(?<=^|\s)(\*\*|~~)(?!\s)(.+?)(?<!\s)\1(?=[\s.,:;!?)\]}"']|$)/g,
250
- "$2"
251
- ).replace(/(?<![\w*])\*(?!\s)([^*]+?)(?<!\s)\*(?![\w*])/g, "$1").replace(/`([^`]+)`/g, "$1").replace(/!?\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/\s+/g, " ").trim();
252
- }
253
- function scalar(value) {
254
- if (value === null) return "null";
255
- if (typeof value === "string") return value;
256
- if (typeof value === "number" || typeof value === "boolean") {
257
- return String(value);
258
- }
259
- if (Array.isArray(value)) {
260
- return `[${value.length} item${value.length === 1 ? "" : "s"}]`;
261
- }
262
- if (typeof value === "object") return "{\u2026}";
263
- return "";
264
- }
265
- function classifyOutput(preview, condenseMarkdown = false) {
266
- let trimmed = preview?.trim() ?? "";
267
- trimmed = trimmed.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])/g, "").replace(/(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "").trimEnd();
268
- if (trimmed === "") return { kind: "empty" };
269
- const structured = trimmed.startsWith("{") || trimmed.startsWith("[");
270
- if (structured) {
271
- try {
272
- const parsed = JSON.parse(trimmed);
273
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
274
- const all = Object.entries(parsed);
275
- const entries = all.slice(0, MAX_JSON_ENTRIES).map(([k, v]) => [k, scalar(v)]);
276
- if (entries.length > 0) {
277
- return { kind: "json", entries, truncated: all.length > entries.length };
278
- }
279
- }
280
- return { kind: "code", text: trimmed };
281
- } catch {
282
- return { kind: "code", text: trimmed };
283
- }
284
- }
285
- if (!condenseMarkdown) return { kind: "text", text: trimmed };
286
- const condensed = condenseText(trimmed);
287
- return condensed === "" ? { kind: "empty" } : { kind: "text", text: condensed };
288
- }
289
- var CLAMP_BY_ROWS = {
290
- 1: "truncate",
291
- 2: "line-clamp-2",
292
- 3: "line-clamp-3",
293
- 4: "line-clamp-4",
294
- 5: "line-clamp-5",
295
- 6: "line-clamp-6"
296
- };
297
- function NodeOutputBody({
298
- shape,
299
- rows = 2,
300
- tone = "default"
301
- }) {
302
- const unclamped = rows === "none";
303
- const clampClass = unclamped ? "" : CLAMP_BY_ROWS[rows] ?? "line-clamp-2";
304
- const codeWrap = rows === 1 ? "" : "whitespace-pre-wrap break-all";
305
- if (shape.kind === "empty") return null;
306
- if (shape.kind === "json") {
307
- const overflow = unclamped ? shape.truncated : shape.truncated || shape.entries.length > rows;
308
- const visible = unclamped ? shape.entries : shape.entries.slice(0, overflow ? Math.max(0, rows - 1) : rows);
309
- const isError = tone === "error";
310
- const keyStyle = isError ? ERROR_TEXT : void 0;
311
- const keyClass = isError ? "" : "text-muted-foreground";
312
- const valueClass = isError ? "" : "text-foreground";
313
- return /* @__PURE__ */ jsxs("dl", { className: "space-y-0.5 font-mono text-[10.5px] leading-snug", children: [
314
- visible.map(([key, value]) => /* @__PURE__ */ jsxs("div", { className: "flex gap-1.5", children: [
315
- /* @__PURE__ */ jsx(
316
- "dt",
317
- {
318
- className: `max-w-[45%] shrink-0 truncate ${keyClass}`,
319
- style: keyStyle,
320
- title: key,
321
- children: key
322
- }
323
- ),
324
- /* @__PURE__ */ jsx(
325
- "dd",
326
- {
327
- className: `min-w-0 flex-1 truncate ${valueClass}`,
328
- style: isError ? ERROR_TEXT : void 0,
329
- title: value,
330
- children: value
331
- }
332
- )
333
- ] }, key)),
334
- overflow && /* @__PURE__ */ jsx("div", { className: `text-[10px] ${keyClass}`, style: keyStyle, children: "\u2026" })
335
- ] });
336
- }
337
- if (shape.kind === "code") {
338
- return /* @__PURE__ */ jsx(
339
- "pre",
340
- {
341
- className: `${clampClass} ${codeWrap} font-mono text-[10.5px] leading-snug ${tone === "error" ? "" : "text-foreground"}`,
342
- style: tone === "error" ? ERROR_TEXT : void 0,
343
- title: shape.text,
344
- children: shape.text
345
- }
346
- );
347
- }
348
- return /* @__PURE__ */ jsx(
349
- "p",
350
- {
351
- className: `${clampClass} text-[11px] leading-snug ${tone === "error" ? "" : "text-foreground"}`,
352
- style: tone === "error" ? ERROR_TEXT : void 0,
353
- title: shape.text,
354
- children: shape.text
355
- }
356
- );
357
- }
358
-
359
- // src/workflows/node-ui.tsx
360
- import {
361
- AlertTriangle,
362
- Bell,
363
- Box,
364
- Cable,
365
- Camera,
366
- Circle,
367
- Clock,
368
- Code,
369
- GitBranch,
370
- Repeat,
371
- ScanSearch,
372
- Sparkles,
373
- UserCheck,
374
- Webhook,
375
- Zap
376
- } from "lucide-react";
377
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
378
- var MUTED_TRACK = "color-mix(in srgb, hsl(var(--muted-foreground)) 22%, transparent)";
379
- var TONE_ACCENT = {
380
- trigger: "hsl(var(--primary))",
381
- structural: "var(--surface-warning-text)",
382
- action: "hsl(var(--muted-foreground))"
383
- };
384
- var STATUS_QUEUED = "hsl(var(--muted-foreground))";
385
- var STATUS_DONE = "var(--surface-success-text)";
386
- var STATUS_FAILED = "var(--surface-danger-text)";
387
- var STATUS_RUNNING = "hsl(var(--primary))";
388
- var STATUS_WAITING = "var(--surface-warning-text)";
389
- var STATUS_COLOR = {
390
- queued: STATUS_QUEUED,
391
- running: STATUS_RUNNING,
392
- waiting: STATUS_WAITING,
393
- succeeded: STATUS_DONE,
394
- failed: STATUS_FAILED
395
- };
396
- function edgeColor(status) {
397
- return status ? STATUS_COLOR[status] : STATUS_QUEUED;
398
- }
399
- var STATUS_LABEL = {
400
- queued: "Queued",
401
- running: "Running",
402
- waiting: "Waiting on you",
403
- succeeded: "Done",
404
- failed: "Failed"
405
- };
406
- var STATUS_PILL = {
407
- queued: {
408
- background: "color-mix(in srgb, hsl(var(--muted-foreground)) 12%, transparent)",
409
- color: "hsl(var(--muted-foreground))",
410
- borderColor: "color-mix(in srgb, hsl(var(--muted-foreground)) 28%, transparent)"
411
- },
412
- running: {
413
- background: "color-mix(in srgb, hsl(var(--primary)) 14%, transparent)",
414
- color: "hsl(var(--primary))",
415
- borderColor: "color-mix(in srgb, hsl(var(--primary)) 40%, transparent)"
416
- },
417
- waiting: {
418
- background: "var(--surface-warning-bg)",
419
- color: "var(--surface-warning-text)",
420
- borderColor: "var(--surface-warning-border)"
421
- },
422
- succeeded: {
423
- background: "var(--surface-success-bg)",
424
- color: "var(--surface-success-text)",
425
- borderColor: "var(--surface-success-border)"
426
- },
427
- failed: {
428
- background: "var(--surface-danger-bg)",
429
- color: "var(--surface-danger-text)",
430
- borderColor: "var(--surface-danger-border)"
431
- }
432
- };
433
- function statusBorder(status) {
434
- const color = STATUS_COLOR[status];
435
- switch (status) {
436
- case "running":
437
- case "waiting":
438
- return {
439
- borderColor: color,
440
- boxShadow: `0 0 0 1px color-mix(in srgb, ${color} 45%, transparent), 0 0 24px -6px ${color}`
441
- };
442
- case "queued":
443
- return { borderColor: "hsl(var(--border))" };
444
- default:
445
- return {
446
- borderColor: color,
447
- boxShadow: `0 0 0 1px color-mix(in srgb, ${color} 35%, transparent)`
448
- };
449
- }
450
- }
451
- function progressFill(status) {
452
- return status === "succeeded" || status === "failed" ? "100%" : status === "running" || status === "waiting" ? "58%" : "6%";
453
- }
454
- var KIND_ICON = {
455
- schedule: Clock,
456
- // Both wake the workflow on an inbound POST, so they share the mark. A
457
- // `provider_event` normally shows its provider's LOGO instead and reaches this
458
- // glyph only when the connector has none.
459
- provider_event: Webhook,
460
- webhook: Webhook,
461
- trigger: Zap,
462
- "agent.run": Sparkles,
463
- "integration.invoke": Cable,
464
- notify: Bell,
465
- parallel: GitBranch,
466
- foreach: Repeat,
467
- decision: UserCheck,
468
- "sandbox.spawn": Box,
469
- "sandbox.snapshot": Camera,
470
- "script.run": Code,
471
- "trace.analyze": ScanSearch
472
- };
473
- var PROBLEM_SURFACE_BY_SEVERITY = {
474
- error: {
475
- background: "var(--surface-danger-bg)",
476
- color: "var(--surface-danger-text)",
477
- borderColor: "var(--surface-danger-border)"
478
- },
479
- warning: {
480
- background: "var(--surface-warning-bg)",
481
- color: "var(--surface-warning-text)",
482
- borderColor: "var(--surface-warning-border)"
483
- }
484
- };
485
- function problemSurface(severity) {
486
- return PROBLEM_SURFACE_BY_SEVERITY[severity] ?? PROBLEM_SURFACE_BY_SEVERITY.error;
487
- }
488
- function problemBorder(severity) {
489
- const color = problemSurface(severity).color;
490
- return {
491
- borderColor: color,
492
- boxShadow: `0 0 0 1px color-mix(in srgb, ${color} 35%, transparent)`
493
- };
494
- }
495
- var SEVERITY_WORD = {
496
- error: "Error",
497
- warning: "Warning"
498
- };
499
- function problemSentence(problem) {
500
- const raw = typeof problem.message === "string" ? problem.message : "";
501
- const message = raw.split("\n").map((line) => line.trim()).filter(Boolean).join(" ");
502
- const word = SEVERITY_WORD[problem.severity] ?? SEVERITY_WORD.error;
503
- return message ? `${word}: ${message}` : word;
504
- }
505
- function joinSentences(parts) {
506
- return parts.reduce(
507
- (joined, part) => joined ? `${joined}${/[.!?;,]$/.test(joined) ? "" : "."} ${part}` : part,
508
- ""
509
- );
510
- }
511
- function problemTitle(problems) {
512
- return problems.map(problemSentence).join("\n");
513
- }
514
- function ProblemMessages({ problems }) {
515
- if (problems.length === 0) return null;
516
- return /* @__PURE__ */ jsx2("span", { className: "sr-only", children: problems.length === 1 ? problemSentence(problems[0]) : `${problems.length} problems. ${joinSentences(problems.map(problemSentence))}` });
517
- }
518
- function ProblemMarker({
519
- problems,
520
- className = ""
521
- }) {
522
- const severity = worstSeverity(problems) ?? "error";
523
- return /* @__PURE__ */ jsxs2(
524
- "span",
525
- {
526
- "data-testid": "wf-node-problem",
527
- "data-severity": severity,
528
- className: `flex shrink-0 items-center gap-0.5 rounded-full border px-1 py-[1px] font-medium text-[10px] leading-none ${className}`,
529
- style: problemSurface(severity),
530
- children: [
531
- /* @__PURE__ */ jsxs2("span", { "aria-hidden": true, className: "flex items-center gap-0.5", title: problemTitle(problems), children: [
532
- /* @__PURE__ */ jsx2(AlertTriangle, { size: 9 }),
533
- problems.length > 1 && /* @__PURE__ */ jsx2("span", { children: problems.length })
534
- ] }),
535
- /* @__PURE__ */ jsx2(ProblemMessages, { problems })
536
- ]
537
- }
538
- );
539
- }
540
- function StatusPill({ status }) {
541
- return /* @__PURE__ */ jsx2(
542
- "span",
543
- {
544
- className: "shrink-0 whitespace-nowrap rounded-full border px-2 py-[1px] font-medium text-[10px]",
545
- style: STATUS_PILL[status],
546
- children: STATUS_LABEL[status]
547
- }
548
- );
549
- }
550
- function StatusFooter({
551
- status,
552
- rounds,
553
- cost,
554
- tokens,
555
- elapsed
556
- }) {
557
- const roundsLabel = rounds !== void 0 ? `${rounds} round${rounds === 1 ? "" : "s"}` : void 0;
558
- const caption = [roundsLabel, cost, tokens].filter(Boolean).join(" \xB7 ");
559
- return /* @__PURE__ */ jsxs2("div", { className: "wf-node-body-in mt-auto border-border border-t", children: [
560
- /* @__PURE__ */ jsx2(
561
- "div",
562
- {
563
- className: "h-1 w-full overflow-hidden",
564
- style: { background: MUTED_TRACK },
565
- children: /* @__PURE__ */ jsx2(
566
- "div",
567
- {
568
- "data-testid": "wf-node-progress",
569
- className: `h-full ${status === "running" ? "animate-pulse" : ""}`,
570
- ...status === "running" ? { "data-motion": "essential" } : {},
571
- style: {
572
- width: progressFill(status),
573
- background: STATUS_COLOR[status]
574
- }
575
- }
576
- )
577
- }
578
- ),
579
- /* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between gap-2 px-3.5 py-1 text-[10px] text-muted-foreground leading-[15px]", children: [
580
- /* @__PURE__ */ jsx2("span", { className: "min-h-[15px] truncate tabular-nums", children: caption }),
581
- /* @__PURE__ */ jsx2("span", { className: "min-h-[15px] shrink-0 tabular-nums", children: elapsed ?? "" })
582
- ] })
583
- ] });
584
- }
585
- function emptySlotLabel(status) {
586
- switch (status) {
587
- case "queued":
588
- return "Not run yet";
589
- case "succeeded":
590
- return "No output";
591
- case "failed":
592
- return "No error reported";
593
- default:
594
- return void 0;
595
- }
596
- }
597
- function NodeMark({
598
- kind,
599
- provider,
600
- model,
601
- accent,
602
- tile
603
- }) {
604
- const Icon = kind && KIND_ICON[kind] || Circle;
605
- const brand = !provider && model ? modelBrandFor(model) : null;
606
- const mark = Math.round(tile * (provider ? 0.58 : 0.5));
607
- return /* @__PURE__ */ jsx2(
608
- "span",
609
- {
610
- "aria-hidden": "true",
611
- className: "flex shrink-0 items-center justify-center overflow-hidden rounded-lg border border-border",
612
- style: {
613
- width: tile,
614
- height: tile,
615
- background: `color-mix(in srgb, ${accent} 10%, hsl(var(--card)))`
616
- },
617
- children: provider ? /* @__PURE__ */ jsx2(
618
- ProviderIcon,
619
- {
620
- id: provider,
621
- displayName: providerLabel(provider),
622
- size: mark,
623
- className: "rounded-[3px]"
624
- }
625
- ) : brand ? (
626
- // One lab's own model draws ONE mark (28px at `md`) and fills the tile. A
627
- // HOSTED model draws two — host behind, lab in front — and that stack is
628
- // 36px wide, which would push the lab chip past the border of an expanded
629
- // card's 34px tile. So the stacked pair steps down unless the tile can hold
630
- // it; the single mark never has to.
631
- /* @__PURE__ */ jsx2(
632
- ModelBrandStack,
633
- {
634
- identity: brand,
635
- size: brand.combined || tile >= 38 ? "md" : "sm"
636
- }
637
- )
638
- ) : /* @__PURE__ */ jsx2(Icon, { size: mark, strokeWidth: 1.75, style: { color: accent } })
639
- }
640
- );
641
- }
642
-
643
- // src/workflows/WorkflowGraph.tsx
644
- import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
645
- var FIT_VIEW = { padding: "12px" };
646
- function fitZoomCeiling(compact) {
647
- return compact ? 1.5 : 1;
648
- }
649
- function fitZoomFloor(compact) {
650
- return compact ? 0.55 : 0.35;
651
- }
652
- function layoutBounds(nodes) {
653
- if (nodes.length === 0) return { x: 0, y: 0, width: 0, height: 0 };
654
- let minX = Number.POSITIVE_INFINITY;
655
- let minY = Number.POSITIVE_INFINITY;
656
- let maxX = Number.NEGATIVE_INFINITY;
657
- let maxY = Number.NEGATIVE_INFINITY;
658
- for (const node of nodes) {
659
- minX = Math.min(minX, node.position.x);
660
- minY = Math.min(minY, node.position.y);
661
- maxX = Math.max(maxX, node.position.x + (node.width ?? 0));
662
- maxY = Math.max(maxY, node.position.y + (node.height ?? 0));
663
- }
664
- return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
665
- }
666
- function framingViewport(bounds, width, height, compact) {
667
- const fit = getViewportForBounds(
668
- bounds,
669
- width,
670
- height,
671
- fitZoomFloor(compact),
672
- fitZoomCeiling(compact),
673
- FIT_VIEW.padding
674
- );
675
- const anchored = (offset, origin) => offset + Math.max(0, -(offset + origin * fit.zoom));
676
- return {
677
- zoom: fit.zoom,
678
- x: anchored(fit.x, bounds.x),
679
- y: anchored(fit.y, bounds.y)
680
- };
681
- }
682
- var LAYOUT_TRANSITION_MS = 220;
683
- var DENSITY_FLIP_AT = { grow: 0.12, shrink: 0.75 };
684
- function motionAllowed() {
685
- return typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches === false;
686
- }
687
- var lerp = (a, b, t) => a + (b - a) * t;
688
- var easeInOutCubic = (t) => t < 0.5 ? 4 * t * t * t : 1 - (-2 * t + 2) ** 3 / 2;
689
- function useColorMode() {
690
- const [mode, setMode] = useState(
691
- () => typeof document !== "undefined" && document.documentElement.classList.contains("dark") ? "dark" : "light"
692
- );
693
- useEffect(() => {
694
- const el = document.documentElement;
695
- const update = () => setMode(el.classList.contains("dark") ? "dark" : "light");
696
- update();
697
- const observer = new MutationObserver(update);
698
- observer.observe(el, { attributes: true, attributeFilter: ["class"] });
699
- return () => observer.disconnect();
700
- }, []);
701
- return mode;
702
- }
703
- var DirectionContext = createContext("LR");
704
- var DensityContext = createContext(false);
705
- var SelectedNodeContext = createContext(void 0);
706
- var RunModeContext = createContext(false);
707
- var SELECTION_OUTLINE = {
708
- outline: "2px solid hsl(var(--primary))",
709
- outlineOffset: 2
710
- };
711
- var ConnectableContext = createContext(false);
712
- var NodeProblemsContext = createContext(/* @__PURE__ */ new Map());
713
- var TriggerDeleteContext = createContext(null);
714
- var EdgeInsertContext = createContext(null);
715
- function isEditableEdge(edge) {
716
- return edge.data?.kind !== "fork" && triggerNodeIndex(edge.source) === null;
717
- }
718
- var HANDLE_CLASS = "!h-2 !w-2 !min-w-0 !border-0 !bg-transparent opacity-0";
719
- var HANDLE_CONNECTABLE_CLASS = "!z-10 !h-2.5 !w-2.5 !min-w-0 !rounded-full !border !border-border !bg-muted-foreground/70 opacity-100 transition-opacity hover:!bg-primary";
720
- function compactHandleStyle(position, isLR) {
721
- const tileLeft = isLR ? (COMPACT_NODE_SIZE.width - COMPACT_TILE) / 2 : 0;
722
- const center = COMPACT_TILE / 2;
723
- const anchor = (left, top) => ({
724
- left,
725
- top,
726
- right: "auto",
727
- bottom: "auto",
728
- transform: "translate(-50%, -50%)"
729
- });
730
- switch (position) {
731
- case Position2.Left:
732
- return anchor(tileLeft, center);
733
- case Position2.Right:
734
- return anchor(tileLeft + COMPACT_TILE, center);
735
- case Position2.Top:
736
- return anchor(tileLeft + center, 0);
737
- default:
738
- return anchor(tileLeft + center, COMPACT_TILE);
739
- }
740
- }
741
- function WorkflowNode({
742
- id,
743
- data,
744
- // Set only where the LAYOUT decides a node's own sides — a folded row that
745
- // runs right-to-left, or the step that turns the corner and leaves downward.
746
- // A straight layer flow leaves them to the direction, below.
747
- sourcePosition,
748
- targetPosition
749
- }) {
750
- const d = data;
751
- const state = d.state;
752
- const direction = useContext(DirectionContext);
753
- const compact = useContext(DensityContext);
754
- const runMode = useContext(RunModeContext);
755
- const connectable = useContext(ConnectableContext);
756
- const hostSelection = useContext(SelectedNodeContext);
757
- const problems = useContext(NodeProblemsContext).get(id);
758
- const problemSeverity = worstSeverity(problems);
759
- const removeTrigger = useContext(TriggerDeleteContext);
760
- const isTrigger = triggerNodeIndex(id) !== null;
761
- const selected = hostSelection !== void 0 && hostSelection === id;
762
- const isLR = direction === "LR";
763
- const targetPos = targetPosition ?? (isLR ? Position2.Left : Position2.Top);
764
- const sourcePos = sourcePosition ?? (isLR ? Position2.Right : Position2.Bottom);
765
- const accent = TONE_ACCENT[d.tone];
766
- const isAgent = d.kind === "agent.run";
767
- const subtitle = isAgent && state?.model ? shortModel(state.model) : d.subtitle;
768
- const markModel = isAgent && state?.model ? state.model : d.model;
769
- const runBands = runMode && d.tone !== "trigger";
770
- const runStatus = runBands ? state?.status ?? "queued" : state?.status;
771
- const duration = fmtDuration(state?.durationMs);
772
- const cost = fmtCost(state?.costUsd);
773
- const tokens = isAgent ? fmtTokens(state?.inputTokens, state?.outputTokens) : void 0;
774
- const runOutput = (() => {
775
- const source = state?.status === "failed" && state.error ? { text: state.error, tone: "error", label: "Error" } : state && state.status !== "failed" && state.outputPreview ? {
776
- text: state.outputPreview,
777
- tone: "default",
778
- label: "Output"
779
- } : null;
780
- if (!source) return null;
781
- const trimmed = source.text.trim();
782
- if (!trimmed) return null;
783
- const shape = classifyOutput(
784
- clampPreview(trimmed, OUTPUT_PREVIEW_CHARS),
785
- isAgent && source.tone !== "error"
786
- );
787
- return shape.kind === "empty" ? null : { shape, tone: source.tone, label: source.label };
788
- })();
789
- const triggerDeleteButton = isTrigger && removeTrigger ? /* @__PURE__ */ jsx3(
790
- "button",
791
- {
792
- type: "button",
793
- "data-testid": "wf-trigger-delete",
794
- title: "Remove this trigger",
795
- "aria-label": "Remove this trigger",
796
- className: "nodrag nopan -m-1 flex h-6 w-6 shrink-0 items-center justify-center p-1 text-muted-foreground transition hover:text-[var(--surface-danger-text)]",
797
- onClick: (event) => {
798
- event.stopPropagation();
799
- removeTrigger(id);
800
- },
801
- children: /* @__PURE__ */ jsx3("span", { className: "flex h-4 w-4 items-center justify-center rounded-full border border-border bg-card shadow-sm", children: /* @__PURE__ */ jsx3(X, { size: 10, "aria-hidden": true }) })
802
- }
803
- ) : null;
804
- const handleClass = connectable ? HANDLE_CONNECTABLE_CLASS : HANDLE_CLASS;
805
- const handles = /* @__PURE__ */ jsxs3(Fragment, { children: [
806
- !d.isRoot && /* @__PURE__ */ jsx3(
807
- Handle,
808
- {
809
- type: "target",
810
- position: targetPos,
811
- className: handleClass,
812
- style: compact ? compactHandleStyle(targetPos, isLR) : void 0
813
- }
814
- ),
815
- /* @__PURE__ */ jsx3(
816
- Handle,
817
- {
818
- type: "source",
819
- position: sourcePos,
820
- className: handleClass,
821
- style: compact ? compactHandleStyle(sourcePos, isLR) : void 0
822
- }
823
- )
824
- ] });
825
- if (compact) {
826
- return /* @__PURE__ */ jsxs3(
827
- "div",
828
- {
829
- className: `relative flex h-full w-full ${isLR ? "flex-col items-center" : "flex-row items-center"}`,
830
- children: [
831
- handles,
832
- /* @__PURE__ */ jsxs3(
833
- "span",
834
- {
835
- "data-testid": "wf-node-card",
836
- className: "relative flex items-center justify-center rounded-xl border bg-card shadow-sm transition-colors",
837
- style: {
838
- width: COMPACT_TILE,
839
- height: COMPACT_TILE,
840
- ...state ? statusBorder(state.status) : problemSeverity ? problemBorder(problemSeverity) : {
841
- borderColor: `color-mix(in srgb, ${accent} 40%, hsl(var(--border)))`
842
- },
843
- // The TILE is the compact node (the name beside it is unboxed), so
844
- // the selection ring belongs to it and not to the wider box.
845
- ...selected ? SELECTION_OUTLINE : {}
846
- },
847
- children: [
848
- /* @__PURE__ */ jsx3("span", { className: "wf-node-body-in flex items-center justify-center", children: /* @__PURE__ */ jsx3(
849
- NodeMark,
850
- {
851
- kind: d.kind,
852
- provider: d.provider,
853
- model: markModel,
854
- accent,
855
- tile: Math.round(COMPACT_TILE * 0.62)
856
- }
857
- ) }),
858
- problems && problemSeverity && /* @__PURE__ */ jsx3(
859
- ProblemMarker,
860
- {
861
- problems,
862
- className: "-left-1.5 -top-1.5 absolute"
863
- }
864
- ),
865
- triggerDeleteButton && /* @__PURE__ */ jsx3("span", { className: "-right-1.5 -top-1.5 absolute", children: triggerDeleteButton }),
866
- d.badge && !state && /* @__PURE__ */ jsx3("span", { className: "-right-1.5 -top-1.5 absolute rounded-full border border-border bg-card px-1.5 py-[1px] font-medium text-[10px] text-muted-foreground", children: d.badge }),
867
- state && /* @__PURE__ */ jsx3(
868
- "span",
869
- {
870
- className: `-right-1 -top-1 absolute h-2.5 w-2.5 rounded-full border-2 ${state.status === "running" ? "animate-pulse" : ""}`,
871
- ...state.status === "running" ? { "data-motion": "essential" } : {},
872
- style: {
873
- background: STATUS_COLOR[state.status],
874
- borderColor: "hsl(var(--card))"
875
- },
876
- "aria-label": STATUS_LABEL[state.status]
877
- }
878
- )
879
- ]
880
- }
881
- ),
882
- /* @__PURE__ */ jsxs3(
883
- "div",
884
- {
885
- className: `wf-node-body-in min-w-0 ${isLR ? "mt-2 w-full px-1 text-center" : "ml-2 flex-1 text-left"}`,
886
- children: [
887
- /* @__PURE__ */ jsx3(
888
- "div",
889
- {
890
- className: "truncate font-semibold text-[12px] text-foreground leading-tight",
891
- title: d.title,
892
- children: d.title
893
- }
894
- ),
895
- subtitle && /* @__PURE__ */ jsx3(
896
- "div",
897
- {
898
- className: "truncate text-[11px] text-muted-foreground leading-tight",
899
- title: subtitle,
900
- children: subtitle
901
- }
902
- ),
903
- (duration || cost) && /* @__PURE__ */ jsx3("div", { className: "truncate text-[10px] text-muted-foreground/90 leading-tight tabular-nums", children: [duration, cost].filter(Boolean).join(" \xB7 ") })
904
- ]
905
- }
906
- )
907
- ]
908
- }
909
- );
910
- }
911
- const cardShell = (children) => /* @__PURE__ */ jsxs3(
912
- "div",
913
- {
914
- "data-testid": "wf-node-card",
915
- className: "relative flex h-full w-full flex-col overflow-hidden rounded-xl border bg-card shadow-sm transition-colors",
916
- style: {
917
- ...runStatus ? statusBorder(runStatus) : problemSeverity ? problemBorder(problemSeverity) : {
918
- borderColor: `color-mix(in srgb, ${accent} 40%, hsl(var(--border)))`
919
- },
920
- ...selected ? SELECTION_OUTLINE : {}
921
- },
922
- children: [
923
- handles,
924
- children
925
- ]
926
- }
927
- );
928
- const identityRow = /* @__PURE__ */ jsxs3("div", { className: "flex shrink-0 items-center gap-2.5", children: [
929
- /* @__PURE__ */ jsx3(
930
- NodeMark,
931
- {
932
- kind: d.kind,
933
- provider: d.provider,
934
- model: markModel,
935
- accent,
936
- tile: 34
937
- }
938
- ),
939
- /* @__PURE__ */ jsxs3("div", { className: "min-w-0 flex-1", children: [
940
- /* @__PURE__ */ jsx3(
941
- "div",
942
- {
943
- className: "truncate font-semibold text-[13px] text-foreground leading-tight",
944
- title: d.title,
945
- children: d.title
946
- }
947
- ),
948
- subtitle && /* @__PURE__ */ jsx3(
949
- "div",
950
- {
951
- className: "truncate text-[11px] text-muted-foreground leading-tight",
952
- title: subtitle,
953
- children: subtitle
954
- }
955
- )
956
- ] }),
957
- problems && problemSeverity && /* @__PURE__ */ jsx3(ProblemMarker, { problems }),
958
- runStatus ? /* @__PURE__ */ jsx3(StatusPill, { status: runStatus }) : d.badge && /* @__PURE__ */ jsx3("span", { className: "shrink-0 rounded-full border border-border bg-surface-container-high px-2 py-[1px] font-medium text-[10px] text-muted-foreground", children: d.badge }),
959
- triggerDeleteButton
960
- ] });
961
- const footer = runStatus ? /* @__PURE__ */ jsx3(
962
- StatusFooter,
963
- {
964
- status: runStatus,
965
- rounds: isAgent ? state?.rounds : void 0,
966
- cost,
967
- tokens,
968
- elapsed: duration
969
- }
970
- ) : null;
971
- const descriptionBand = (emphasized) => d.description ? /* @__PURE__ */ jsx3(
972
- "p",
973
- {
974
- className: `mt-2 line-clamp-2 shrink-0 text-[11px] leading-snug ${emphasized ? "text-foreground" : "text-muted-foreground"}`,
975
- title: d.description,
976
- children: d.description
977
- }
978
- ) : null;
979
- const emptySlot = runStatus && !runOutput && emptySlotLabel(runStatus) ? /* @__PURE__ */ jsx3("div", { className: "mt-2 flex flex-1 items-center text-[11px] text-muted-foreground italic", children: emptySlotLabel(runStatus) }) : null;
980
- const outputWell = (rows) => runOutput ? /* @__PURE__ */ jsxs3("div", { className: "mt-2 min-h-0 grow shrink-0 rounded-lg border border-border bg-surface-container-high/60 px-2 py-1.5", children: [
981
- /* @__PURE__ */ jsx3("div", { className: "mb-1 font-semibold text-[9px] text-muted-foreground uppercase tracking-[0.09em]", children: runOutput.label }),
982
- /* @__PURE__ */ jsx3(
983
- NodeOutputBody,
984
- {
985
- shape: runOutput.shape,
986
- tone: runOutput.tone,
987
- rows
988
- }
989
- )
990
- ] }) : emptySlot;
991
- const body = (children, justify = "") => (
992
- /* The content region owns its overflow: each band is `shrink-0`, so a band
993
- that renders taller than its reservation (a consumer's font metrics) is
994
- CLIPPED here rather than squeezed — a squeezed band cuts a line of text
995
- in half, and pushes the pinned footer off the card. Fades in when the
996
- density swap lands mid layout-morph; the card's border/surface (the
997
- root) stays opaque so the node never blinks out. */
998
- /* @__PURE__ */ jsx3(
999
- "div",
1000
- {
1001
- className: `wf-node-body-in flex min-h-0 flex-1 flex-col overflow-hidden px-3.5 pt-2.5 pb-2 ${justify}`,
1002
- children
1003
- }
1004
- )
1005
- );
1006
- if (!runBands) {
1007
- return cardShell(body(
1008
- /* @__PURE__ */ jsxs3(Fragment, { children: [
1009
- identityRow,
1010
- descriptionBand(false)
1011
- ] })
1012
- ));
1013
- }
1014
- if (d.kind === "decision") {
1015
- return cardShell(
1016
- /* @__PURE__ */ jsxs3(Fragment, { children: [
1017
- body(
1018
- /* @__PURE__ */ jsxs3(Fragment, { children: [
1019
- /* @__PURE__ */ jsxs3("div", { className: "flex shrink-0 items-start gap-2.5", children: [
1020
- /* @__PURE__ */ jsx3(
1021
- NodeMark,
1022
- {
1023
- kind: d.kind,
1024
- provider: d.provider,
1025
- model: markModel,
1026
- accent,
1027
- tile: 34
1028
- }
1029
- ),
1030
- /* @__PURE__ */ jsxs3("div", { className: "min-w-0 flex-1", children: [
1031
- /* @__PURE__ */ jsx3(
1032
- "div",
1033
- {
1034
- className: "truncate font-semibold text-[13px] text-foreground leading-tight",
1035
- title: d.title,
1036
- children: d.title
1037
- }
1038
- ),
1039
- /* @__PURE__ */ jsxs3("div", { className: "mt-0.5 flex items-center gap-2", children: [
1040
- subtitle && /* @__PURE__ */ jsx3(
1041
- "span",
1042
- {
1043
- className: "truncate text-[11px] text-muted-foreground leading-tight",
1044
- title: subtitle,
1045
- children: subtitle
1046
- }
1047
- ),
1048
- runStatus && /* @__PURE__ */ jsx3("span", { className: "ml-auto shrink-0", children: /* @__PURE__ */ jsx3(StatusPill, { status: runStatus }) })
1049
- ] })
1050
- ] })
1051
- ] }),
1052
- runOutput && runStatus === "failed" ? /* @__PURE__ */ jsx3("div", { className: "mt-2 min-h-0 shrink-0", children: /* @__PURE__ */ jsx3(
1053
- NodeOutputBody,
1054
- {
1055
- shape: runOutput.shape,
1056
- tone: runOutput.tone,
1057
- rows: 2
1058
- }
1059
- ) }) : descriptionBand(true)
1060
- ] })
1061
- ),
1062
- footer
1063
- ] })
1064
- );
1065
- }
1066
- if (isStructuralKind(d.kind)) {
1067
- return cardShell(
1068
- /* @__PURE__ */ jsxs3(Fragment, { children: [
1069
- body(
1070
- /* @__PURE__ */ jsxs3(Fragment, { children: [
1071
- identityRow,
1072
- /* @__PURE__ */ jsx3("div", { className: "mt-1.5 min-h-0 shrink-0", children: runOutput && /* @__PURE__ */ jsx3(
1073
- NodeOutputBody,
1074
- {
1075
- shape: runOutput.shape,
1076
- tone: runOutput.tone,
1077
- rows: STRUCTURAL_OUTPUT_ROWS
1078
- }
1079
- ) })
1080
- ] }),
1081
- // Centered, because the failure slot is empty on every run that does
1082
- // NOT fail — which is most of them. Reserved space it cannot give back
1083
- // (the layout is fixed before the run) reads as a gap under a
1084
- // top-aligned row, and as breathing room around a centered one.
1085
- "justify-center"
1086
- ),
1087
- footer
1088
- ] })
1089
- );
1090
- }
1091
- if (isAgent) {
1092
- const strip = [d.title, subtitle].filter(Boolean).join(" \xB7 ");
1093
- return cardShell(
1094
- /* @__PURE__ */ jsxs3(Fragment, { children: [
1095
- body(
1096
- /* @__PURE__ */ jsxs3(Fragment, { children: [
1097
- /* @__PURE__ */ jsxs3("div", { className: "flex shrink-0 items-center gap-1.5", children: [
1098
- /* @__PURE__ */ jsx3(
1099
- NodeMark,
1100
- {
1101
- kind: d.kind,
1102
- provider: d.provider,
1103
- model: markModel,
1104
- accent,
1105
- tile: 18
1106
- }
1107
- ),
1108
- /* @__PURE__ */ jsx3(
1109
- "span",
1110
- {
1111
- className: "truncate font-medium text-[11px] text-muted-foreground",
1112
- title: strip,
1113
- children: strip
1114
- }
1115
- ),
1116
- /* @__PURE__ */ jsx3("span", { className: "flex-1" }),
1117
- runStatus && /* @__PURE__ */ jsx3(StatusPill, { status: runStatus })
1118
- ] }),
1119
- /* @__PURE__ */ jsx3("div", { className: "mt-2 min-h-0 flex-1 text-foreground leading-snug", children: runOutput ? /* @__PURE__ */ jsx3(
1120
- NodeOutputBody,
1121
- {
1122
- shape: runOutput.shape,
1123
- tone: runOutput.tone,
1124
- rows: AGENT_BODY_ROWS
1125
- }
1126
- ) : runStatus && runStatus !== "succeeded" && runStatus !== "failed" && d.description ? (
1127
- // What it is ABOUT to do, in the muted token — the answer, when it
1128
- // arrives, is the thing set in the foreground.
1129
- //
1130
- // Only while the node is still going to produce one. A FINISHED
1131
- // node that produced nothing must say so: showing its prompt
1132
- // there reads as the result it never returned.
1133
- /* @__PURE__ */ jsx3(
1134
- "p",
1135
- {
1136
- className: "line-clamp-5 text-[11px] text-muted-foreground leading-snug",
1137
- title: d.description,
1138
- children: d.description
1139
- }
1140
- )
1141
- ) : runStatus && /* @__PURE__ */ jsx3("span", { className: "text-muted-foreground italic", children: emptySlotLabel(runStatus) }) })
1142
- ] })
1143
- ),
1144
- footer
1145
- ] })
1146
- );
1147
- }
1148
- return cardShell(
1149
- /* @__PURE__ */ jsxs3(Fragment, { children: [
1150
- body(
1151
- /* @__PURE__ */ jsxs3(Fragment, { children: [
1152
- identityRow,
1153
- outputWell(ACTION_OUTPUT_ROWS)
1154
- ] })
1155
- ),
1156
- footer
1157
- ] })
1158
- );
1159
- }
1160
- function releasePoint(event) {
1161
- if (!event) return null;
1162
- const touch = "changedTouches" in event ? event.changedTouches[0] : null;
1163
- const source = touch ?? ("clientX" in event ? event : null);
1164
- return source ? { x: source.clientX, y: source.clientY } : null;
1165
- }
1166
- var CANVAS_SURFACE_SELECTOR = ".react-flow__pane, .react-flow__background";
1167
- function releasedOnCanvas(at, frame) {
1168
- if (!frame) return false;
1169
- const under = typeof document === "undefined" ? null : document.elementFromPoint(at.x, at.y);
1170
- if (!under?.matches(CANVAS_SURFACE_SELECTOR)) return false;
1171
- return frame.contains(under);
1172
- }
1173
- var NodeBoxesContext = createContext([]);
1174
- var EDGE_CHIP_CLASS = "rounded-full border border-border bg-card/90 px-1.5 py-[1px] text-[10px] leading-tight backdrop-blur";
1175
- function WfEdgeRenderer({
1176
- id,
1177
- source,
1178
- target,
1179
- sourceX,
1180
- sourceY,
1181
- targetX,
1182
- targetY,
1183
- sourcePosition,
1184
- targetPosition,
1185
- data,
1186
- markerEnd,
1187
- style
1188
- }) {
1189
- const insertStep = useContext(EdgeInsertContext);
1190
- const [path, labelX, labelY] = getSmoothStepPath({
1191
- sourceX,
1192
- sourceY,
1193
- sourcePosition,
1194
- targetX,
1195
- targetY,
1196
- targetPosition
1197
- });
1198
- const backEdge = data?.backEdge === true;
1199
- const whenLabel = data?.whenLabel;
1200
- const visits = data?.maxNodeVisits;
1201
- const problems = data?.problems;
1202
- const problemSeverity = worstSeverity(problems);
1203
- const offersInsert = data?.insertable === true && insertStep !== null;
1204
- const nodeBoxes = useContext(NodeBoxesContext);
1205
- const direction = useContext(DirectionContext);
1206
- const cleared = offersInsert || problemSeverity ? clearOfNodeBoxes(
1207
- { x: labelX, y: labelY },
1208
- nodeBoxes,
1209
- direction,
1210
- // A chip is far wider than the control, and in "TB" that width is what
1211
- // has to clear the cards — so the cluster is measured by what it
1212
- // actually carries rather than by its tallest member.
1213
- problemSeverity ? CLUSTER_HALF_SIZE.chip : CLUSTER_HALF_SIZE.control
1214
- ) : 0;
1215
- const clearance = cleared ?? 0;
1216
- const stealsPointer = cleared === null;
1217
- const isLR = direction === "LR";
1218
- const clusterX = labelX + (isLR ? 0 : clearance);
1219
- const clusterY = labelY + (isLR ? clearance : 0);
1220
- return /* @__PURE__ */ jsxs3(Fragment, { children: [
1221
- /* @__PURE__ */ jsx3(BaseEdge, { id, path, markerEnd, style }),
1222
- /* @__PURE__ */ jsx3(EdgeLabelRenderer, { children: /* @__PURE__ */ jsxs3(
1223
- "div",
1224
- {
1225
- className: "nodrag nopan absolute flex flex-col items-center gap-0.5",
1226
- style: {
1227
- zIndex: 1,
1228
- transform: `translate(-50%, -50%) translate(${clusterX}px, ${clusterY}px)`
1229
- },
1230
- children: [
1231
- problems && problemSeverity && /* @__PURE__ */ jsxs3(
1232
- "span",
1233
- {
1234
- "data-testid": "wf-edge-problem",
1235
- "data-severity": problemSeverity,
1236
- className: `${EDGE_CHIP_CLASS} max-w-40 truncate ${stealsPointer ? "" : "pointer-events-auto"}`,
1237
- style: problemSurface(problemSeverity),
1238
- children: [
1239
- /* @__PURE__ */ jsx3("span", { "aria-hidden": true, title: problemTitle(problems), children: problems.length === 1 ? problems[0].message : `${problems.length} problems` }),
1240
- /* @__PURE__ */ jsx3(ProblemMessages, { problems })
1241
- ]
1242
- }
1243
- ),
1244
- whenLabel && /* @__PURE__ */ jsx3(
1245
- "span",
1246
- {
1247
- title: whenLabel,
1248
- className: `${EDGE_CHIP_CLASS} max-w-40 truncate text-muted-foreground`,
1249
- children: whenLabel
1250
- }
1251
- ),
1252
- backEdge && /* @__PURE__ */ jsx3(
1253
- "span",
1254
- {
1255
- title: visits !== void 0 ? `Loops back \u2014 each node runs at most ${visits} time${visits === 1 ? "" : "s"} per run` : "Loops back to a step that has already run",
1256
- className: `${EDGE_CHIP_CLASS} shrink-0 text-foreground`,
1257
- children: visits !== void 0 ? `\u21BA \u2264${visits}` : "\u21BA"
1258
- }
1259
- ),
1260
- offersInsert && /* @__PURE__ */ jsx3(
1261
- "button",
1262
- {
1263
- type: "button",
1264
- "data-testid": "wf-edge-insert",
1265
- title: "Insert a step here",
1266
- "aria-label": "Insert a step here",
1267
- className: `flex h-5 w-5 items-center justify-center rounded-full border border-border bg-card text-muted-foreground shadow-sm backdrop-blur transition hover:border-primary hover:text-primary ${stealsPointer ? "" : "pointer-events-auto"}`,
1268
- onClick: (event) => {
1269
- event.stopPropagation();
1270
- insertStep(source, target);
1271
- },
1272
- children: /* @__PURE__ */ jsx3(Plus, { size: 12, "aria-hidden": true })
1273
- }
1274
- )
1275
- ]
1276
- }
1277
- ) })
1278
- ] });
1279
- }
1280
- var NODE_TYPES = { wfNode: WorkflowNode };
1281
- var EDGE_TYPES = { [WF_EDGE_TYPE]: WfEdgeRenderer };
1282
- function buildStyledEdges(base, nodeState, maxNodeVisits) {
1283
- return base.map((e) => {
1284
- const status = nodeState?.[e.target]?.status;
1285
- const color = edgeColor(nodeState ? status : void 0);
1286
- const backEdge = e.data?.backEdge === true;
1287
- return {
1288
- ...e,
1289
- // The active hop flows; everything else is static. A cycle-closing edge
1290
- // never flows even into a running target: it is the loop's RETURN path,
1291
- // and animating it would read as the run travelling backwards.
1292
- animated: !backEdge && status === "running",
1293
- style: {
1294
- strokeWidth: 1.75,
1295
- stroke: color,
1296
- // Dashed, so a return path is distinguishable from a forward hop before
1297
- // its badge is read — and at a zoom where the badge isn't legible.
1298
- ...backEdge ? { strokeDasharray: "6 3" } : {}
1299
- },
1300
- markerEnd: {
1301
- type: MarkerType.ArrowClosed,
1302
- width: 15,
1303
- height: 15,
1304
- color
1305
- },
1306
- ...backEdge && maxNodeVisits !== void 0 && e.data ? { data: { ...e.data, maxNodeVisits } } : {}
1307
- };
1308
- });
1309
- }
1310
- function decorateAuthoringEdges(edges, byEdge, insertable, hasRunOverlay) {
1311
- if (byEdge.size === 0 && !edges.some(insertable)) return edges;
1312
- return edges.map((e) => {
1313
- const problems = byEdge.get(e.id);
1314
- const severity = worstSeverity(problems);
1315
- const offersInsert = insertable(e);
1316
- if (!severity && !offersInsert) return e;
1317
- const stroke = severity && !hasRunOverlay ? problemSurface(severity).color : void 0;
1318
- return {
1319
- ...e,
1320
- type: WF_EDGE_TYPE,
1321
- data: {
1322
- ...e.data ?? { kind: "spine" },
1323
- ...problems ? { problems } : {},
1324
- ...offersInsert ? { insertable: true } : {}
1325
- },
1326
- ...stroke ? {
1327
- style: { ...e.style, stroke },
1328
- markerEnd: e.markerEnd && typeof e.markerEnd === "object" ? { ...e.markerEnd, color: stroke } : e.markerEnd
1329
- } : {}
1330
- };
1331
- });
1332
- }
1333
- function WorkflowGraph({
1334
- yaml,
1335
- variant = "full",
1336
- direction = "LR",
1337
- defaultCompact,
1338
- onCompactChange,
1339
- className,
1340
- wrap = false,
1341
- nodeState,
1342
- edges: declaredEdges,
1343
- maxNodeVisits,
1344
- selectedNodeId,
1345
- onNodeClick,
1346
- onEdgeConnect,
1347
- onEdgeDelete,
1348
- onEdgeClick,
1349
- onEdgeInsert,
1350
- onNodeInsert,
1351
- onTriggerAdd,
1352
- onTriggerDelete,
1353
- problems
1354
- }) {
1355
- const editable = onEdgeConnect !== void 0;
1356
- const insertOnEdge = editable ? onEdgeInsert : void 0;
1357
- const insertArmed = insertOnEdge !== void 0;
1358
- const insertAtNode = editable ? onNodeInsert : void 0;
1359
- const addTrigger = editable ? onTriggerAdd : void 0;
1360
- const deleteTrigger = editable ? onTriggerDelete : void 0;
1361
- const colorMode = useColorMode();
1362
- const isPreview = variant === "preview";
1363
- const [userCompact, setUserCompact] = useState(
1364
- defaultCompact ?? nodeState === void 0
1365
- );
1366
- const nextDensityFrom = useRef(userCompact);
1367
- const compact = isPreview || userCompact;
1368
- const [displayCompact, setDisplayCompact] = useState(compact);
1369
- const rfRef = useRef(
1370
- null
1371
- );
1372
- const wrapperRef = useRef(null);
1373
- const framedRef = useRef(false);
1374
- const [framingCue, setFramingCue] = useState(0);
1375
- const hasRunOverlay = nodeState !== void 0;
1376
- const structural = useMemo(
1377
- () => buildFlowGraph(yaml, {
1378
- nodeState: hasRunOverlay ? {} : void 0,
1379
- direction,
1380
- compact,
1381
- wrap,
1382
- // The insert control lives IN the gap between two layers, so the gap is
1383
- // reserved here — where the layout is decided — rather than measured
1384
- // afterwards. It is the one editing prop that reaches the layout, which
1385
- // is why arming it re-lays the graph out.
1386
- reserveEdgeInsert: insertArmed,
1387
- ...declaredEdges ? { edges: declaredEdges } : {}
1388
- }),
1389
- [
1390
- yaml,
1391
- hasRunOverlay,
1392
- direction,
1393
- compact,
1394
- wrap,
1395
- insertArmed,
1396
- declaredEdges
1397
- ]
1398
- );
1399
- const nodeBoxes = useMemo(
1400
- () => structural.nodes.map((node) => ({
1401
- x: node.position.x,
1402
- y: node.position.y,
1403
- width: node.width ?? 0,
1404
- height: node.height ?? 0
1405
- })),
1406
- [structural]
1407
- );
1408
- const problemIndex = useMemo(() => indexProblems(problems), [problems]);
1409
- const styledEdges = useMemo(() => {
1410
- const run = buildStyledEdges(structural.edges, nodeState, maxNodeVisits);
1411
- const styled = decorateAuthoringEdges(
1412
- run,
1413
- problemIndex.byEdge,
1414
- (edge) => insertArmed && isEditableEdge(edge),
1415
- hasRunOverlay
1416
- );
1417
- return styled.map((e) => ({
1418
- ...e,
1419
- deletable: editable ? isEditableEdge(e) : false
1420
- }));
1421
- }, [
1422
- structural.edges,
1423
- nodeState,
1424
- maxNodeVisits,
1425
- editable,
1426
- problemIndex,
1427
- insertArmed,
1428
- hasRunOverlay
1429
- ]);
1430
- const [nodes, setNodes, onNodesChange] = useNodesState(structural.nodes);
1431
- const [edges, setEdges, onEdgesChange] = useEdgesState(styledEdges);
1432
- const nodesRef = useRef(nodes);
1433
- nodesRef.current = nodes;
1434
- const nodeStateRef = useRef(nodeState);
1435
- nodeStateRef.current = nodeState;
1436
- useEffect(() => {
1437
- setEdges(styledEdges);
1438
- }, [styledEdges, setEdges]);
1439
- useEffect(() => {
1440
- const baseById = new Map(structural.nodes.map((n) => [n.id, n.data]));
1441
- setNodes((prev) => mergeRunState(prev, baseById, nodeState));
1442
- }, [nodeState, structural, setNodes]);
1443
- const didInitialSeedRef = useRef(false);
1444
- useEffect(() => {
1445
- const withRunState = (nodes2) => {
1446
- const state = nodeStateRef.current;
1447
- if (!state) return nodes2;
1448
- return nodes2.map(
1449
- (n) => state[n.id] ? { ...n, data: { ...n.data, state: state[n.id] } } : n
1450
- );
1451
- };
1452
- if (!didInitialSeedRef.current) {
1453
- didInitialSeedRef.current = true;
1454
- setNodes(withRunState(structural.nodes));
1455
- setDisplayCompact(compact);
1456
- return;
1457
- }
1458
- const inst = rfRef.current;
1459
- const frame = wrapperRef.current?.getBoundingClientRect();
1460
- if (!inst || !frame || frame.width === 0 || frame.height === 0) {
1461
- setNodes(withRunState(structural.nodes));
1462
- setDisplayCompact(compact);
1463
- return;
1464
- }
1465
- const endViewport = framingViewport(
1466
- layoutBounds(structural.nodes),
1467
- frame.width,
1468
- frame.height,
1469
- compact
1470
- );
1471
- framedRef.current = true;
1472
- const prev = nodesRef.current;
1473
- const prevById = new Map(prev.map((n) => [n.id, n]));
1474
- const sameNodeSet = prev.length === structural.nodes.length && structural.nodes.every((n) => prevById.has(n.id));
1475
- if (!sameNodeSet || !motionAllowed() || typeof requestAnimationFrame === "undefined") {
1476
- setNodes(withRunState(structural.nodes));
1477
- setDisplayCompact(compact);
1478
- inst.setViewport(endViewport);
1479
- return;
1480
- }
1481
- const flipTimer = setTimeout(
1482
- () => setDisplayCompact(compact),
1483
- LAYOUT_TRANSITION_MS * (compact ? DENSITY_FLIP_AT.shrink : DENSITY_FLIP_AT.grow)
1484
- );
1485
- const startViewport = inst.getViewport();
1486
- const startedAt = performance.now();
1487
- let raf = requestAnimationFrame(function step(now) {
1488
- const t = Math.min(1, (now - startedAt) / LAYOUT_TRANSITION_MS);
1489
- const e = easeInOutCubic(t);
1490
- setNodes(
1491
- withRunState(
1492
- t >= 1 ? structural.nodes : structural.nodes.map((n) => {
1493
- const p = prevById.get(n.id);
1494
- if (!p || p.width === void 0 || p.height === void 0) {
1495
- return n;
1496
- }
1497
- const width = lerp(p.width, n.width ?? p.width, e);
1498
- const height = lerp(p.height, n.height ?? p.height, e);
1499
- return {
1500
- ...n,
1501
- position: {
1502
- x: lerp(p.position.x, n.position.x, e),
1503
- y: lerp(p.position.y, n.position.y, e)
1504
- },
1505
- width,
1506
- height,
1507
- style: { ...n.style, width, height }
1508
- };
1509
- })
1510
- )
1511
- );
1512
- inst.setViewport({
1513
- x: lerp(startViewport.x, endViewport.x, e),
1514
- y: lerp(startViewport.y, endViewport.y, e),
1515
- zoom: lerp(startViewport.zoom, endViewport.zoom, e)
1516
- });
1517
- if (t < 1) raf = requestAnimationFrame(step);
1518
- });
1519
- return () => {
1520
- cancelAnimationFrame(raf);
1521
- clearTimeout(flipTimer);
1522
- };
1523
- }, [structural, compact, setNodes]);
1524
- useEffect(() => {
1525
- if (framedRef.current) return;
1526
- const inst = rfRef.current;
1527
- const frame = wrapperRef.current?.getBoundingClientRect();
1528
- if (!inst || !frame || frame.width === 0 || frame.height === 0) return;
1529
- inst.setViewport(
1530
- framingViewport(
1531
- layoutBounds(structural.nodes),
1532
- frame.width,
1533
- frame.height,
1534
- compact
1535
- )
1536
- );
1537
- framedRef.current = true;
1538
- }, [framingCue, structural, compact]);
1539
- useEffect(() => {
1540
- const el = wrapperRef.current;
1541
- if (!el || typeof ResizeObserver === "undefined") return;
1542
- const observer = new ResizeObserver(() => {
1543
- if (framedRef.current) {
1544
- observer.disconnect();
1545
- return;
1546
- }
1547
- setFramingCue((cue) => cue + 1);
1548
- });
1549
- observer.observe(el);
1550
- return () => observer.disconnect();
1551
- }, []);
1552
- const handleNodeClick = useCallback(
1553
- (_event, node) => {
1554
- onNodeClick?.(node.id, node.data);
1555
- },
1556
- [onNodeClick]
1557
- );
1558
- const handleConnectEnd = useCallback(
1559
- (event, state) => {
1560
- if (!insertAtNode) return;
1561
- if (state.toNode || state.toHandle) return;
1562
- const from = state.fromNode;
1563
- const fromHandle = state.fromHandle;
1564
- if (!from || !fromHandle) return;
1565
- const at = releasePoint(event);
1566
- if (!at || !releasedOnCanvas(at, wrapperRef.current)) return;
1567
- insertAtNode(from.id, fromHandle.type === "target" ? "before" : "after");
1568
- },
1569
- [insertAtNode]
1570
- );
1571
- const handleNodeKeyDown = useCallback(
1572
- (event) => {
1573
- if (event.key !== "Enter" && event.key !== " ") return;
1574
- if (!(event.target instanceof HTMLElement)) return;
1575
- const nodeId = event.target.closest(".react-flow__node")?.getAttribute("data-id");
1576
- if (!nodeId) return;
1577
- const node = nodesRef.current.find((n) => n.id === nodeId);
1578
- if (!node) return;
1579
- event.preventDefault();
1580
- event.stopPropagation();
1581
- onNodeClick?.(node.id, node.data);
1582
- },
1583
- [onNodeClick]
1584
- );
1585
- if (structural.error || structural.nodes.length === 0) {
1586
- return /* @__PURE__ */ jsx3(
1587
- "div",
1588
- {
1589
- className: `flex items-center justify-center rounded-lg border border-border border-dashed p-4 text-center text-muted-foreground text-xs ${className ?? ""}`,
1590
- children: structural.error ?? "Nothing to show"
1591
- }
1592
- );
1593
- }
1594
- return /* @__PURE__ */ jsx3(DirectionContext.Provider, { value: direction, children: /* @__PURE__ */ jsx3(DensityContext.Provider, { value: displayCompact, children: /* @__PURE__ */ jsx3(RunModeContext.Provider, { value: hasRunOverlay, children: /* @__PURE__ */ jsx3(SelectedNodeContext.Provider, { value: selectedNodeId, children: /* @__PURE__ */ jsx3(ConnectableContext.Provider, { value: editable, children: /* @__PURE__ */ jsx3(NodeBoxesContext.Provider, { value: nodeBoxes, children: /* @__PURE__ */ jsx3(NodeProblemsContext.Provider, { value: problemIndex.byNode, children: /* @__PURE__ */ jsx3(TriggerDeleteContext.Provider, { value: deleteTrigger ?? null, children: /* @__PURE__ */ jsx3(EdgeInsertContext.Provider, { value: insertOnEdge ?? null, children: /* @__PURE__ */ jsx3("div", { ref: wrapperRef, className: `wf-graph ${className ?? ""}`, children: /* @__PURE__ */ jsxs3(
1595
- ReactFlow,
1596
- {
1597
- nodes,
1598
- edges,
1599
- onNodesChange,
1600
- onEdgesChange,
1601
- nodeTypes: NODE_TYPES,
1602
- edgeTypes: EDGE_TYPES,
1603
- colorMode,
1604
- onInit: (inst) => {
1605
- rfRef.current = inst;
1606
- if (!framedRef.current) setFramingCue((cue) => cue + 1);
1607
- },
1608
- onNodeClick: onNodeClick ? handleNodeClick : void 0,
1609
- onKeyDown: onNodeClick ? handleNodeKeyDown : void 0,
1610
- fitView: true,
1611
- fitViewOptions: {
1612
- ...FIT_VIEW,
1613
- minZoom: fitZoomFloor(compact),
1614
- maxZoom: fitZoomCeiling(compact)
1615
- },
1616
- proOptions: { hideAttribution: true },
1617
- nodesDraggable: !isPreview,
1618
- nodesConnectable: editable,
1619
- deleteKeyCode: editable ? ["Backspace", "Delete"] : null,
1620
- edgesReconnectable: false,
1621
- elementsSelectable: !isPreview,
1622
- edgesFocusable: editable,
1623
- onConnect: editable ? (connection) => {
1624
- if (connection.source && connection.target) {
1625
- onEdgeConnect(connection.source, connection.target);
1626
- }
1627
- } : void 0,
1628
- onEdgesDelete: editable ? (deleted) => {
1629
- for (const edge of deleted) {
1630
- if (isEditableEdge(edge)) {
1631
- onEdgeDelete?.(edge.source, edge.target);
1632
- }
1633
- }
1634
- } : void 0,
1635
- onConnectEnd: insertAtNode ? handleConnectEnd : void 0,
1636
- onEdgeClick: editable ? (_event, edge) => {
1637
- if (isEditableEdge(edge)) {
1638
- onEdgeClick?.(edge.source, edge.target);
1639
- }
1640
- } : void 0,
1641
- zoomOnScroll: false,
1642
- zoomOnPinch: true,
1643
- zoomOnDoubleClick: !isPreview,
1644
- panOnDrag: true,
1645
- preventScrolling: false,
1646
- minZoom: 0.2,
1647
- maxZoom: 1.5,
1648
- children: [
1649
- /* @__PURE__ */ jsx3(Background, { gap: 18 }),
1650
- !isPreview && /* @__PURE__ */ jsxs3(Fragment, { children: [
1651
- /* @__PURE__ */ jsx3(
1652
- Controls,
1653
- {
1654
- showInteractive: false,
1655
- position: "bottom-right",
1656
- fitViewOptions: { padding: FIT_VIEW.padding }
1657
- }
1658
- ),
1659
- /* @__PURE__ */ jsxs3(Panel, { position: "top-left", className: "flex items-center gap-1", children: [
1660
- addTrigger && /* @__PURE__ */ jsxs3(
1661
- "button",
1662
- {
1663
- type: "button",
1664
- "data-testid": "wf-trigger-add",
1665
- onClick: addTrigger,
1666
- title: "Add a trigger",
1667
- "aria-label": "Add a trigger",
1668
- className: "flex items-center gap-1 rounded-md border border-border bg-card/80 px-2 py-1 text-[11px] text-muted-foreground shadow-sm backdrop-blur transition hover:text-foreground",
1669
- children: [
1670
- /* @__PURE__ */ jsx3(Plus, { size: 12, "aria-hidden": true }),
1671
- "Trigger"
1672
- ]
1673
- }
1674
- ),
1675
- /* @__PURE__ */ jsxs3(
1676
- "button",
1677
- {
1678
- type: "button",
1679
- onClick: () => {
1680
- const next = !nextDensityFrom.current;
1681
- nextDensityFrom.current = next;
1682
- setUserCompact(next);
1683
- onCompactChange?.(next);
1684
- },
1685
- "aria-pressed": userCompact,
1686
- title: userCompact ? "Expand nodes" : "Collapse nodes",
1687
- className: "flex items-center gap-1 rounded-md border border-border bg-card/80 px-2 py-1 text-[11px] text-muted-foreground shadow-sm backdrop-blur transition hover:text-foreground",
1688
- children: [
1689
- userCompact ? /* @__PURE__ */ jsx3(Maximize2, { size: 12 }) : /* @__PURE__ */ jsx3(Minimize2, { size: 12 }),
1690
- userCompact ? "Expand" : "Compact"
1691
- ]
1692
- }
1693
- )
1694
- ] })
1695
- ] })
1696
- ]
1697
- }
1698
- ) }) }) }) }) }) }) }) }) }) });
1699
- }
1700
- export {
1701
- ConnectableContext,
1702
- DensityContext,
1703
- DirectionContext,
1704
- EdgeInsertContext,
1705
- FIT_VIEW,
1706
- LAYOUT_TRANSITION_MS,
1707
- NodeBoxesContext,
1708
- NodeProblemsContext,
1709
- RunModeContext,
1710
- SelectedNodeContext,
1711
- TriggerDeleteContext,
1712
- WfEdgeRenderer,
1713
- WorkflowGraph,
1714
- WorkflowNode,
1715
- buildStyledEdges,
1716
- decorateAuthoringEdges,
1717
- fitZoomCeiling,
1718
- fitZoomFloor,
1719
- framingViewport,
1720
- isEditableEdge,
1721
- layoutBounds
1722
- };