@tangle-network/sandbox-ui 0.75.0 → 0.76.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.
- package/dist/WorkflowGraph-V2AVCNDC.js +795 -0
- package/dist/chunk-UEMWLVVY.js +606 -0
- package/dist/globals.css +44 -36
- package/dist/styles.css +44 -36
- package/dist/workflows.d.ts +47 -31
- package/dist/workflows.js +2 -2
- package/package.json +1 -1
- package/dist/WorkflowGraph-RA7ZHN4I.js +0 -636
- package/dist/chunk-FNE5QLU3.js +0 -344
|
@@ -0,0 +1,795 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ProviderIcon
|
|
3
|
+
} from "./chunk-GTJ5PSK5.js";
|
|
4
|
+
import {
|
|
5
|
+
COMPACT_NODE_SIZE,
|
|
6
|
+
COMPACT_TILE,
|
|
7
|
+
buildWorkflowGraph,
|
|
8
|
+
clampPreview,
|
|
9
|
+
fmtCost,
|
|
10
|
+
fmtDuration,
|
|
11
|
+
fmtTokens,
|
|
12
|
+
providerLabel,
|
|
13
|
+
shortModel
|
|
14
|
+
} from "./chunk-UEMWLVVY.js";
|
|
15
|
+
import "./chunk-U67V476Y.js";
|
|
16
|
+
|
|
17
|
+
// src/workflows/WorkflowGraph.tsx
|
|
18
|
+
import {
|
|
19
|
+
Background,
|
|
20
|
+
Controls,
|
|
21
|
+
Handle,
|
|
22
|
+
MarkerType,
|
|
23
|
+
Panel,
|
|
24
|
+
Position as Position2,
|
|
25
|
+
ReactFlow,
|
|
26
|
+
useEdgesState,
|
|
27
|
+
useNodesState
|
|
28
|
+
} from "@xyflow/react";
|
|
29
|
+
import "@xyflow/react/dist/style.css";
|
|
30
|
+
import { Maximize2, Minimize2 } from "lucide-react";
|
|
31
|
+
import {
|
|
32
|
+
createContext,
|
|
33
|
+
useCallback,
|
|
34
|
+
useContext,
|
|
35
|
+
useEffect,
|
|
36
|
+
useMemo,
|
|
37
|
+
useRef,
|
|
38
|
+
useState
|
|
39
|
+
} from "react";
|
|
40
|
+
|
|
41
|
+
// src/workflows/flow-graph.ts
|
|
42
|
+
import { Position } from "@xyflow/react";
|
|
43
|
+
function isNodeStateMap(arg) {
|
|
44
|
+
const values = Object.values(arg);
|
|
45
|
+
return values.length > 0 && values.every(
|
|
46
|
+
(v) => v !== null && typeof v === "object" && "status" in v
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
function normalizeFlowGraphOptions(arg) {
|
|
50
|
+
if (!arg) return {};
|
|
51
|
+
if ("nodeState" in arg || "direction" in arg || "compact" in arg) {
|
|
52
|
+
return arg;
|
|
53
|
+
}
|
|
54
|
+
return isNodeStateMap(arg) ? { nodeState: arg } : {};
|
|
55
|
+
}
|
|
56
|
+
function buildFlowGraph(yaml, optionsOrNodeState) {
|
|
57
|
+
const options = normalizeFlowGraphOptions(optionsOrNodeState);
|
|
58
|
+
const nodeState = options.nodeState;
|
|
59
|
+
const direction = options.direction ?? "LR";
|
|
60
|
+
const compact = options.compact ?? false;
|
|
61
|
+
const graph = buildWorkflowGraph(yaml, {
|
|
62
|
+
reserveRunState: nodeState !== void 0,
|
|
63
|
+
direction,
|
|
64
|
+
compact
|
|
65
|
+
});
|
|
66
|
+
const isLR = direction === "LR";
|
|
67
|
+
const sourcePosition = isLR ? Position.Right : Position.Bottom;
|
|
68
|
+
const targetPosition = isLR ? Position.Left : Position.Top;
|
|
69
|
+
return {
|
|
70
|
+
error: graph.error,
|
|
71
|
+
nodes: graph.nodes.map(
|
|
72
|
+
(n) => ({
|
|
73
|
+
id: n.id,
|
|
74
|
+
type: "wfNode",
|
|
75
|
+
position: n.position,
|
|
76
|
+
width: n.width,
|
|
77
|
+
height: n.height,
|
|
78
|
+
// Fix the DOM box to the laid-out size so the card can't grow past its
|
|
79
|
+
// reserved space and overlap a neighbour (see model.ts nodeHeight).
|
|
80
|
+
style: { width: n.width, height: n.height },
|
|
81
|
+
sourcePosition,
|
|
82
|
+
targetPosition,
|
|
83
|
+
data: nodeState?.[n.id] ? { ...n.data, state: nodeState[n.id] } : n.data
|
|
84
|
+
})
|
|
85
|
+
),
|
|
86
|
+
edges: graph.edges.map((e) => ({
|
|
87
|
+
id: e.id,
|
|
88
|
+
source: e.source,
|
|
89
|
+
target: e.target,
|
|
90
|
+
type: "smoothstep"
|
|
91
|
+
}))
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function sameRunState(a, b) {
|
|
95
|
+
if (a === b) return true;
|
|
96
|
+
if (!a || !b) return false;
|
|
97
|
+
const aKeys = Object.keys(a);
|
|
98
|
+
const bKeys = Object.keys(b);
|
|
99
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
100
|
+
return aKeys.every((k) => a[k] === b[k]);
|
|
101
|
+
}
|
|
102
|
+
function mergeRunState(prev, baseDataById, nodeState) {
|
|
103
|
+
return prev.map((n) => {
|
|
104
|
+
const base = baseDataById.get(n.id) ?? n.data;
|
|
105
|
+
const s = nodeState?.[n.id];
|
|
106
|
+
const nextData = s ? { ...base, state: s } : base;
|
|
107
|
+
return sameRunState(n.data.state, nextData.state) ? n : { ...n, data: nextData };
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// src/workflows/node-output.tsx
|
|
112
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
113
|
+
var ERROR_TEXT = { color: "var(--surface-danger-text)" };
|
|
114
|
+
var MAX_JSON_ENTRIES = 6;
|
|
115
|
+
function isStructureOnly(line) {
|
|
116
|
+
if (/^(```|~~~)/.test(line)) return true;
|
|
117
|
+
const isBreak = /^([-*_])\1{2,}$/.test(line);
|
|
118
|
+
if (/^[\s|:-]+$/.test(line) && line.includes("|")) return line.includes("-");
|
|
119
|
+
return isBreak;
|
|
120
|
+
}
|
|
121
|
+
function condenseText(text) {
|
|
122
|
+
const isListItem = (line) => /^\s*([-*+]\s|\d+[.)]\s)/.test(line);
|
|
123
|
+
const lines = text.split("\n").map((line) => ({
|
|
124
|
+
// A list item keeps its boundary when the lines are joined: run three
|
|
125
|
+
// bullets together with plain spaces and they read as one long sentence,
|
|
126
|
+
// which is the "word dump" all over again.
|
|
127
|
+
item: isListItem(line),
|
|
128
|
+
text: line.replace(/^\s{0,3}#{1,6}\s+/, "").replace(/^\s{0,3}>\s?/, "").replace(/^\s*[-*+]\s+(\[[ xX]\]\s+)?/, "").replace(/^\s*\d+[.)]\s+/, "").trim()
|
|
129
|
+
})).filter(({ text: line }) => line !== "" && !isStructureOnly(line));
|
|
130
|
+
return lines.map(
|
|
131
|
+
({ item, text: line }, i) => i > 0 && item ? `\xB7 ${line}` : line
|
|
132
|
+
).join(" ").replace(
|
|
133
|
+
/(?<=^|\s)(\*\*|~~)(?!\s)(.+?)(?<!\s)\1(?=[\s.,:;!?)\]}"']|$)/g,
|
|
134
|
+
"$2"
|
|
135
|
+
).replace(/(?<![\w*])\*(?!\s)([^*]+?)(?<!\s)\*(?![\w*])/g, "$1").replace(/`([^`]+)`/g, "$1").replace(/!?\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/\s+/g, " ").trim();
|
|
136
|
+
}
|
|
137
|
+
function scalar(value) {
|
|
138
|
+
if (value === null) return "null";
|
|
139
|
+
if (typeof value === "string") return value;
|
|
140
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
141
|
+
return String(value);
|
|
142
|
+
}
|
|
143
|
+
if (Array.isArray(value)) {
|
|
144
|
+
return `[${value.length} item${value.length === 1 ? "" : "s"}]`;
|
|
145
|
+
}
|
|
146
|
+
if (typeof value === "object") return "{\u2026}";
|
|
147
|
+
return "";
|
|
148
|
+
}
|
|
149
|
+
function classifyOutput(preview, condenseMarkdown = false) {
|
|
150
|
+
let trimmed = preview?.trim() ?? "";
|
|
151
|
+
trimmed = trimmed.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])/g, "").replace(/(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "").trimEnd();
|
|
152
|
+
if (trimmed === "") return { kind: "empty" };
|
|
153
|
+
const structured = trimmed.startsWith("{") || trimmed.startsWith("[");
|
|
154
|
+
if (structured) {
|
|
155
|
+
try {
|
|
156
|
+
const parsed = JSON.parse(trimmed);
|
|
157
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
158
|
+
const all = Object.entries(parsed);
|
|
159
|
+
const entries = all.slice(0, MAX_JSON_ENTRIES).map(([k, v]) => [k, scalar(v)]);
|
|
160
|
+
if (entries.length > 0) {
|
|
161
|
+
return { kind: "json", entries, truncated: all.length > entries.length };
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return { kind: "code", text: trimmed };
|
|
165
|
+
} catch {
|
|
166
|
+
return { kind: "code", text: trimmed };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (!condenseMarkdown) return { kind: "text", text: trimmed };
|
|
170
|
+
const condensed = condenseText(trimmed);
|
|
171
|
+
return condensed === "" ? { kind: "empty" } : { kind: "text", text: condensed };
|
|
172
|
+
}
|
|
173
|
+
var CLAMP_BY_ROWS = {
|
|
174
|
+
1: "truncate",
|
|
175
|
+
2: "line-clamp-2",
|
|
176
|
+
3: "line-clamp-3"
|
|
177
|
+
};
|
|
178
|
+
function NodeOutputBody({
|
|
179
|
+
shape,
|
|
180
|
+
rows = 2,
|
|
181
|
+
tone = "default"
|
|
182
|
+
}) {
|
|
183
|
+
const clampClass = CLAMP_BY_ROWS[rows] ?? "line-clamp-2";
|
|
184
|
+
const codeWrap = rows === 1 ? "" : "whitespace-pre-wrap break-all";
|
|
185
|
+
if (shape.kind === "empty") return null;
|
|
186
|
+
if (shape.kind === "json") {
|
|
187
|
+
const overflow = shape.truncated || shape.entries.length > rows;
|
|
188
|
+
const visible = shape.entries.slice(0, overflow ? Math.max(0, rows - 1) : rows);
|
|
189
|
+
const isError = tone === "error";
|
|
190
|
+
const keyStyle = isError ? ERROR_TEXT : void 0;
|
|
191
|
+
const keyClass = isError ? "" : "text-muted-foreground";
|
|
192
|
+
const valueClass = isError ? "" : "text-foreground";
|
|
193
|
+
return /* @__PURE__ */ jsxs("dl", { className: "space-y-0.5 font-mono text-[10.5px] leading-snug", children: [
|
|
194
|
+
visible.map(([key, value]) => /* @__PURE__ */ jsxs("div", { className: "flex gap-1.5", children: [
|
|
195
|
+
/* @__PURE__ */ jsx(
|
|
196
|
+
"dt",
|
|
197
|
+
{
|
|
198
|
+
className: `max-w-[45%] shrink-0 truncate ${keyClass}`,
|
|
199
|
+
style: keyStyle,
|
|
200
|
+
title: key,
|
|
201
|
+
children: key
|
|
202
|
+
}
|
|
203
|
+
),
|
|
204
|
+
/* @__PURE__ */ jsx(
|
|
205
|
+
"dd",
|
|
206
|
+
{
|
|
207
|
+
className: `min-w-0 flex-1 truncate ${valueClass}`,
|
|
208
|
+
style: isError ? ERROR_TEXT : void 0,
|
|
209
|
+
title: value,
|
|
210
|
+
children: value
|
|
211
|
+
}
|
|
212
|
+
)
|
|
213
|
+
] }, key)),
|
|
214
|
+
overflow && /* @__PURE__ */ jsx("div", { className: `text-[10px] ${keyClass}`, style: keyStyle, children: "\u2026" })
|
|
215
|
+
] });
|
|
216
|
+
}
|
|
217
|
+
if (shape.kind === "code") {
|
|
218
|
+
return /* @__PURE__ */ jsx(
|
|
219
|
+
"pre",
|
|
220
|
+
{
|
|
221
|
+
className: `${clampClass} ${codeWrap} font-mono text-[10.5px] leading-snug ${tone === "error" ? "" : "text-foreground"}`,
|
|
222
|
+
style: tone === "error" ? ERROR_TEXT : void 0,
|
|
223
|
+
title: shape.text,
|
|
224
|
+
children: shape.text
|
|
225
|
+
}
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
return /* @__PURE__ */ jsx(
|
|
229
|
+
"p",
|
|
230
|
+
{
|
|
231
|
+
className: `${clampClass} text-[11px] leading-snug ${tone === "error" ? "" : "text-foreground"}`,
|
|
232
|
+
style: tone === "error" ? ERROR_TEXT : void 0,
|
|
233
|
+
title: shape.text,
|
|
234
|
+
children: shape.text
|
|
235
|
+
}
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// src/workflows/node-ui.tsx
|
|
240
|
+
import {
|
|
241
|
+
Bell,
|
|
242
|
+
Box,
|
|
243
|
+
Cable,
|
|
244
|
+
Circle,
|
|
245
|
+
Clock,
|
|
246
|
+
GitBranch,
|
|
247
|
+
Repeat,
|
|
248
|
+
Sparkles,
|
|
249
|
+
UserCheck,
|
|
250
|
+
Webhook,
|
|
251
|
+
Zap
|
|
252
|
+
} from "lucide-react";
|
|
253
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
254
|
+
var TONE_ACCENT = {
|
|
255
|
+
trigger: "hsl(var(--primary))",
|
|
256
|
+
structural: "var(--surface-warning-text)",
|
|
257
|
+
action: "hsl(var(--muted-foreground))"
|
|
258
|
+
};
|
|
259
|
+
var STATUS_QUEUED = "hsl(var(--muted-foreground))";
|
|
260
|
+
var STATUS_DONE = "var(--surface-success-text)";
|
|
261
|
+
var STATUS_FAILED = "var(--surface-danger-text)";
|
|
262
|
+
var STATUS_RUNNING = "hsl(var(--primary))";
|
|
263
|
+
var STATUS_COLOR = {
|
|
264
|
+
queued: STATUS_QUEUED,
|
|
265
|
+
running: STATUS_RUNNING,
|
|
266
|
+
succeeded: STATUS_DONE,
|
|
267
|
+
failed: STATUS_FAILED
|
|
268
|
+
};
|
|
269
|
+
function edgeColor(status) {
|
|
270
|
+
return status ? STATUS_COLOR[status] : STATUS_QUEUED;
|
|
271
|
+
}
|
|
272
|
+
var STATUS_LABEL = {
|
|
273
|
+
queued: "Queued",
|
|
274
|
+
running: "Running",
|
|
275
|
+
succeeded: "Done",
|
|
276
|
+
failed: "Failed"
|
|
277
|
+
};
|
|
278
|
+
var STATUS_PILL = {
|
|
279
|
+
queued: {
|
|
280
|
+
background: "color-mix(in srgb, hsl(var(--muted-foreground)) 12%, transparent)",
|
|
281
|
+
color: "hsl(var(--muted-foreground))",
|
|
282
|
+
borderColor: "color-mix(in srgb, hsl(var(--muted-foreground)) 28%, transparent)"
|
|
283
|
+
},
|
|
284
|
+
running: {
|
|
285
|
+
background: "color-mix(in srgb, hsl(var(--primary)) 14%, transparent)",
|
|
286
|
+
color: "hsl(var(--primary))",
|
|
287
|
+
borderColor: "color-mix(in srgb, hsl(var(--primary)) 40%, transparent)"
|
|
288
|
+
},
|
|
289
|
+
succeeded: {
|
|
290
|
+
background: "var(--surface-success-bg)",
|
|
291
|
+
color: "var(--surface-success-text)",
|
|
292
|
+
borderColor: "var(--surface-success-border)"
|
|
293
|
+
},
|
|
294
|
+
failed: {
|
|
295
|
+
background: "var(--surface-danger-bg)",
|
|
296
|
+
color: "var(--surface-danger-text)",
|
|
297
|
+
borderColor: "var(--surface-danger-border)"
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
function statusBorder(status) {
|
|
301
|
+
const color = STATUS_COLOR[status];
|
|
302
|
+
switch (status) {
|
|
303
|
+
case "running":
|
|
304
|
+
return {
|
|
305
|
+
borderColor: color,
|
|
306
|
+
boxShadow: `0 0 0 1px color-mix(in srgb, ${color} 45%, transparent), 0 0 24px -6px ${color}`
|
|
307
|
+
};
|
|
308
|
+
case "queued":
|
|
309
|
+
return { borderColor: "hsl(var(--border))" };
|
|
310
|
+
default:
|
|
311
|
+
return {
|
|
312
|
+
borderColor: color,
|
|
313
|
+
boxShadow: `0 0 0 1px color-mix(in srgb, ${color} 35%, transparent)`
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
function progressFill(status) {
|
|
318
|
+
return status === "succeeded" || status === "failed" ? "100%" : status === "running" ? "58%" : "6%";
|
|
319
|
+
}
|
|
320
|
+
var KIND_ICON = {
|
|
321
|
+
schedule: Clock,
|
|
322
|
+
provider_event: Webhook,
|
|
323
|
+
trigger: Zap,
|
|
324
|
+
"agent.run": Sparkles,
|
|
325
|
+
"integration.invoke": Cable,
|
|
326
|
+
notify: Bell,
|
|
327
|
+
parallel: GitBranch,
|
|
328
|
+
foreach: Repeat,
|
|
329
|
+
decision: UserCheck,
|
|
330
|
+
"sandbox.spawn": Box
|
|
331
|
+
};
|
|
332
|
+
function NodeMark({
|
|
333
|
+
kind,
|
|
334
|
+
provider,
|
|
335
|
+
accent,
|
|
336
|
+
tile
|
|
337
|
+
}) {
|
|
338
|
+
const Icon = kind && KIND_ICON[kind] || Circle;
|
|
339
|
+
const mark = Math.round(tile * (provider ? 0.58 : 0.5));
|
|
340
|
+
return /* @__PURE__ */ jsx2(
|
|
341
|
+
"span",
|
|
342
|
+
{
|
|
343
|
+
"aria-hidden": "true",
|
|
344
|
+
className: "flex shrink-0 items-center justify-center rounded-lg border border-border",
|
|
345
|
+
style: {
|
|
346
|
+
width: tile,
|
|
347
|
+
height: tile,
|
|
348
|
+
background: `color-mix(in srgb, ${accent} 10%, hsl(var(--card)))`
|
|
349
|
+
},
|
|
350
|
+
children: provider ? /* @__PURE__ */ jsx2(
|
|
351
|
+
ProviderIcon,
|
|
352
|
+
{
|
|
353
|
+
id: provider,
|
|
354
|
+
displayName: providerLabel(provider),
|
|
355
|
+
size: mark,
|
|
356
|
+
className: "rounded-[3px]"
|
|
357
|
+
}
|
|
358
|
+
) : /* @__PURE__ */ jsx2(Icon, { size: mark, strokeWidth: 1.75, style: { color: accent } })
|
|
359
|
+
}
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// src/workflows/WorkflowGraph.tsx
|
|
364
|
+
import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
365
|
+
var MUTED_TRACK = "color-mix(in srgb, hsl(var(--muted-foreground)) 22%, transparent)";
|
|
366
|
+
var FIT_VIEW = { padding: 0.16, minZoom: 0.55, maxZoom: 1 };
|
|
367
|
+
function useColorMode() {
|
|
368
|
+
const [mode, setMode] = useState(
|
|
369
|
+
() => typeof document !== "undefined" && document.documentElement.classList.contains("dark") ? "dark" : "light"
|
|
370
|
+
);
|
|
371
|
+
useEffect(() => {
|
|
372
|
+
const el = document.documentElement;
|
|
373
|
+
const update = () => setMode(el.classList.contains("dark") ? "dark" : "light");
|
|
374
|
+
update();
|
|
375
|
+
const observer = new MutationObserver(update);
|
|
376
|
+
observer.observe(el, { attributes: true, attributeFilter: ["class"] });
|
|
377
|
+
return () => observer.disconnect();
|
|
378
|
+
}, []);
|
|
379
|
+
return mode;
|
|
380
|
+
}
|
|
381
|
+
var DirectionContext = createContext("LR");
|
|
382
|
+
var DensityContext = createContext(false);
|
|
383
|
+
function StatusFooter({
|
|
384
|
+
status,
|
|
385
|
+
rounds,
|
|
386
|
+
elapsed
|
|
387
|
+
}) {
|
|
388
|
+
const roundsLabel = rounds !== void 0 ? `${rounds} round${rounds === 1 ? "" : "s"}` : void 0;
|
|
389
|
+
return /* @__PURE__ */ jsxs2("div", { className: "mt-auto border-border border-t", children: [
|
|
390
|
+
/* @__PURE__ */ jsx3(
|
|
391
|
+
"div",
|
|
392
|
+
{
|
|
393
|
+
className: "h-1 w-full overflow-hidden",
|
|
394
|
+
style: { background: MUTED_TRACK },
|
|
395
|
+
children: /* @__PURE__ */ jsx3(
|
|
396
|
+
"div",
|
|
397
|
+
{
|
|
398
|
+
className: `h-full ${status === "running" ? "animate-pulse" : ""}`,
|
|
399
|
+
style: {
|
|
400
|
+
width: progressFill(status),
|
|
401
|
+
background: STATUS_COLOR[status]
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
)
|
|
405
|
+
}
|
|
406
|
+
),
|
|
407
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between gap-2 px-3.5 py-1 text-[10px] text-muted-foreground leading-[15px]", children: [
|
|
408
|
+
/* @__PURE__ */ jsx3("span", { className: "min-h-[15px] truncate", children: roundsLabel ?? "" }),
|
|
409
|
+
/* @__PURE__ */ jsx3("span", { className: "min-h-[15px] shrink-0 tabular-nums", children: elapsed ?? "" })
|
|
410
|
+
] })
|
|
411
|
+
] });
|
|
412
|
+
}
|
|
413
|
+
function emptySlotLabel(status) {
|
|
414
|
+
switch (status) {
|
|
415
|
+
case "queued":
|
|
416
|
+
return "Not run yet";
|
|
417
|
+
case "succeeded":
|
|
418
|
+
return "No output";
|
|
419
|
+
case "failed":
|
|
420
|
+
return "No error reported";
|
|
421
|
+
default:
|
|
422
|
+
return void 0;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
function StatusPill({ status }) {
|
|
426
|
+
return /* @__PURE__ */ jsx3(
|
|
427
|
+
"span",
|
|
428
|
+
{
|
|
429
|
+
className: "shrink-0 rounded-full border px-2 py-[1px] font-medium text-[10px]",
|
|
430
|
+
style: STATUS_PILL[status],
|
|
431
|
+
children: STATUS_LABEL[status]
|
|
432
|
+
}
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
var HANDLE_CLASS = "!h-2 !w-2 !min-w-0 !border-0 !bg-transparent opacity-0";
|
|
436
|
+
function compactHandleStyle(position, isLR) {
|
|
437
|
+
const tileLeft = isLR ? (COMPACT_NODE_SIZE.width - COMPACT_TILE) / 2 : 0;
|
|
438
|
+
const center = COMPACT_TILE / 2;
|
|
439
|
+
const anchor = (left, top) => ({
|
|
440
|
+
left,
|
|
441
|
+
top,
|
|
442
|
+
right: "auto",
|
|
443
|
+
bottom: "auto",
|
|
444
|
+
transform: "translate(-50%, -50%)"
|
|
445
|
+
});
|
|
446
|
+
switch (position) {
|
|
447
|
+
case Position2.Left:
|
|
448
|
+
return anchor(tileLeft, center);
|
|
449
|
+
case Position2.Right:
|
|
450
|
+
return anchor(tileLeft + COMPACT_TILE, center);
|
|
451
|
+
case Position2.Top:
|
|
452
|
+
return anchor(tileLeft + center, 0);
|
|
453
|
+
default:
|
|
454
|
+
return anchor(tileLeft + center, COMPACT_TILE);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
function WorkflowNode({ data }) {
|
|
458
|
+
const d = data;
|
|
459
|
+
const state = d.state;
|
|
460
|
+
const direction = useContext(DirectionContext);
|
|
461
|
+
const compact = useContext(DensityContext);
|
|
462
|
+
const isLR = direction === "LR";
|
|
463
|
+
const targetPos = isLR ? Position2.Left : Position2.Top;
|
|
464
|
+
const sourcePos = isLR ? Position2.Right : Position2.Bottom;
|
|
465
|
+
const accent = TONE_ACCENT[d.tone];
|
|
466
|
+
const isAgent = d.kind === "agent.run";
|
|
467
|
+
const subtitle = isAgent && state?.model ? shortModel(state.model) : d.subtitle;
|
|
468
|
+
const runBands = state !== void 0 && d.tone !== "trigger";
|
|
469
|
+
const duration = fmtDuration(state?.durationMs);
|
|
470
|
+
const cost = fmtCost(state?.costUsd);
|
|
471
|
+
const tokens = isAgent ? fmtTokens(state?.inputTokens, state?.outputTokens) : void 0;
|
|
472
|
+
const runOutput = (() => {
|
|
473
|
+
const source = state?.status === "failed" && state.error ? { text: state.error, tone: "error", label: "Error" } : state && state.status !== "failed" && state.outputPreview ? {
|
|
474
|
+
text: state.outputPreview,
|
|
475
|
+
tone: "default",
|
|
476
|
+
label: "Output"
|
|
477
|
+
} : null;
|
|
478
|
+
if (!source) return null;
|
|
479
|
+
const trimmed = source.text.trim();
|
|
480
|
+
if (!trimmed) return null;
|
|
481
|
+
const shape = classifyOutput(
|
|
482
|
+
clampPreview(trimmed),
|
|
483
|
+
isAgent && source.tone !== "error"
|
|
484
|
+
);
|
|
485
|
+
return shape.kind === "empty" ? null : { shape, tone: source.tone, label: source.label };
|
|
486
|
+
})();
|
|
487
|
+
const handles = /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
488
|
+
!d.isRoot && /* @__PURE__ */ jsx3(
|
|
489
|
+
Handle,
|
|
490
|
+
{
|
|
491
|
+
type: "target",
|
|
492
|
+
position: targetPos,
|
|
493
|
+
className: HANDLE_CLASS,
|
|
494
|
+
style: compact ? compactHandleStyle(targetPos, isLR) : void 0
|
|
495
|
+
}
|
|
496
|
+
),
|
|
497
|
+
/* @__PURE__ */ jsx3(
|
|
498
|
+
Handle,
|
|
499
|
+
{
|
|
500
|
+
type: "source",
|
|
501
|
+
position: sourcePos,
|
|
502
|
+
className: HANDLE_CLASS,
|
|
503
|
+
style: compact ? compactHandleStyle(sourcePos, isLR) : void 0
|
|
504
|
+
}
|
|
505
|
+
)
|
|
506
|
+
] });
|
|
507
|
+
if (compact) {
|
|
508
|
+
return /* @__PURE__ */ jsxs2(
|
|
509
|
+
"div",
|
|
510
|
+
{
|
|
511
|
+
className: `relative flex h-full w-full ${isLR ? "flex-col items-center" : "flex-row items-center"}`,
|
|
512
|
+
children: [
|
|
513
|
+
handles,
|
|
514
|
+
/* @__PURE__ */ jsxs2(
|
|
515
|
+
"span",
|
|
516
|
+
{
|
|
517
|
+
className: "relative flex items-center justify-center rounded-xl border bg-card shadow-sm transition-colors",
|
|
518
|
+
style: {
|
|
519
|
+
width: COMPACT_TILE,
|
|
520
|
+
height: COMPACT_TILE,
|
|
521
|
+
...state ? statusBorder(state.status) : {
|
|
522
|
+
borderColor: `color-mix(in srgb, ${accent} 40%, hsl(var(--border)))`
|
|
523
|
+
}
|
|
524
|
+
},
|
|
525
|
+
children: [
|
|
526
|
+
/* @__PURE__ */ jsx3(
|
|
527
|
+
NodeMark,
|
|
528
|
+
{
|
|
529
|
+
kind: d.kind,
|
|
530
|
+
provider: d.provider,
|
|
531
|
+
accent,
|
|
532
|
+
tile: Math.round(COMPACT_TILE * 0.62)
|
|
533
|
+
}
|
|
534
|
+
),
|
|
535
|
+
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 }),
|
|
536
|
+
state && /* @__PURE__ */ jsx3(
|
|
537
|
+
"span",
|
|
538
|
+
{
|
|
539
|
+
className: `-right-1 -top-1 absolute h-2.5 w-2.5 rounded-full border-2 ${state.status === "running" ? "animate-pulse" : ""}`,
|
|
540
|
+
style: {
|
|
541
|
+
background: STATUS_COLOR[state.status],
|
|
542
|
+
borderColor: "hsl(var(--card))"
|
|
543
|
+
},
|
|
544
|
+
"aria-label": STATUS_LABEL[state.status]
|
|
545
|
+
}
|
|
546
|
+
)
|
|
547
|
+
]
|
|
548
|
+
}
|
|
549
|
+
),
|
|
550
|
+
/* @__PURE__ */ jsxs2(
|
|
551
|
+
"div",
|
|
552
|
+
{
|
|
553
|
+
className: `min-w-0 ${isLR ? "mt-2 w-full px-1 text-center" : "ml-2 flex-1 text-left"}`,
|
|
554
|
+
children: [
|
|
555
|
+
/* @__PURE__ */ jsx3(
|
|
556
|
+
"div",
|
|
557
|
+
{
|
|
558
|
+
className: "truncate font-semibold text-[12px] text-foreground leading-tight",
|
|
559
|
+
title: d.title,
|
|
560
|
+
children: d.title
|
|
561
|
+
}
|
|
562
|
+
),
|
|
563
|
+
subtitle && /* @__PURE__ */ jsx3(
|
|
564
|
+
"div",
|
|
565
|
+
{
|
|
566
|
+
className: "truncate text-[11px] text-muted-foreground leading-tight",
|
|
567
|
+
title: subtitle,
|
|
568
|
+
children: subtitle
|
|
569
|
+
}
|
|
570
|
+
),
|
|
571
|
+
(duration || cost) && /* @__PURE__ */ jsx3("div", { className: "truncate text-[10px] text-muted-foreground/90 leading-tight tabular-nums", children: [duration, cost].filter(Boolean).join(" \xB7 ") })
|
|
572
|
+
]
|
|
573
|
+
}
|
|
574
|
+
)
|
|
575
|
+
]
|
|
576
|
+
}
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
return /* @__PURE__ */ jsxs2(
|
|
580
|
+
"div",
|
|
581
|
+
{
|
|
582
|
+
className: "relative flex h-full w-full flex-col overflow-hidden rounded-xl border bg-card shadow-sm transition-colors",
|
|
583
|
+
style: state ? statusBorder(state.status) : {
|
|
584
|
+
borderColor: `color-mix(in srgb, ${accent} 40%, hsl(var(--border)))`
|
|
585
|
+
},
|
|
586
|
+
children: [
|
|
587
|
+
handles,
|
|
588
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex min-h-0 flex-1 flex-col overflow-hidden px-3.5 pt-2.5 pb-2", children: [
|
|
589
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex shrink-0 items-center gap-2.5", children: [
|
|
590
|
+
/* @__PURE__ */ jsx3(
|
|
591
|
+
NodeMark,
|
|
592
|
+
{
|
|
593
|
+
kind: d.kind,
|
|
594
|
+
provider: d.provider,
|
|
595
|
+
accent,
|
|
596
|
+
tile: 34
|
|
597
|
+
}
|
|
598
|
+
),
|
|
599
|
+
/* @__PURE__ */ jsxs2("div", { className: "min-w-0 flex-1", children: [
|
|
600
|
+
/* @__PURE__ */ jsx3(
|
|
601
|
+
"div",
|
|
602
|
+
{
|
|
603
|
+
className: "truncate font-semibold text-[13px] text-foreground leading-tight",
|
|
604
|
+
title: d.title,
|
|
605
|
+
children: d.title
|
|
606
|
+
}
|
|
607
|
+
),
|
|
608
|
+
subtitle && /* @__PURE__ */ jsx3(
|
|
609
|
+
"div",
|
|
610
|
+
{
|
|
611
|
+
className: "truncate text-[11px] text-muted-foreground leading-tight",
|
|
612
|
+
title: subtitle,
|
|
613
|
+
children: subtitle
|
|
614
|
+
}
|
|
615
|
+
)
|
|
616
|
+
] }),
|
|
617
|
+
state ? /* @__PURE__ */ jsx3(StatusPill, { status: state.status }) : 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 })
|
|
618
|
+
] }),
|
|
619
|
+
d.description && /* @__PURE__ */ jsx3(
|
|
620
|
+
"p",
|
|
621
|
+
{
|
|
622
|
+
className: "mt-2 line-clamp-2 shrink-0 text-[11px] text-muted-foreground leading-snug",
|
|
623
|
+
title: d.description,
|
|
624
|
+
children: d.description
|
|
625
|
+
}
|
|
626
|
+
),
|
|
627
|
+
runBands && (cost || tokens) && /* @__PURE__ */ jsx3("div", { className: "mt-2 shrink-0 truncate text-[11px] text-muted-foreground tabular-nums", children: [cost, tokens].filter(Boolean).join(" \xB7 ") }),
|
|
628
|
+
runBands && runOutput && /* @__PURE__ */ jsxs2("div", { className: "mt-2 min-h-0 shrink-0 rounded-lg border border-border bg-surface-container-high/60 px-2 py-1.5", children: [
|
|
629
|
+
/* @__PURE__ */ jsx3("div", { className: "mb-1 font-semibold text-[9px] text-muted-foreground uppercase tracking-[0.09em]", children: runOutput.label }),
|
|
630
|
+
/* @__PURE__ */ jsx3(
|
|
631
|
+
NodeOutputBody,
|
|
632
|
+
{
|
|
633
|
+
shape: runOutput.shape,
|
|
634
|
+
tone: runOutput.tone,
|
|
635
|
+
rows: 2
|
|
636
|
+
}
|
|
637
|
+
)
|
|
638
|
+
] }),
|
|
639
|
+
runBands && state && !runOutput && emptySlotLabel(state.status) && /* @__PURE__ */ jsx3("div", { className: "mt-2 flex flex-1 items-center text-[11px] text-muted-foreground italic", children: emptySlotLabel(state.status) })
|
|
640
|
+
] }),
|
|
641
|
+
runBands && state && /* @__PURE__ */ jsx3(
|
|
642
|
+
StatusFooter,
|
|
643
|
+
{
|
|
644
|
+
status: state.status,
|
|
645
|
+
rounds: isAgent ? state.rounds : void 0,
|
|
646
|
+
elapsed: duration
|
|
647
|
+
}
|
|
648
|
+
)
|
|
649
|
+
]
|
|
650
|
+
}
|
|
651
|
+
);
|
|
652
|
+
}
|
|
653
|
+
var NODE_TYPES = { wfNode: WorkflowNode };
|
|
654
|
+
function buildStyledEdges(base, nodeState) {
|
|
655
|
+
return base.map((e) => {
|
|
656
|
+
const status = nodeState?.[e.target]?.status;
|
|
657
|
+
const color = edgeColor(nodeState ? status : void 0);
|
|
658
|
+
return {
|
|
659
|
+
...e,
|
|
660
|
+
// The active hop flows; everything else is static.
|
|
661
|
+
animated: status === "running",
|
|
662
|
+
style: { strokeWidth: 1.75, stroke: color },
|
|
663
|
+
markerEnd: {
|
|
664
|
+
type: MarkerType.ArrowClosed,
|
|
665
|
+
width: 15,
|
|
666
|
+
height: 15,
|
|
667
|
+
color
|
|
668
|
+
}
|
|
669
|
+
};
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
function WorkflowGraph({
|
|
673
|
+
yaml,
|
|
674
|
+
variant = "full",
|
|
675
|
+
direction = "LR",
|
|
676
|
+
defaultCompact = true,
|
|
677
|
+
className,
|
|
678
|
+
nodeState,
|
|
679
|
+
onNodeClick
|
|
680
|
+
}) {
|
|
681
|
+
const colorMode = useColorMode();
|
|
682
|
+
const isPreview = variant === "preview";
|
|
683
|
+
const [userCompact, setUserCompact] = useState(defaultCompact);
|
|
684
|
+
const compact = isPreview || userCompact;
|
|
685
|
+
const rfRef = useRef(null);
|
|
686
|
+
const hasRunOverlay = nodeState !== void 0;
|
|
687
|
+
const structural = useMemo(
|
|
688
|
+
() => buildFlowGraph(yaml, {
|
|
689
|
+
nodeState: hasRunOverlay ? {} : void 0,
|
|
690
|
+
direction,
|
|
691
|
+
compact
|
|
692
|
+
}),
|
|
693
|
+
[yaml, hasRunOverlay, direction, compact]
|
|
694
|
+
);
|
|
695
|
+
const styledEdges = useMemo(
|
|
696
|
+
() => buildStyledEdges(structural.edges, nodeState),
|
|
697
|
+
[structural.edges, nodeState]
|
|
698
|
+
);
|
|
699
|
+
const [nodes, setNodes, onNodesChange] = useNodesState(structural.nodes);
|
|
700
|
+
const [edges, setEdges, onEdgesChange] = useEdgesState(styledEdges);
|
|
701
|
+
useEffect(() => {
|
|
702
|
+
setNodes(structural.nodes);
|
|
703
|
+
}, [structural, setNodes]);
|
|
704
|
+
useEffect(() => {
|
|
705
|
+
setEdges(styledEdges);
|
|
706
|
+
}, [styledEdges, setEdges]);
|
|
707
|
+
useEffect(() => {
|
|
708
|
+
const baseById = new Map(structural.nodes.map((n) => [n.id, n.data]));
|
|
709
|
+
setNodes((prev) => mergeRunState(prev, baseById, nodeState));
|
|
710
|
+
}, [nodeState, structural, setNodes]);
|
|
711
|
+
const didInitialFitRef = useRef(false);
|
|
712
|
+
useEffect(() => {
|
|
713
|
+
if (!didInitialFitRef.current) {
|
|
714
|
+
didInitialFitRef.current = true;
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
const inst = rfRef.current;
|
|
718
|
+
if (!inst || typeof requestAnimationFrame === "undefined") return;
|
|
719
|
+
const raf = requestAnimationFrame(() => inst.fitView(FIT_VIEW));
|
|
720
|
+
return () => cancelAnimationFrame(raf);
|
|
721
|
+
}, [structural]);
|
|
722
|
+
const handleNodeClick = useCallback(
|
|
723
|
+
(_event, node) => {
|
|
724
|
+
onNodeClick?.(node.id, node.data);
|
|
725
|
+
},
|
|
726
|
+
[onNodeClick]
|
|
727
|
+
);
|
|
728
|
+
if (structural.error || structural.nodes.length === 0) {
|
|
729
|
+
return /* @__PURE__ */ jsx3(
|
|
730
|
+
"div",
|
|
731
|
+
{
|
|
732
|
+
className: `flex items-center justify-center rounded-lg border border-border border-dashed p-4 text-center text-muted-foreground text-xs ${className ?? ""}`,
|
|
733
|
+
children: structural.error ?? "Nothing to show"
|
|
734
|
+
}
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
return /* @__PURE__ */ jsx3(DirectionContext.Provider, { value: direction, children: /* @__PURE__ */ jsx3(DensityContext.Provider, { value: compact, children: /* @__PURE__ */ jsx3("div", { className: `wf-graph ${className ?? ""}`, children: /* @__PURE__ */ jsxs2(
|
|
738
|
+
ReactFlow,
|
|
739
|
+
{
|
|
740
|
+
nodes,
|
|
741
|
+
edges,
|
|
742
|
+
onNodesChange,
|
|
743
|
+
onEdgesChange,
|
|
744
|
+
nodeTypes: NODE_TYPES,
|
|
745
|
+
colorMode,
|
|
746
|
+
onInit: (inst) => {
|
|
747
|
+
rfRef.current = inst;
|
|
748
|
+
},
|
|
749
|
+
onNodeClick: onNodeClick ? handleNodeClick : void 0,
|
|
750
|
+
fitView: true,
|
|
751
|
+
fitViewOptions: FIT_VIEW,
|
|
752
|
+
proOptions: { hideAttribution: true },
|
|
753
|
+
nodesDraggable: !isPreview,
|
|
754
|
+
nodesConnectable: false,
|
|
755
|
+
deleteKeyCode: null,
|
|
756
|
+
edgesReconnectable: false,
|
|
757
|
+
elementsSelectable: !isPreview,
|
|
758
|
+
edgesFocusable: false,
|
|
759
|
+
zoomOnScroll: false,
|
|
760
|
+
zoomOnPinch: true,
|
|
761
|
+
zoomOnDoubleClick: !isPreview,
|
|
762
|
+
panOnDrag: true,
|
|
763
|
+
preventScrolling: false,
|
|
764
|
+
minZoom: 0.2,
|
|
765
|
+
maxZoom: 1.5,
|
|
766
|
+
children: [
|
|
767
|
+
/* @__PURE__ */ jsx3(Background, { gap: 18 }),
|
|
768
|
+
!isPreview && /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
769
|
+
/* @__PURE__ */ jsx3(Controls, { showInteractive: false, position: "bottom-right" }),
|
|
770
|
+
/* @__PURE__ */ jsx3(Panel, { position: "top-left", children: /* @__PURE__ */ jsxs2(
|
|
771
|
+
"button",
|
|
772
|
+
{
|
|
773
|
+
type: "button",
|
|
774
|
+
onClick: () => setUserCompact((c) => !c),
|
|
775
|
+
"aria-pressed": userCompact,
|
|
776
|
+
title: userCompact ? "Expand nodes" : "Collapse nodes",
|
|
777
|
+
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",
|
|
778
|
+
children: [
|
|
779
|
+
userCompact ? /* @__PURE__ */ jsx3(Maximize2, { size: 12 }) : /* @__PURE__ */ jsx3(Minimize2, { size: 12 }),
|
|
780
|
+
userCompact ? "Expand" : "Compact"
|
|
781
|
+
]
|
|
782
|
+
}
|
|
783
|
+
) })
|
|
784
|
+
] })
|
|
785
|
+
]
|
|
786
|
+
}
|
|
787
|
+
) }) }) });
|
|
788
|
+
}
|
|
789
|
+
export {
|
|
790
|
+
DensityContext,
|
|
791
|
+
DirectionContext,
|
|
792
|
+
WorkflowGraph,
|
|
793
|
+
WorkflowNode,
|
|
794
|
+
buildStyledEdges
|
|
795
|
+
};
|