@tangle-network/sandbox-ui 0.73.0 → 0.75.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-RA7ZHN4I.js +636 -0
- package/dist/{chunk-34F66QTV.js → chunk-FNE5QLU3.js} +90 -55
- package/dist/globals.css +52 -9
- package/dist/styles.css +52 -9
- package/dist/workflows.d.ts +38 -11
- package/dist/workflows.js +5 -5
- package/package.json +1 -1
- package/dist/WorkflowGraph-M5ZOY43R.js +0 -328
|
@@ -0,0 +1,636 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ProviderIcon
|
|
3
|
+
} from "./chunk-GTJ5PSK5.js";
|
|
4
|
+
import {
|
|
5
|
+
COMPACT_NODE_SIZE,
|
|
6
|
+
buildWorkflowGraph,
|
|
7
|
+
providerLabel
|
|
8
|
+
} from "./chunk-FNE5QLU3.js";
|
|
9
|
+
import "./chunk-U67V476Y.js";
|
|
10
|
+
|
|
11
|
+
// src/workflows/WorkflowGraph.tsx
|
|
12
|
+
import {
|
|
13
|
+
Background,
|
|
14
|
+
Controls,
|
|
15
|
+
Handle,
|
|
16
|
+
MarkerType,
|
|
17
|
+
Panel,
|
|
18
|
+
Position as Position2,
|
|
19
|
+
ReactFlow,
|
|
20
|
+
useEdgesState,
|
|
21
|
+
useNodesState
|
|
22
|
+
} from "@xyflow/react";
|
|
23
|
+
import "@xyflow/react/dist/style.css";
|
|
24
|
+
import { Maximize2, Minimize2 } from "lucide-react";
|
|
25
|
+
import {
|
|
26
|
+
createContext,
|
|
27
|
+
useCallback,
|
|
28
|
+
useContext,
|
|
29
|
+
useEffect,
|
|
30
|
+
useMemo,
|
|
31
|
+
useRef,
|
|
32
|
+
useState
|
|
33
|
+
} from "react";
|
|
34
|
+
|
|
35
|
+
// src/workflows/flow-graph.ts
|
|
36
|
+
import { Position } from "@xyflow/react";
|
|
37
|
+
function normalizeFlowGraphOptions(arg) {
|
|
38
|
+
if (!arg) return {};
|
|
39
|
+
if ("nodeState" in arg || "direction" in arg || "compact" in arg || "measure" in arg) {
|
|
40
|
+
return arg;
|
|
41
|
+
}
|
|
42
|
+
return { nodeState: arg };
|
|
43
|
+
}
|
|
44
|
+
function buildFlowGraph(yaml, optionsOrNodeState) {
|
|
45
|
+
const options = normalizeFlowGraphOptions(optionsOrNodeState);
|
|
46
|
+
const nodeState = options.nodeState;
|
|
47
|
+
const direction = options.direction ?? "LR";
|
|
48
|
+
const compact = options.compact ?? false;
|
|
49
|
+
const graph = buildWorkflowGraph(yaml, {
|
|
50
|
+
reserveRunState: nodeState !== void 0,
|
|
51
|
+
direction,
|
|
52
|
+
// Compact nodes are a fixed size; otherwise honor the caller's `measure` (or
|
|
53
|
+
// fall back to the default content sizing).
|
|
54
|
+
measure: compact ? () => COMPACT_NODE_SIZE : options?.measure
|
|
55
|
+
});
|
|
56
|
+
const isLR = direction === "LR";
|
|
57
|
+
const sourcePosition = isLR ? Position.Right : Position.Bottom;
|
|
58
|
+
const targetPosition = isLR ? Position.Left : Position.Top;
|
|
59
|
+
return {
|
|
60
|
+
error: graph.error,
|
|
61
|
+
nodes: graph.nodes.map(
|
|
62
|
+
(n) => ({
|
|
63
|
+
id: n.id,
|
|
64
|
+
type: "wfNode",
|
|
65
|
+
position: n.position,
|
|
66
|
+
width: n.width,
|
|
67
|
+
height: n.height,
|
|
68
|
+
// Fix the DOM box to the laid-out size so the card can't grow past its
|
|
69
|
+
// reserved space and overlap a neighbour (see model.ts nodeHeight).
|
|
70
|
+
style: { width: n.width, height: n.height },
|
|
71
|
+
sourcePosition,
|
|
72
|
+
targetPosition,
|
|
73
|
+
data: nodeState?.[n.id] ? { ...n.data, state: nodeState[n.id] } : n.data
|
|
74
|
+
})
|
|
75
|
+
),
|
|
76
|
+
edges: graph.edges.map((e) => ({
|
|
77
|
+
id: e.id,
|
|
78
|
+
source: e.source,
|
|
79
|
+
target: e.target,
|
|
80
|
+
type: "smoothstep"
|
|
81
|
+
}))
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function sameRunState(a, b) {
|
|
85
|
+
if (a === b) return true;
|
|
86
|
+
if (!a || !b) return false;
|
|
87
|
+
const aKeys = Object.keys(a);
|
|
88
|
+
const bKeys = Object.keys(b);
|
|
89
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
90
|
+
return aKeys.every((k) => a[k] === b[k]);
|
|
91
|
+
}
|
|
92
|
+
function mergeRunState(prev, baseDataById, nodeState) {
|
|
93
|
+
return prev.map((n) => {
|
|
94
|
+
const base = baseDataById.get(n.id) ?? n.data;
|
|
95
|
+
const s = nodeState?.[n.id];
|
|
96
|
+
const nextData = s ? { ...base, state: s } : base;
|
|
97
|
+
return sameRunState(n.data.state, nextData.state) ? n : { ...n, data: nextData };
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// src/workflows/format.ts
|
|
102
|
+
function fmtDuration(ms) {
|
|
103
|
+
if (ms === void 0 || !Number.isFinite(ms) || ms < 0) return void 0;
|
|
104
|
+
if (ms < 1e3) return `${Math.round(ms)}ms`;
|
|
105
|
+
const s = ms / 1e3;
|
|
106
|
+
if (s < 60) return `${s.toFixed(1)}s`;
|
|
107
|
+
const totalSeconds = Math.round(s);
|
|
108
|
+
return `${Math.floor(totalSeconds / 60)}m${totalSeconds % 60}s`;
|
|
109
|
+
}
|
|
110
|
+
function fmtCost(usd) {
|
|
111
|
+
if (usd === void 0 || !Number.isFinite(usd) || usd < 0) return void 0;
|
|
112
|
+
if (usd === 0) return "$0";
|
|
113
|
+
return usd < 0.01 ? `$${usd.toFixed(4)}` : `$${usd.toFixed(2)}`;
|
|
114
|
+
}
|
|
115
|
+
function clampPreview(text, max = 200) {
|
|
116
|
+
if (text.length <= max) return text;
|
|
117
|
+
const lastCode = text.charCodeAt(max - 1);
|
|
118
|
+
const end = lastCode >= 55296 && lastCode <= 56319 ? max - 1 : max;
|
|
119
|
+
return `${text.slice(0, end)}\u2026`;
|
|
120
|
+
}
|
|
121
|
+
function fmtTokens(input, output) {
|
|
122
|
+
const valid = (n) => n !== void 0 && Number.isFinite(n) && n >= 0 ? n : void 0;
|
|
123
|
+
const i = valid(input);
|
|
124
|
+
const o = valid(output);
|
|
125
|
+
if (i === void 0 && o === void 0) return void 0;
|
|
126
|
+
return `${i ?? 0}/${o ?? 0} tok`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// src/workflows/node-output.tsx
|
|
130
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
131
|
+
var MAX_JSON_ENTRIES = 6;
|
|
132
|
+
function scalar(value) {
|
|
133
|
+
if (value === null) return "null";
|
|
134
|
+
if (typeof value === "string") return value;
|
|
135
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
136
|
+
return String(value);
|
|
137
|
+
}
|
|
138
|
+
if (Array.isArray(value)) {
|
|
139
|
+
return `[${value.length} item${value.length === 1 ? "" : "s"}]`;
|
|
140
|
+
}
|
|
141
|
+
if (typeof value === "object") return "{\u2026}";
|
|
142
|
+
return "";
|
|
143
|
+
}
|
|
144
|
+
function classifyOutput(preview) {
|
|
145
|
+
let trimmed = preview?.trim() ?? "";
|
|
146
|
+
trimmed = trimmed.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])/g, "").replace(/(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "").trimEnd();
|
|
147
|
+
if (trimmed === "") return { kind: "empty" };
|
|
148
|
+
const structured = trimmed.startsWith("{") || trimmed.startsWith("[");
|
|
149
|
+
if (structured) {
|
|
150
|
+
try {
|
|
151
|
+
const parsed = JSON.parse(trimmed);
|
|
152
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
153
|
+
const all = Object.entries(parsed);
|
|
154
|
+
const entries = all.slice(0, MAX_JSON_ENTRIES).map(([k, v]) => [k, scalar(v)]);
|
|
155
|
+
if (entries.length > 0) {
|
|
156
|
+
return { kind: "json", entries, truncated: all.length > entries.length };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return { kind: "code", text: trimmed };
|
|
160
|
+
} catch {
|
|
161
|
+
return { kind: "code", text: trimmed };
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return { kind: "text", text: trimmed };
|
|
165
|
+
}
|
|
166
|
+
var CLAMP_BY_ROWS = {
|
|
167
|
+
1: "truncate",
|
|
168
|
+
2: "line-clamp-2",
|
|
169
|
+
3: "line-clamp-3"
|
|
170
|
+
};
|
|
171
|
+
function NodeOutputBody({
|
|
172
|
+
shape,
|
|
173
|
+
rows = 2,
|
|
174
|
+
tone = "default"
|
|
175
|
+
}) {
|
|
176
|
+
const clampClass = CLAMP_BY_ROWS[rows] ?? "line-clamp-2";
|
|
177
|
+
const codeWrap = rows === 1 ? "" : "whitespace-pre-wrap break-all";
|
|
178
|
+
if (shape.kind === "empty") return null;
|
|
179
|
+
if (shape.kind === "json") {
|
|
180
|
+
const overflow = shape.truncated || shape.entries.length > rows;
|
|
181
|
+
const visible = shape.entries.slice(0, overflow ? Math.max(0, rows - 1) : rows);
|
|
182
|
+
const isError = tone === "error";
|
|
183
|
+
const keyClass = isError ? "text-red-400/80" : "text-muted-foreground";
|
|
184
|
+
const valueClass = isError ? "text-red-400" : "text-foreground";
|
|
185
|
+
return /* @__PURE__ */ jsxs("dl", { className: "space-y-0.5 font-mono text-[10px] leading-tight", children: [
|
|
186
|
+
visible.map(([key, value]) => /* @__PURE__ */ jsxs("div", { className: "flex gap-1.5", children: [
|
|
187
|
+
/* @__PURE__ */ jsx("dt", { className: `max-w-[45%] shrink-0 truncate ${keyClass}`, title: key, children: key }),
|
|
188
|
+
/* @__PURE__ */ jsx("dd", { className: `min-w-0 flex-1 truncate ${valueClass}`, title: value, children: value })
|
|
189
|
+
] }, key)),
|
|
190
|
+
overflow && /* @__PURE__ */ jsx("div", { className: `text-[9px] ${keyClass}`, children: "\u2026" })
|
|
191
|
+
] });
|
|
192
|
+
}
|
|
193
|
+
if (shape.kind === "code") {
|
|
194
|
+
return /* @__PURE__ */ jsx(
|
|
195
|
+
"pre",
|
|
196
|
+
{
|
|
197
|
+
className: `${clampClass} ${codeWrap} font-mono text-[10px] leading-tight ${tone === "error" ? "text-red-400" : "text-foreground/80"}`,
|
|
198
|
+
title: shape.text,
|
|
199
|
+
children: shape.text
|
|
200
|
+
}
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
return /* @__PURE__ */ jsx(
|
|
204
|
+
"p",
|
|
205
|
+
{
|
|
206
|
+
className: `${clampClass} text-[10px] leading-snug ${tone === "error" ? "text-red-400" : "text-muted-foreground"}`,
|
|
207
|
+
title: shape.text,
|
|
208
|
+
children: shape.text
|
|
209
|
+
}
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// src/workflows/node-ui.tsx
|
|
214
|
+
import {
|
|
215
|
+
Bell,
|
|
216
|
+
Box,
|
|
217
|
+
Cable,
|
|
218
|
+
Circle,
|
|
219
|
+
Clock,
|
|
220
|
+
Repeat,
|
|
221
|
+
Sparkles,
|
|
222
|
+
Split,
|
|
223
|
+
Webhook
|
|
224
|
+
} from "lucide-react";
|
|
225
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
226
|
+
var TONE_ACCENT = {
|
|
227
|
+
trigger: "hsl(var(--primary))",
|
|
228
|
+
structural: "var(--surface-warning-text)",
|
|
229
|
+
action: "hsl(var(--muted-foreground))"
|
|
230
|
+
};
|
|
231
|
+
var EDGE_MUTED = "hsl(var(--muted-foreground))";
|
|
232
|
+
var EDGE_DONE = "#22c55e";
|
|
233
|
+
var EDGE_FAIL = "#ef4444";
|
|
234
|
+
var EDGE_RUNNING = "hsl(var(--primary))";
|
|
235
|
+
var STATUS_COLOR = {
|
|
236
|
+
queued: EDGE_MUTED,
|
|
237
|
+
running: EDGE_RUNNING,
|
|
238
|
+
succeeded: EDGE_DONE,
|
|
239
|
+
failed: EDGE_FAIL
|
|
240
|
+
};
|
|
241
|
+
function edgeColor(status) {
|
|
242
|
+
switch (status) {
|
|
243
|
+
case "running":
|
|
244
|
+
return EDGE_RUNNING;
|
|
245
|
+
case "succeeded":
|
|
246
|
+
return EDGE_DONE;
|
|
247
|
+
case "failed":
|
|
248
|
+
return EDGE_FAIL;
|
|
249
|
+
default:
|
|
250
|
+
return EDGE_MUTED;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
var STATUS_LABEL = {
|
|
254
|
+
queued: "Queued",
|
|
255
|
+
running: "Running",
|
|
256
|
+
succeeded: "Done",
|
|
257
|
+
failed: "Failed"
|
|
258
|
+
};
|
|
259
|
+
var STATUS_BADGE = {
|
|
260
|
+
queued: "bg-surface-container-high text-muted-foreground",
|
|
261
|
+
running: "bg-primary/15 text-primary",
|
|
262
|
+
succeeded: "bg-green-600/10 text-green-600",
|
|
263
|
+
failed: "bg-red-500/10 text-red-400"
|
|
264
|
+
};
|
|
265
|
+
function statusBorder(status) {
|
|
266
|
+
switch (status) {
|
|
267
|
+
case "running":
|
|
268
|
+
return "border-primary ring-1 ring-primary/40";
|
|
269
|
+
case "succeeded":
|
|
270
|
+
return "border-green-500";
|
|
271
|
+
case "failed":
|
|
272
|
+
return "border-red-500";
|
|
273
|
+
default:
|
|
274
|
+
return "opacity-70";
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
function progressFill(status) {
|
|
278
|
+
return status === "succeeded" || status === "failed" ? "100%" : status === "running" ? "58%" : "6%";
|
|
279
|
+
}
|
|
280
|
+
var KIND_ICON = {
|
|
281
|
+
schedule: Clock,
|
|
282
|
+
provider_event: Webhook,
|
|
283
|
+
trigger: Webhook,
|
|
284
|
+
"agent.run": Sparkles,
|
|
285
|
+
"integration.invoke": Cable,
|
|
286
|
+
notify: Bell,
|
|
287
|
+
parallel: Split,
|
|
288
|
+
foreach: Repeat,
|
|
289
|
+
"sandbox.spawn": Box
|
|
290
|
+
};
|
|
291
|
+
function NodeIcon({
|
|
292
|
+
kind,
|
|
293
|
+
accent,
|
|
294
|
+
box,
|
|
295
|
+
glyph
|
|
296
|
+
}) {
|
|
297
|
+
const Icon = kind && KIND_ICON[kind] || Circle;
|
|
298
|
+
return /* @__PURE__ */ jsx2(
|
|
299
|
+
"span",
|
|
300
|
+
{
|
|
301
|
+
"aria-hidden": "true",
|
|
302
|
+
className: "flex shrink-0 items-center justify-center rounded-md",
|
|
303
|
+
style: {
|
|
304
|
+
width: box,
|
|
305
|
+
height: box,
|
|
306
|
+
background: `color-mix(in srgb, ${accent} 16%, transparent)`,
|
|
307
|
+
color: accent
|
|
308
|
+
},
|
|
309
|
+
children: /* @__PURE__ */ jsx2(Icon, { size: glyph, strokeWidth: 2 })
|
|
310
|
+
}
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
function MetaChip({
|
|
314
|
+
children,
|
|
315
|
+
title
|
|
316
|
+
}) {
|
|
317
|
+
return /* @__PURE__ */ jsx2(
|
|
318
|
+
"span",
|
|
319
|
+
{
|
|
320
|
+
title,
|
|
321
|
+
className: "inline-block max-w-[150px] truncate rounded bg-surface-container-high px-1.5 py-0.5 align-middle text-[10px] text-muted-foreground",
|
|
322
|
+
children
|
|
323
|
+
}
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// src/workflows/WorkflowGraph.tsx
|
|
328
|
+
import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
329
|
+
var MUTED_TRACK = "color-mix(in srgb, hsl(var(--muted-foreground)) 22%, transparent)";
|
|
330
|
+
function useColorMode() {
|
|
331
|
+
const [mode, setMode] = useState(
|
|
332
|
+
() => typeof document !== "undefined" && document.documentElement.classList.contains("dark") ? "dark" : "light"
|
|
333
|
+
);
|
|
334
|
+
useEffect(() => {
|
|
335
|
+
const el = document.documentElement;
|
|
336
|
+
const update = () => setMode(el.classList.contains("dark") ? "dark" : "light");
|
|
337
|
+
update();
|
|
338
|
+
const observer = new MutationObserver(update);
|
|
339
|
+
observer.observe(el, { attributes: true, attributeFilter: ["class"] });
|
|
340
|
+
return () => observer.disconnect();
|
|
341
|
+
}, []);
|
|
342
|
+
return mode;
|
|
343
|
+
}
|
|
344
|
+
var DirectionContext = createContext("LR");
|
|
345
|
+
var DensityContext = createContext(false);
|
|
346
|
+
function StatusFooter({
|
|
347
|
+
status,
|
|
348
|
+
rounds,
|
|
349
|
+
elapsed
|
|
350
|
+
}) {
|
|
351
|
+
const roundsLabel = rounds !== void 0 ? `${rounds} round${rounds === 1 ? "" : "s"}` : void 0;
|
|
352
|
+
return /* @__PURE__ */ jsxs2("div", { className: "mt-auto border-border/60 border-t", children: [
|
|
353
|
+
/* @__PURE__ */ jsx3("div", { className: "h-1 w-full overflow-hidden", style: { background: MUTED_TRACK }, children: /* @__PURE__ */ jsx3(
|
|
354
|
+
"div",
|
|
355
|
+
{
|
|
356
|
+
className: `h-full ${status === "running" ? "animate-pulse" : ""}`,
|
|
357
|
+
style: { width: progressFill(status), background: STATUS_COLOR[status] }
|
|
358
|
+
}
|
|
359
|
+
) }),
|
|
360
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between gap-2 px-3 py-1 text-[9px] text-muted-foreground", children: [
|
|
361
|
+
/* @__PURE__ */ jsx3("span", { className: "truncate", children: roundsLabel ?? "" }),
|
|
362
|
+
elapsed && /* @__PURE__ */ jsx3("span", { className: "shrink-0", children: elapsed })
|
|
363
|
+
] })
|
|
364
|
+
] });
|
|
365
|
+
}
|
|
366
|
+
var HANDLE_CLASS = "!h-2 !w-2 !min-w-0 !border-0 !bg-transparent opacity-0";
|
|
367
|
+
function WorkflowNode({ data }) {
|
|
368
|
+
const d = data;
|
|
369
|
+
const state = d.state;
|
|
370
|
+
const direction = useContext(DirectionContext);
|
|
371
|
+
const compact = useContext(DensityContext);
|
|
372
|
+
const isLR = direction === "LR";
|
|
373
|
+
const targetPos = isLR ? Position2.Left : Position2.Top;
|
|
374
|
+
const sourcePos = isLR ? Position2.Right : Position2.Bottom;
|
|
375
|
+
const accent = TONE_ACCENT[d.tone];
|
|
376
|
+
const isAgent = d.kind === "agent.run";
|
|
377
|
+
const model = state?.model ?? d.model;
|
|
378
|
+
const duration = fmtDuration(state?.durationMs);
|
|
379
|
+
const cost = fmtCost(state?.costUsd);
|
|
380
|
+
const tokens = isAgent ? fmtTokens(state?.inputTokens, state?.outputTokens) : void 0;
|
|
381
|
+
const runOutput = (() => {
|
|
382
|
+
const source = state?.status === "failed" && state.error ? { text: state.error, tone: "error", label: "Error" } : state && state.status !== "failed" && state.outputPreview ? { text: state.outputPreview, tone: "default", label: "Output" } : null;
|
|
383
|
+
if (!source) return null;
|
|
384
|
+
const trimmed = source.text.trim();
|
|
385
|
+
if (!trimmed) return null;
|
|
386
|
+
const shape = classifyOutput(clampPreview(trimmed));
|
|
387
|
+
return shape.kind === "empty" ? null : { shape, tone: source.tone, label: source.label };
|
|
388
|
+
})();
|
|
389
|
+
const wrapperClass = `relative flex h-full w-full flex-col overflow-hidden rounded-lg border bg-card shadow-sm transition-colors ${state ? statusBorder(state.status) : ""}`;
|
|
390
|
+
const wrapperStyle = state ? state.status === "running" ? { boxShadow: "0 0 22px -6px hsl(var(--primary))" } : void 0 : {
|
|
391
|
+
borderColor: `color-mix(in srgb, ${accent} 42%, transparent)`,
|
|
392
|
+
backgroundColor: `color-mix(in srgb, ${accent} 6%, hsl(var(--card)))`
|
|
393
|
+
};
|
|
394
|
+
const handles = /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
395
|
+
!d.isRoot && /* @__PURE__ */ jsx3(Handle, { type: "target", position: targetPos, className: HANDLE_CLASS }),
|
|
396
|
+
/* @__PURE__ */ jsx3(Handle, { type: "source", position: sourcePos, className: HANDLE_CLASS })
|
|
397
|
+
] });
|
|
398
|
+
if (compact) {
|
|
399
|
+
return /* @__PURE__ */ jsxs2("div", { className: `${wrapperClass} justify-center px-2.5`, style: wrapperStyle, children: [
|
|
400
|
+
handles,
|
|
401
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2.5", children: [
|
|
402
|
+
/* @__PURE__ */ jsx3(NodeIcon, { kind: d.kind, accent, box: 32, glyph: 16 }),
|
|
403
|
+
/* @__PURE__ */ jsxs2("div", { className: "min-w-0 flex-1", children: [
|
|
404
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-1.5", children: [
|
|
405
|
+
/* @__PURE__ */ jsx3("span", { className: "truncate font-medium text-[12px] text-foreground", children: d.title }),
|
|
406
|
+
state ? /* @__PURE__ */ jsx3(
|
|
407
|
+
"span",
|
|
408
|
+
{
|
|
409
|
+
className: "ml-auto h-1.5 w-1.5 shrink-0 rounded-full",
|
|
410
|
+
style: { backgroundColor: STATUS_COLOR[state.status] }
|
|
411
|
+
}
|
|
412
|
+
) : d.badge && /* @__PURE__ */ jsx3("span", { className: "ml-auto shrink-0 rounded bg-surface-container-high px-1 py-0.5 text-[9px] text-muted-foreground", children: d.badge })
|
|
413
|
+
] }),
|
|
414
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-1.5 text-[10px] text-muted-foreground", children: [
|
|
415
|
+
/* @__PURE__ */ jsx3("span", { className: "truncate", children: model ?? d.subtitle ?? d.kind }),
|
|
416
|
+
cost && /* @__PURE__ */ jsx3("span", { className: "ml-auto shrink-0", children: cost }),
|
|
417
|
+
duration && /* @__PURE__ */ jsxs2("span", { className: "shrink-0", children: [
|
|
418
|
+
"\xB7 ",
|
|
419
|
+
duration
|
|
420
|
+
] })
|
|
421
|
+
] })
|
|
422
|
+
] })
|
|
423
|
+
] }),
|
|
424
|
+
state?.status === "running" && /* @__PURE__ */ jsx3(
|
|
425
|
+
"div",
|
|
426
|
+
{
|
|
427
|
+
className: "mt-1.5 h-0.5 w-full overflow-hidden rounded-full",
|
|
428
|
+
style: { background: MUTED_TRACK },
|
|
429
|
+
children: /* @__PURE__ */ jsx3(
|
|
430
|
+
"div",
|
|
431
|
+
{
|
|
432
|
+
className: "h-full w-1/2 animate-pulse rounded-full",
|
|
433
|
+
style: { background: EDGE_RUNNING }
|
|
434
|
+
}
|
|
435
|
+
)
|
|
436
|
+
}
|
|
437
|
+
)
|
|
438
|
+
] });
|
|
439
|
+
}
|
|
440
|
+
return /* @__PURE__ */ jsxs2("div", { className: wrapperClass, style: wrapperStyle, children: [
|
|
441
|
+
handles,
|
|
442
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex min-h-0 flex-1 flex-col px-3 pt-2 pb-2", children: [
|
|
443
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2", children: [
|
|
444
|
+
/* @__PURE__ */ jsx3(NodeIcon, { kind: d.kind, accent, box: 24, glyph: 14 }),
|
|
445
|
+
/* @__PURE__ */ jsx3("span", { className: "truncate font-medium text-[12px] text-foreground", children: d.title }),
|
|
446
|
+
state ? /* @__PURE__ */ jsx3(
|
|
447
|
+
"span",
|
|
448
|
+
{
|
|
449
|
+
className: `ml-auto shrink-0 rounded px-1.5 py-0.5 text-[10px] ${STATUS_BADGE[state.status]}`,
|
|
450
|
+
children: STATUS_LABEL[state.status]
|
|
451
|
+
}
|
|
452
|
+
) : d.badge && /* @__PURE__ */ jsx3("span", { className: "ml-auto shrink-0 rounded bg-surface-container-high px-1.5 py-0.5 text-[10px] text-muted-foreground", children: d.badge })
|
|
453
|
+
] }),
|
|
454
|
+
d.subtitle && /* @__PURE__ */ jsx3(
|
|
455
|
+
"p",
|
|
456
|
+
{
|
|
457
|
+
className: "mt-1 truncate text-[11px] text-muted-foreground",
|
|
458
|
+
title: d.subtitle,
|
|
459
|
+
children: d.subtitle
|
|
460
|
+
}
|
|
461
|
+
),
|
|
462
|
+
d.provider && /* @__PURE__ */ jsxs2("span", { className: "mt-1.5 inline-flex w-fit items-center gap-1 rounded bg-surface-container-high px-1.5 py-0.5 text-[10px] text-muted-foreground", children: [
|
|
463
|
+
/* @__PURE__ */ jsx3(
|
|
464
|
+
ProviderIcon,
|
|
465
|
+
{
|
|
466
|
+
id: d.provider,
|
|
467
|
+
displayName: providerLabel(d.provider),
|
|
468
|
+
size: 12,
|
|
469
|
+
className: "rounded-[2px]"
|
|
470
|
+
}
|
|
471
|
+
),
|
|
472
|
+
providerLabel(d.provider)
|
|
473
|
+
] }),
|
|
474
|
+
(model || cost || tokens) && /* @__PURE__ */ jsxs2("div", { className: "mt-1.5 flex flex-wrap items-center gap-1", children: [
|
|
475
|
+
model && /* @__PURE__ */ jsx3(MetaChip, { title: model, children: model }),
|
|
476
|
+
cost && /* @__PURE__ */ jsx3(MetaChip, { children: cost }),
|
|
477
|
+
tokens && /* @__PURE__ */ jsx3(MetaChip, { children: tokens })
|
|
478
|
+
] }),
|
|
479
|
+
runOutput && /* @__PURE__ */ jsxs2("div", { className: "mt-1.5 min-h-0", children: [
|
|
480
|
+
/* @__PURE__ */ jsx3("div", { className: "mb-0.5 font-medium text-[8px] text-muted-foreground/70 uppercase tracking-[0.08em]", children: runOutput.label }),
|
|
481
|
+
/* @__PURE__ */ jsx3(NodeOutputBody, { shape: runOutput.shape, tone: runOutput.tone, rows: 2 })
|
|
482
|
+
] })
|
|
483
|
+
] }),
|
|
484
|
+
state && d.tone !== "trigger" && /* @__PURE__ */ jsx3(
|
|
485
|
+
StatusFooter,
|
|
486
|
+
{
|
|
487
|
+
status: state.status,
|
|
488
|
+
rounds: isAgent ? state.rounds : void 0,
|
|
489
|
+
elapsed: duration
|
|
490
|
+
}
|
|
491
|
+
)
|
|
492
|
+
] });
|
|
493
|
+
}
|
|
494
|
+
var NODE_TYPES = { wfNode: WorkflowNode };
|
|
495
|
+
function buildStyledEdges(base, nodeState) {
|
|
496
|
+
return base.map((e) => {
|
|
497
|
+
const status = nodeState?.[e.target]?.status;
|
|
498
|
+
const color = edgeColor(nodeState ? status : void 0);
|
|
499
|
+
return {
|
|
500
|
+
...e,
|
|
501
|
+
// The active hop flows; everything else is static.
|
|
502
|
+
animated: status === "running",
|
|
503
|
+
style: { strokeWidth: 1.75, stroke: color },
|
|
504
|
+
markerEnd: {
|
|
505
|
+
type: MarkerType.ArrowClosed,
|
|
506
|
+
width: 15,
|
|
507
|
+
height: 15,
|
|
508
|
+
color
|
|
509
|
+
}
|
|
510
|
+
};
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
function WorkflowGraph({
|
|
514
|
+
yaml,
|
|
515
|
+
variant = "full",
|
|
516
|
+
direction = "LR",
|
|
517
|
+
defaultCompact = false,
|
|
518
|
+
className,
|
|
519
|
+
nodeState,
|
|
520
|
+
onNodeClick
|
|
521
|
+
}) {
|
|
522
|
+
const colorMode = useColorMode();
|
|
523
|
+
const isPreview = variant === "preview";
|
|
524
|
+
const [userCompact, setUserCompact] = useState(defaultCompact);
|
|
525
|
+
const compact = isPreview || userCompact;
|
|
526
|
+
const rfRef = useRef(null);
|
|
527
|
+
const hasRunOverlay = nodeState !== void 0;
|
|
528
|
+
const structural = useMemo(
|
|
529
|
+
() => buildFlowGraph(yaml, {
|
|
530
|
+
nodeState: hasRunOverlay ? {} : void 0,
|
|
531
|
+
direction,
|
|
532
|
+
compact
|
|
533
|
+
}),
|
|
534
|
+
[yaml, hasRunOverlay, direction, compact]
|
|
535
|
+
);
|
|
536
|
+
const styledEdges = useMemo(
|
|
537
|
+
() => buildStyledEdges(structural.edges, nodeState),
|
|
538
|
+
[structural.edges, nodeState]
|
|
539
|
+
);
|
|
540
|
+
const [nodes, setNodes, onNodesChange] = useNodesState(structural.nodes);
|
|
541
|
+
const [edges, setEdges, onEdgesChange] = useEdgesState(styledEdges);
|
|
542
|
+
useEffect(() => {
|
|
543
|
+
setNodes(structural.nodes);
|
|
544
|
+
}, [structural, setNodes]);
|
|
545
|
+
useEffect(() => {
|
|
546
|
+
setEdges(styledEdges);
|
|
547
|
+
}, [styledEdges, setEdges]);
|
|
548
|
+
useEffect(() => {
|
|
549
|
+
const baseById = new Map(structural.nodes.map((n) => [n.id, n.data]));
|
|
550
|
+
setNodes((prev) => mergeRunState(prev, baseById, nodeState));
|
|
551
|
+
}, [nodeState, structural, setNodes]);
|
|
552
|
+
const didInitialFitRef = useRef(false);
|
|
553
|
+
useEffect(() => {
|
|
554
|
+
if (!didInitialFitRef.current) {
|
|
555
|
+
didInitialFitRef.current = true;
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
const inst = rfRef.current;
|
|
559
|
+
if (!inst || typeof requestAnimationFrame === "undefined") return;
|
|
560
|
+
const raf = requestAnimationFrame(() => inst.fitView({ padding: 0.18 }));
|
|
561
|
+
return () => cancelAnimationFrame(raf);
|
|
562
|
+
}, [structural]);
|
|
563
|
+
const handleNodeClick = useCallback(
|
|
564
|
+
(_event, node) => {
|
|
565
|
+
onNodeClick?.(node.id, node.data);
|
|
566
|
+
},
|
|
567
|
+
[onNodeClick]
|
|
568
|
+
);
|
|
569
|
+
if (structural.error || structural.nodes.length === 0) {
|
|
570
|
+
return /* @__PURE__ */ jsx3(
|
|
571
|
+
"div",
|
|
572
|
+
{
|
|
573
|
+
className: `flex items-center justify-center rounded-lg border border-border border-dashed p-4 text-center text-muted-foreground text-xs ${className ?? ""}`,
|
|
574
|
+
children: structural.error ?? "Nothing to show"
|
|
575
|
+
}
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
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(
|
|
579
|
+
ReactFlow,
|
|
580
|
+
{
|
|
581
|
+
nodes,
|
|
582
|
+
edges,
|
|
583
|
+
onNodesChange,
|
|
584
|
+
onEdgesChange,
|
|
585
|
+
nodeTypes: NODE_TYPES,
|
|
586
|
+
colorMode,
|
|
587
|
+
onInit: (inst) => {
|
|
588
|
+
rfRef.current = inst;
|
|
589
|
+
},
|
|
590
|
+
onNodeClick: onNodeClick ? handleNodeClick : void 0,
|
|
591
|
+
fitView: true,
|
|
592
|
+
fitViewOptions: { padding: 0.18 },
|
|
593
|
+
proOptions: { hideAttribution: true },
|
|
594
|
+
nodesDraggable: !isPreview,
|
|
595
|
+
nodesConnectable: false,
|
|
596
|
+
deleteKeyCode: null,
|
|
597
|
+
edgesReconnectable: false,
|
|
598
|
+
elementsSelectable: !isPreview,
|
|
599
|
+
edgesFocusable: false,
|
|
600
|
+
zoomOnScroll: false,
|
|
601
|
+
zoomOnPinch: true,
|
|
602
|
+
zoomOnDoubleClick: !isPreview,
|
|
603
|
+
panOnDrag: true,
|
|
604
|
+
preventScrolling: false,
|
|
605
|
+
minZoom: 0.2,
|
|
606
|
+
maxZoom: 1.5,
|
|
607
|
+
children: [
|
|
608
|
+
/* @__PURE__ */ jsx3(Background, { gap: 18 }),
|
|
609
|
+
!isPreview && /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
610
|
+
/* @__PURE__ */ jsx3(Controls, { showInteractive: false, position: "bottom-right" }),
|
|
611
|
+
/* @__PURE__ */ jsx3(Panel, { position: "top-left", children: /* @__PURE__ */ jsxs2(
|
|
612
|
+
"button",
|
|
613
|
+
{
|
|
614
|
+
type: "button",
|
|
615
|
+
onClick: () => setUserCompact((c) => !c),
|
|
616
|
+
"aria-pressed": userCompact,
|
|
617
|
+
title: userCompact ? "Expand nodes" : "Collapse nodes",
|
|
618
|
+
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",
|
|
619
|
+
children: [
|
|
620
|
+
userCompact ? /* @__PURE__ */ jsx3(Maximize2, { size: 12 }) : /* @__PURE__ */ jsx3(Minimize2, { size: 12 }),
|
|
621
|
+
userCompact ? "Expand" : "Compact"
|
|
622
|
+
]
|
|
623
|
+
}
|
|
624
|
+
) })
|
|
625
|
+
] })
|
|
626
|
+
]
|
|
627
|
+
}
|
|
628
|
+
) }) }) });
|
|
629
|
+
}
|
|
630
|
+
export {
|
|
631
|
+
DensityContext,
|
|
632
|
+
DirectionContext,
|
|
633
|
+
WorkflowGraph,
|
|
634
|
+
WorkflowNode,
|
|
635
|
+
buildStyledEdges
|
|
636
|
+
};
|