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