footprint-explainable-ui 0.5.0 → 0.7.2
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/README.md +158 -401
- package/dist/flowchart.cjs +764 -531
- package/dist/flowchart.cjs.map +1 -1
- package/dist/flowchart.d.cts +68 -15
- package/dist/flowchart.d.ts +68 -15
- package/dist/flowchart.js +734 -502
- package/dist/flowchart.js.map +1 -1
- package/dist/index.cjs +1248 -485
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +171 -33
- package/dist/index.d.ts +171 -33
- package/dist/index.js +1225 -469
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/flowchart.js
CHANGED
|
@@ -9,14 +9,14 @@ import {
|
|
|
9
9
|
} from "@xyflow/react";
|
|
10
10
|
|
|
11
11
|
// src/components/StageNode/StageNode.tsx
|
|
12
|
-
import { memo } from "react";
|
|
12
|
+
import { memo, useEffect as useEffect2, useRef } from "react";
|
|
13
13
|
import { Handle, Position } from "@xyflow/react";
|
|
14
14
|
|
|
15
15
|
// src/theme/ThemeProvider.tsx
|
|
16
16
|
import { createContext, useContext } from "react";
|
|
17
17
|
|
|
18
18
|
// src/theme/tokens.ts
|
|
19
|
-
var
|
|
19
|
+
var rawDefaults = {
|
|
20
20
|
colors: {
|
|
21
21
|
primary: "#6366f1",
|
|
22
22
|
success: "#22c55e",
|
|
@@ -36,6 +36,26 @@ var defaultTokens = {
|
|
|
36
36
|
mono: "'JetBrains Mono', 'Fira Code', monospace"
|
|
37
37
|
}
|
|
38
38
|
};
|
|
39
|
+
var defaultTokens = {
|
|
40
|
+
colors: {
|
|
41
|
+
primary: `var(--fp-color-primary, ${rawDefaults.colors.primary})`,
|
|
42
|
+
success: `var(--fp-color-success, ${rawDefaults.colors.success})`,
|
|
43
|
+
error: `var(--fp-color-error, ${rawDefaults.colors.error})`,
|
|
44
|
+
warning: `var(--fp-color-warning, ${rawDefaults.colors.warning})`,
|
|
45
|
+
bgPrimary: `var(--fp-bg-primary, ${rawDefaults.colors.bgPrimary})`,
|
|
46
|
+
bgSecondary: `var(--fp-bg-secondary, ${rawDefaults.colors.bgSecondary})`,
|
|
47
|
+
bgTertiary: `var(--fp-bg-tertiary, ${rawDefaults.colors.bgTertiary})`,
|
|
48
|
+
textPrimary: `var(--fp-text-primary, ${rawDefaults.colors.textPrimary})`,
|
|
49
|
+
textSecondary: `var(--fp-text-secondary, ${rawDefaults.colors.textSecondary})`,
|
|
50
|
+
textMuted: `var(--fp-text-muted, ${rawDefaults.colors.textMuted})`,
|
|
51
|
+
border: `var(--fp-border, ${rawDefaults.colors.border})`
|
|
52
|
+
},
|
|
53
|
+
radius: `var(--fp-radius, ${rawDefaults.radius})`,
|
|
54
|
+
fontFamily: {
|
|
55
|
+
sans: `var(--fp-font-sans, ${rawDefaults.fontFamily.sans})`,
|
|
56
|
+
mono: `var(--fp-font-mono, ${rawDefaults.fontFamily.mono})`
|
|
57
|
+
}
|
|
58
|
+
};
|
|
39
59
|
|
|
40
60
|
// src/theme/ThemeProvider.tsx
|
|
41
61
|
import { jsx } from "react/jsx-runtime";
|
|
@@ -72,12 +92,174 @@ var padding = {
|
|
|
72
92
|
detailed: 16
|
|
73
93
|
};
|
|
74
94
|
|
|
95
|
+
// src/theme/useDarkModeTokens.ts
|
|
96
|
+
import { useState, useEffect } from "react";
|
|
97
|
+
|
|
75
98
|
// src/components/StageNode/StageNode.tsx
|
|
76
99
|
import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
100
|
+
var KEYFRAMES_ID = "fp-stage-node-keyframes";
|
|
101
|
+
var KEYFRAMES_CSS = `
|
|
102
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
103
|
+
@keyframes fp-pulse {
|
|
104
|
+
0%, 100% { opacity: 0.4; transform: scale(1); }
|
|
105
|
+
50% { opacity: 0.15; transform: scale(1.06); }
|
|
106
|
+
}
|
|
107
|
+
@keyframes fp-blink {
|
|
108
|
+
0%, 100% { opacity: 1; }
|
|
109
|
+
50% { opacity: 0.3; }
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
@media (prefers-reduced-motion: reduce) {
|
|
113
|
+
@keyframes fp-pulse { 0%, 100% { opacity: 0.3; } }
|
|
114
|
+
@keyframes fp-blink { 0%, 100% { opacity: 1; } }
|
|
115
|
+
}
|
|
116
|
+
`;
|
|
117
|
+
var ICON_SIZE = 16;
|
|
118
|
+
function StageIcon({ type, color }) {
|
|
119
|
+
const s = ICON_SIZE;
|
|
120
|
+
const props = { width: s, height: s, viewBox: `0 0 ${s} ${s}`, fill: "none", style: { flexShrink: 0 } };
|
|
121
|
+
switch (type) {
|
|
122
|
+
// LLM / AI call — brain/sparkle
|
|
123
|
+
case "llm":
|
|
124
|
+
case "ai":
|
|
125
|
+
return /* @__PURE__ */ jsxs("svg", { ...props, children: [
|
|
126
|
+
/* @__PURE__ */ jsx2("circle", { cx: "8", cy: "8", r: "6", stroke: color, strokeWidth: "1.5" }),
|
|
127
|
+
/* @__PURE__ */ jsx2("path", { d: "M5.5 8C5.5 6.5 6.5 5 8 5S10.5 6.5 10.5 8", stroke: color, strokeWidth: "1.2", strokeLinecap: "round" }),
|
|
128
|
+
/* @__PURE__ */ jsx2("circle", { cx: "8", cy: "9.5", r: "1", fill: color }),
|
|
129
|
+
/* @__PURE__ */ jsx2("line", { x1: "8", y1: "2", x2: "8", y2: "3.5", stroke: color, strokeWidth: "1", strokeLinecap: "round" }),
|
|
130
|
+
/* @__PURE__ */ jsx2("line", { x1: "12.5", y1: "4", x2: "11.2", y2: "5", stroke: color, strokeWidth: "1", strokeLinecap: "round" }),
|
|
131
|
+
/* @__PURE__ */ jsx2("line", { x1: "3.5", y1: "4", x2: "4.8", y2: "5", stroke: color, strokeWidth: "1", strokeLinecap: "round" })
|
|
132
|
+
] });
|
|
133
|
+
// Tool / function call — gear
|
|
134
|
+
case "tool":
|
|
135
|
+
case "function":
|
|
136
|
+
return /* @__PURE__ */ jsxs("svg", { ...props, children: [
|
|
137
|
+
/* @__PURE__ */ jsx2("circle", { cx: "8", cy: "8", r: "3", stroke: color, strokeWidth: "1.5" }),
|
|
138
|
+
[0, 45, 90, 135, 180, 225, 270, 315].map((angle) => {
|
|
139
|
+
const rad = angle * Math.PI / 180;
|
|
140
|
+
const x1 = 8 + Math.cos(rad) * 4.5;
|
|
141
|
+
const y1 = 8 + Math.sin(rad) * 4.5;
|
|
142
|
+
const x2 = 8 + Math.cos(rad) * 6;
|
|
143
|
+
const y2 = 8 + Math.sin(rad) * 6;
|
|
144
|
+
return /* @__PURE__ */ jsx2("line", { x1, y1, x2, y2, stroke: color, strokeWidth: "1.5", strokeLinecap: "round" }, angle);
|
|
145
|
+
})
|
|
146
|
+
] });
|
|
147
|
+
// RAG / retrieval — magnifying glass + doc
|
|
148
|
+
case "rag":
|
|
149
|
+
case "search":
|
|
150
|
+
case "retrieval":
|
|
151
|
+
return /* @__PURE__ */ jsxs("svg", { ...props, children: [
|
|
152
|
+
/* @__PURE__ */ jsx2("circle", { cx: "7", cy: "7", r: "4", stroke: color, strokeWidth: "1.5" }),
|
|
153
|
+
/* @__PURE__ */ jsx2("line", { x1: "10", y1: "10", x2: "13.5", y2: "13.5", stroke: color, strokeWidth: "1.5", strokeLinecap: "round" }),
|
|
154
|
+
/* @__PURE__ */ jsx2("line", { x1: "5.5", y1: "6", x2: "8.5", y2: "6", stroke: color, strokeWidth: "1", strokeLinecap: "round" }),
|
|
155
|
+
/* @__PURE__ */ jsx2("line", { x1: "5.5", y1: "8", x2: "7.5", y2: "8", stroke: color, strokeWidth: "1", strokeLinecap: "round" })
|
|
156
|
+
] });
|
|
157
|
+
// Parse / process — diamond with arrows
|
|
158
|
+
case "parse":
|
|
159
|
+
case "process":
|
|
160
|
+
case "transform":
|
|
161
|
+
return /* @__PURE__ */ jsx2("svg", { ...props, children: /* @__PURE__ */ jsx2("rect", { x: "4", y: "4", width: "8", height: "8", rx: "1.5", stroke: color, strokeWidth: "1.5", transform: "rotate(45 8 8)" }) });
|
|
162
|
+
// Start / seed — play triangle
|
|
163
|
+
case "start":
|
|
164
|
+
case "seed":
|
|
165
|
+
case "init":
|
|
166
|
+
return /* @__PURE__ */ jsx2("svg", { ...props, children: /* @__PURE__ */ jsx2("path", { d: "M5 3.5L12.5 8L5 12.5V3.5Z", fill: color, opacity: "0.8" }) });
|
|
167
|
+
// End / finalize — stop square
|
|
168
|
+
case "end":
|
|
169
|
+
case "finalize":
|
|
170
|
+
case "output":
|
|
171
|
+
return /* @__PURE__ */ jsx2("svg", { ...props, children: /* @__PURE__ */ jsx2("rect", { x: "4", y: "4", width: "8", height: "8", rx: "1.5", fill: color, opacity: "0.8" }) });
|
|
172
|
+
// Agent — person silhouette
|
|
173
|
+
case "agent":
|
|
174
|
+
case "orchestrator":
|
|
175
|
+
return /* @__PURE__ */ jsxs("svg", { ...props, children: [
|
|
176
|
+
/* @__PURE__ */ jsx2("circle", { cx: "8", cy: "5", r: "2.5", stroke: color, strokeWidth: "1.5" }),
|
|
177
|
+
/* @__PURE__ */ jsx2("path", { d: "M3.5 14C3.5 11 5.5 9 8 9S12.5 11 12.5 14", stroke: color, strokeWidth: "1.5", strokeLinecap: "round" })
|
|
178
|
+
] });
|
|
179
|
+
// Swarm — multi-agent
|
|
180
|
+
case "swarm":
|
|
181
|
+
case "multi-agent":
|
|
182
|
+
return /* @__PURE__ */ jsxs("svg", { ...props, children: [
|
|
183
|
+
/* @__PURE__ */ jsx2("circle", { cx: "5", cy: "5", r: "2", stroke: color, strokeWidth: "1.2" }),
|
|
184
|
+
/* @__PURE__ */ jsx2("circle", { cx: "11", cy: "5", r: "2", stroke: color, strokeWidth: "1.2" }),
|
|
185
|
+
/* @__PURE__ */ jsx2("circle", { cx: "8", cy: "11", r: "2", stroke: color, strokeWidth: "1.2" }),
|
|
186
|
+
/* @__PURE__ */ jsx2("line", { x1: "5", y1: "7", x2: "8", y2: "9", stroke: color, strokeWidth: "1", opacity: "0.5" }),
|
|
187
|
+
/* @__PURE__ */ jsx2("line", { x1: "11", y1: "7", x2: "8", y2: "9", stroke: color, strokeWidth: "1", opacity: "0.5" })
|
|
188
|
+
] });
|
|
189
|
+
// Guard / guardrail — shield
|
|
190
|
+
case "guard":
|
|
191
|
+
case "guardrail":
|
|
192
|
+
case "validate":
|
|
193
|
+
return /* @__PURE__ */ jsxs("svg", { ...props, children: [
|
|
194
|
+
/* @__PURE__ */ jsx2("path", { d: "M8 2L3 5V9C3 11.5 5 13.5 8 14.5C11 13.5 13 11.5 13 9V5L8 2Z", stroke: color, strokeWidth: "1.5", strokeLinejoin: "round" }),
|
|
195
|
+
/* @__PURE__ */ jsx2("path", { d: "M6 8L7.5 9.5L10 6.5", stroke: color, strokeWidth: "1.2", strokeLinecap: "round", strokeLinejoin: "round" })
|
|
196
|
+
] });
|
|
197
|
+
// Stream — wave
|
|
198
|
+
case "stream":
|
|
199
|
+
case "streaming":
|
|
200
|
+
return /* @__PURE__ */ jsxs("svg", { ...props, children: [
|
|
201
|
+
/* @__PURE__ */ jsx2("path", { d: "M2 8C4 5 6 11 8 8S12 5 14 8", stroke: color, strokeWidth: "1.5", strokeLinecap: "round", fill: "none" }),
|
|
202
|
+
/* @__PURE__ */ jsx2("path", { d: "M2 11C4 8 6 14 8 11S12 8 14 11", stroke: color, strokeWidth: "1", strokeLinecap: "round", fill: "none", opacity: "0.5" })
|
|
203
|
+
] });
|
|
204
|
+
// Memory / state — database cylinder
|
|
205
|
+
case "memory":
|
|
206
|
+
case "state":
|
|
207
|
+
case "db":
|
|
208
|
+
return /* @__PURE__ */ jsxs("svg", { ...props, children: [
|
|
209
|
+
/* @__PURE__ */ jsx2("ellipse", { cx: "8", cy: "4.5", rx: "5", ry: "2", stroke: color, strokeWidth: "1.3" }),
|
|
210
|
+
/* @__PURE__ */ jsx2("line", { x1: "3", y1: "4.5", x2: "3", y2: "11.5", stroke: color, strokeWidth: "1.3" }),
|
|
211
|
+
/* @__PURE__ */ jsx2("line", { x1: "13", y1: "4.5", x2: "13", y2: "11.5", stroke: color, strokeWidth: "1.3" }),
|
|
212
|
+
/* @__PURE__ */ jsx2("ellipse", { cx: "8", cy: "11.5", rx: "5", ry: "2", stroke: color, strokeWidth: "1.3" })
|
|
213
|
+
] });
|
|
214
|
+
// Loop — circular arrow
|
|
215
|
+
case "loop":
|
|
216
|
+
case "retry":
|
|
217
|
+
return /* @__PURE__ */ jsxs("svg", { ...props, children: [
|
|
218
|
+
/* @__PURE__ */ jsx2("path", { d: "M12 8A4 4 0 1 1 8 4", stroke: color, strokeWidth: "1.5", strokeLinecap: "round", fill: "none" }),
|
|
219
|
+
/* @__PURE__ */ jsx2("path", { d: "M8 1.5L10.5 4L8 6.5", stroke: color, strokeWidth: "1.3", strokeLinecap: "round", strokeLinejoin: "round", fill: "none" })
|
|
220
|
+
] });
|
|
221
|
+
// Lazy / service — cloud (deferred resolution, loaded on demand)
|
|
222
|
+
case "lazy":
|
|
223
|
+
case "service":
|
|
224
|
+
case "cloud":
|
|
225
|
+
return /* @__PURE__ */ jsx2("svg", { ...props, children: /* @__PURE__ */ jsx2(
|
|
226
|
+
"path",
|
|
227
|
+
{
|
|
228
|
+
d: "M4.5 12C2.8 12 1.5 10.7 1.5 9C1.5 7.5 2.5 6.3 3.8 6C4 4 5.8 2.5 8 2.5C9.8 2.5 11.3 3.5 11.9 5C13.9 5.2 15.5 6.8 15.5 8.8C15.5 10.8 13.9 12.5 11.8 12.5H4.5",
|
|
229
|
+
stroke: color,
|
|
230
|
+
strokeWidth: "1.3",
|
|
231
|
+
strokeLinecap: "round",
|
|
232
|
+
fill: "none"
|
|
233
|
+
}
|
|
234
|
+
) });
|
|
235
|
+
// Decision — diamond (already handled by isDecider shape)
|
|
236
|
+
case "decision":
|
|
237
|
+
case "router":
|
|
238
|
+
return /* @__PURE__ */ jsxs("svg", { ...props, children: [
|
|
239
|
+
/* @__PURE__ */ jsx2("path", { d: "M8 2L14 8L8 14L2 8Z", stroke: color, strokeWidth: "1.5", fill: "none" }),
|
|
240
|
+
/* @__PURE__ */ jsx2("circle", { cx: "8", cy: "8", r: "1.5", fill: color })
|
|
241
|
+
] });
|
|
242
|
+
default:
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
77
246
|
var StageNode = memo(function StageNode2({
|
|
78
247
|
data
|
|
79
248
|
}) {
|
|
80
|
-
const { label, active, done, error, linked, stepNumbers, dimmed, isSubflow, description } = data;
|
|
249
|
+
const { label, active, done, error, linked, icon, stepNumbers, dimmed, isSubflow, isLazy, isDecider, isFork, description } = data;
|
|
250
|
+
const effectiveIcon = icon || (isLazy ? "lazy" : void 0);
|
|
251
|
+
const isLazyUnresolved = isLazy && !done && !active;
|
|
252
|
+
const injectedRef = useRef(false);
|
|
253
|
+
useEffect2(() => {
|
|
254
|
+
if (injectedRef.current) return;
|
|
255
|
+
if (typeof document !== "undefined" && !document.getElementById(KEYFRAMES_ID)) {
|
|
256
|
+
const styleEl = document.createElement("style");
|
|
257
|
+
styleEl.id = KEYFRAMES_ID;
|
|
258
|
+
styleEl.textContent = KEYFRAMES_CSS;
|
|
259
|
+
document.head.appendChild(styleEl);
|
|
260
|
+
}
|
|
261
|
+
injectedRef.current = true;
|
|
262
|
+
}, []);
|
|
81
263
|
const isOnPath = active || done;
|
|
82
264
|
const bg = active ? theme.primary : done ? theme.success : error ? theme.error : theme.bgSecondary;
|
|
83
265
|
const borderColor = active ? theme.primary : done ? theme.success : error ? theme.error : theme.border;
|
|
@@ -139,7 +321,8 @@ var StageNode = memo(function StageNode2({
|
|
|
139
321
|
style: {
|
|
140
322
|
position: "absolute",
|
|
141
323
|
inset: -6,
|
|
142
|
-
borderRadius: `calc(${theme.radius} + 4px)`,
|
|
324
|
+
borderRadius: isDecider ? 0 : `calc(${theme.radius} + 4px)`,
|
|
325
|
+
transform: isDecider ? "rotate(45deg)" : void 0,
|
|
143
326
|
border: `2px solid ${theme.primary}`,
|
|
144
327
|
opacity: 0.4,
|
|
145
328
|
animation: "fp-pulse 2s ease-in-out infinite"
|
|
@@ -152,107 +335,175 @@ var StageNode = memo(function StageNode2({
|
|
|
152
335
|
style: {
|
|
153
336
|
position: "absolute",
|
|
154
337
|
inset: -6,
|
|
155
|
-
borderRadius: `calc(${theme.radius} + 4px)`,
|
|
338
|
+
borderRadius: isDecider ? 0 : `calc(${theme.radius} + 4px)`,
|
|
339
|
+
transform: isDecider ? "rotate(45deg)" : void 0,
|
|
156
340
|
border: `2px solid ${theme.primary}`,
|
|
157
341
|
opacity: 0.3,
|
|
158
342
|
animation: "fp-pulse 1.5s ease-out infinite"
|
|
159
343
|
}
|
|
160
344
|
}
|
|
161
345
|
),
|
|
162
|
-
/* @__PURE__ */
|
|
346
|
+
isDecider ? /* @__PURE__ */ jsx2(
|
|
163
347
|
"div",
|
|
164
348
|
{
|
|
165
349
|
style: {
|
|
166
350
|
background: bg,
|
|
167
|
-
border: `2px solid ${borderColor}`,
|
|
168
|
-
borderRadius:
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
flexDirection: "column",
|
|
172
|
-
alignItems: "center",
|
|
173
|
-
gap: description ? 2 : 0,
|
|
351
|
+
border: `2px ${isLazyUnresolved ? "dashed" : "solid"} ${borderColor}`,
|
|
352
|
+
borderRadius: 4,
|
|
353
|
+
transform: "rotate(45deg)",
|
|
354
|
+
padding: 20,
|
|
174
355
|
boxShadow: shadow,
|
|
175
356
|
transition: "all 0.3s ease",
|
|
176
|
-
|
|
177
|
-
|
|
357
|
+
display: "flex",
|
|
358
|
+
alignItems: "center",
|
|
178
359
|
justifyContent: "center"
|
|
179
360
|
},
|
|
180
|
-
children:
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
"
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
361
|
+
children: /* @__PURE__ */ jsxs(
|
|
362
|
+
"div",
|
|
363
|
+
{
|
|
364
|
+
style: {
|
|
365
|
+
transform: "rotate(-45deg)",
|
|
366
|
+
display: "flex",
|
|
367
|
+
flexDirection: "column",
|
|
368
|
+
alignItems: "center",
|
|
369
|
+
gap: 2,
|
|
370
|
+
fontFamily: theme.fontSans
|
|
371
|
+
},
|
|
372
|
+
children: [
|
|
373
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 5 }, children: [
|
|
374
|
+
icon && /* @__PURE__ */ jsx2(StageIcon, { type: icon, color: textColor }),
|
|
375
|
+
!icon && /* @__PURE__ */ jsx2("span", { style: { fontSize: 10, color: textColor }, children: "\u25C7" }),
|
|
376
|
+
/* @__PURE__ */ jsx2(
|
|
377
|
+
"span",
|
|
378
|
+
{
|
|
379
|
+
style: {
|
|
380
|
+
fontSize: 12,
|
|
381
|
+
fontWeight: 600,
|
|
382
|
+
color: textColor,
|
|
383
|
+
whiteSpace: "nowrap"
|
|
384
|
+
},
|
|
385
|
+
children: label
|
|
386
|
+
}
|
|
387
|
+
)
|
|
388
|
+
] }),
|
|
389
|
+
description && /* @__PURE__ */ jsx2(
|
|
390
|
+
"span",
|
|
391
|
+
{
|
|
392
|
+
style: {
|
|
393
|
+
fontSize: 9,
|
|
394
|
+
fontWeight: 400,
|
|
395
|
+
color: textColor,
|
|
396
|
+
opacity: 0.7,
|
|
397
|
+
whiteSpace: "nowrap",
|
|
398
|
+
overflow: "hidden",
|
|
399
|
+
textOverflow: "ellipsis",
|
|
400
|
+
maxWidth: 130
|
|
401
|
+
},
|
|
402
|
+
children: description
|
|
193
403
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
404
|
+
)
|
|
405
|
+
]
|
|
406
|
+
}
|
|
407
|
+
)
|
|
408
|
+
}
|
|
409
|
+
) : (
|
|
410
|
+
/* Standard rectangular node */
|
|
411
|
+
/* @__PURE__ */ jsxs(
|
|
412
|
+
"div",
|
|
413
|
+
{
|
|
414
|
+
style: {
|
|
415
|
+
background: bg,
|
|
416
|
+
border: `2px ${isLazyUnresolved ? "dashed" : "solid"} ${borderColor}`,
|
|
417
|
+
borderRadius: theme.radius,
|
|
418
|
+
padding: description ? "8px 16px" : "10px 20px",
|
|
419
|
+
display: "flex",
|
|
420
|
+
flexDirection: "column",
|
|
421
|
+
alignItems: "center",
|
|
422
|
+
gap: description ? 2 : 0,
|
|
423
|
+
boxShadow: shadow,
|
|
424
|
+
transition: "all 0.3s ease",
|
|
425
|
+
fontFamily: theme.fontSans,
|
|
426
|
+
minWidth: 100,
|
|
427
|
+
justifyContent: "center"
|
|
428
|
+
},
|
|
429
|
+
children: [
|
|
430
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
|
|
431
|
+
effectiveIcon && /* @__PURE__ */ jsx2(StageIcon, { type: effectiveIcon, color: textColor }),
|
|
432
|
+
done && !effectiveIcon && /* @__PURE__ */ jsx2("span", { style: { fontSize: 10, color: textColor }, children: "\u2713" }),
|
|
433
|
+
active && !effectiveIcon && /* @__PURE__ */ jsx2(
|
|
434
|
+
"span",
|
|
435
|
+
{
|
|
436
|
+
style: {
|
|
437
|
+
width: 8,
|
|
438
|
+
height: 8,
|
|
439
|
+
borderRadius: "50%",
|
|
440
|
+
background: "#fff",
|
|
441
|
+
animation: "fp-blink 1s ease-in-out infinite",
|
|
442
|
+
flexShrink: 0
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
),
|
|
446
|
+
error && !effectiveIcon && /* @__PURE__ */ jsx2("span", { style: { fontSize: 10, color: textColor }, children: "\u2717" }),
|
|
447
|
+
/* @__PURE__ */ jsx2(
|
|
448
|
+
"span",
|
|
449
|
+
{
|
|
450
|
+
style: {
|
|
451
|
+
fontSize: 13,
|
|
452
|
+
fontWeight: 500,
|
|
453
|
+
color: textColor,
|
|
454
|
+
whiteSpace: "nowrap"
|
|
455
|
+
},
|
|
456
|
+
children: label
|
|
457
|
+
}
|
|
458
|
+
),
|
|
459
|
+
isSubflow && /* @__PURE__ */ jsx2(
|
|
460
|
+
"span",
|
|
461
|
+
{
|
|
462
|
+
style: {
|
|
463
|
+
display: "inline-flex",
|
|
464
|
+
alignItems: "center",
|
|
465
|
+
justifyContent: "center",
|
|
466
|
+
width: 16,
|
|
467
|
+
height: 16,
|
|
468
|
+
borderRadius: 3,
|
|
469
|
+
border: `1.5px solid ${textColor}`,
|
|
470
|
+
position: "relative",
|
|
471
|
+
opacity: 0.7,
|
|
472
|
+
flexShrink: 0
|
|
473
|
+
},
|
|
474
|
+
children: /* @__PURE__ */ jsx2(
|
|
475
|
+
"span",
|
|
476
|
+
{
|
|
477
|
+
style: {
|
|
478
|
+
width: 8,
|
|
479
|
+
height: 8,
|
|
480
|
+
borderRadius: 2,
|
|
481
|
+
border: `1px solid ${textColor}`
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
)
|
|
485
|
+
}
|
|
486
|
+
)
|
|
487
|
+
] }),
|
|
488
|
+
description && /* @__PURE__ */ jsx2(
|
|
198
489
|
"span",
|
|
199
490
|
{
|
|
200
491
|
style: {
|
|
201
|
-
fontSize:
|
|
202
|
-
fontWeight:
|
|
492
|
+
fontSize: 10,
|
|
493
|
+
fontWeight: 400,
|
|
203
494
|
color: textColor,
|
|
204
|
-
whiteSpace: "nowrap"
|
|
205
|
-
},
|
|
206
|
-
children: label
|
|
207
|
-
}
|
|
208
|
-
),
|
|
209
|
-
isSubflow && /* @__PURE__ */ jsx2(
|
|
210
|
-
"span",
|
|
211
|
-
{
|
|
212
|
-
style: {
|
|
213
|
-
display: "inline-flex",
|
|
214
|
-
alignItems: "center",
|
|
215
|
-
justifyContent: "center",
|
|
216
|
-
width: 16,
|
|
217
|
-
height: 16,
|
|
218
|
-
borderRadius: 3,
|
|
219
|
-
border: `1.5px solid ${textColor}`,
|
|
220
|
-
position: "relative",
|
|
221
495
|
opacity: 0.7,
|
|
222
|
-
|
|
496
|
+
whiteSpace: "nowrap",
|
|
497
|
+
overflow: "hidden",
|
|
498
|
+
textOverflow: "ellipsis",
|
|
499
|
+
maxWidth: 160
|
|
223
500
|
},
|
|
224
|
-
children:
|
|
225
|
-
"span",
|
|
226
|
-
{
|
|
227
|
-
style: {
|
|
228
|
-
width: 8,
|
|
229
|
-
height: 8,
|
|
230
|
-
borderRadius: 2,
|
|
231
|
-
border: `1px solid ${textColor}`
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
)
|
|
501
|
+
children: description
|
|
235
502
|
}
|
|
236
503
|
)
|
|
237
|
-
]
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
{
|
|
241
|
-
style: {
|
|
242
|
-
fontSize: 10,
|
|
243
|
-
fontWeight: 400,
|
|
244
|
-
color: textColor,
|
|
245
|
-
opacity: 0.7,
|
|
246
|
-
whiteSpace: "nowrap",
|
|
247
|
-
overflow: "hidden",
|
|
248
|
-
textOverflow: "ellipsis",
|
|
249
|
-
maxWidth: 160
|
|
250
|
-
},
|
|
251
|
-
children: description
|
|
252
|
-
}
|
|
253
|
-
)
|
|
254
|
-
]
|
|
255
|
-
}
|
|
504
|
+
]
|
|
505
|
+
}
|
|
506
|
+
)
|
|
256
507
|
)
|
|
257
508
|
]
|
|
258
509
|
}
|
|
@@ -393,23 +644,24 @@ function FlowchartView({
|
|
|
393
644
|
}
|
|
394
645
|
|
|
395
646
|
// src/components/FlowchartView/TracedFlowchartView.tsx
|
|
396
|
-
import {
|
|
647
|
+
import { useMemo as useMemo2, useCallback as useCallback2, useEffect as useEffect3 } from "react";
|
|
397
648
|
import {
|
|
398
649
|
ReactFlow as ReactFlow2,
|
|
399
650
|
Background as Background2,
|
|
400
|
-
BackgroundVariant as BackgroundVariant2
|
|
651
|
+
BackgroundVariant as BackgroundVariant2,
|
|
652
|
+
useReactFlow
|
|
401
653
|
} from "@xyflow/react";
|
|
402
654
|
|
|
403
655
|
// src/components/FlowchartView/specToReactFlow.ts
|
|
404
656
|
var DEFAULT_COLORS = {
|
|
405
|
-
edgeDefault:
|
|
406
|
-
edgeExecuted:
|
|
407
|
-
edgeActive:
|
|
408
|
-
edgeLoop:
|
|
409
|
-
labelDefault:
|
|
410
|
-
labelExecuted:
|
|
411
|
-
labelLoop:
|
|
412
|
-
pathGlow: `${
|
|
657
|
+
edgeDefault: rawDefaults.colors.textMuted,
|
|
658
|
+
edgeExecuted: rawDefaults.colors.success,
|
|
659
|
+
edgeActive: rawDefaults.colors.primary,
|
|
660
|
+
edgeLoop: rawDefaults.colors.warning,
|
|
661
|
+
labelDefault: rawDefaults.colors.textSecondary,
|
|
662
|
+
labelExecuted: rawDefaults.colors.success,
|
|
663
|
+
labelLoop: rawDefaults.colors.warning,
|
|
664
|
+
pathGlow: `${rawDefaults.colors.success}4D`
|
|
413
665
|
// ~30% opacity hex
|
|
414
666
|
};
|
|
415
667
|
var Y_STEP = 100;
|
|
@@ -417,122 +669,33 @@ var X_SPREAD = 200;
|
|
|
417
669
|
function nid(n) {
|
|
418
670
|
return n.name || n.id || `spec-${Math.random()}`;
|
|
419
671
|
}
|
|
420
|
-
function
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
const c = state.colors;
|
|
424
|
-
const executed = o && o.executedStages.has(source) && o.executedStages.has(target);
|
|
425
|
-
const isLeadingEdge = o && source === o.activeStage && !o.doneStages.has(target);
|
|
426
|
-
if (isLoop) {
|
|
427
|
-
let loopExecuted = false;
|
|
428
|
-
if (o?.executionOrder) {
|
|
429
|
-
const lastSourceIdx = o.executionOrder.lastIndexOf(source);
|
|
430
|
-
if (lastSourceIdx >= 0) {
|
|
431
|
-
loopExecuted = o.executionOrder.slice(lastSourceIdx + 1).includes(target);
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
state.edges.push({
|
|
435
|
-
id: `se${state.edgeCounter}`,
|
|
436
|
-
source,
|
|
437
|
-
target,
|
|
438
|
-
sourceHandle: "loop-source",
|
|
439
|
-
targetHandle: "loop-target",
|
|
440
|
-
label: label ?? "loop",
|
|
441
|
-
type: "smoothstep",
|
|
442
|
-
pathOptions: { offset: 40, borderRadius: 16 },
|
|
443
|
-
style: {
|
|
444
|
-
stroke: c.edgeLoop,
|
|
445
|
-
strokeWidth: loopExecuted ? 3 : 2,
|
|
446
|
-
strokeDasharray: "6 3",
|
|
447
|
-
opacity: o && !loopExecuted ? 0.35 : 1
|
|
448
|
-
},
|
|
449
|
-
labelStyle: { fontSize: 10, fontWeight: 700, fill: c.labelLoop },
|
|
450
|
-
animated: loopExecuted,
|
|
451
|
-
zIndex: 2
|
|
452
|
-
});
|
|
453
|
-
return;
|
|
454
|
-
}
|
|
455
|
-
if (executed) {
|
|
456
|
-
state.edges.push({
|
|
457
|
-
id: `se${state.edgeCounter}-glow`,
|
|
458
|
-
source,
|
|
459
|
-
target,
|
|
460
|
-
style: {
|
|
461
|
-
stroke: c.pathGlow,
|
|
462
|
-
strokeWidth: 8,
|
|
463
|
-
opacity: 0.4
|
|
464
|
-
},
|
|
465
|
-
zIndex: 0,
|
|
466
|
-
selectable: false,
|
|
467
|
-
focusable: false
|
|
468
|
-
});
|
|
469
|
-
state.edges.push({
|
|
470
|
-
id: `se${state.edgeCounter}`,
|
|
471
|
-
source,
|
|
472
|
-
target,
|
|
473
|
-
label,
|
|
474
|
-
style: {
|
|
475
|
-
stroke: isLeadingEdge ? c.edgeActive : c.edgeExecuted,
|
|
476
|
-
strokeWidth: 3.5
|
|
477
|
-
},
|
|
478
|
-
labelStyle: { fontSize: 10, fontWeight: 600, fill: c.labelExecuted },
|
|
479
|
-
animated: !!isLeadingEdge,
|
|
480
|
-
zIndex: 1
|
|
481
|
-
});
|
|
482
|
-
} else {
|
|
483
|
-
state.edges.push({
|
|
484
|
-
id: `se${state.edgeCounter}`,
|
|
485
|
-
source,
|
|
486
|
-
target,
|
|
487
|
-
label,
|
|
488
|
-
style: {
|
|
489
|
-
stroke: c.edgeDefault,
|
|
490
|
-
strokeWidth: 1.5,
|
|
491
|
-
opacity: o ? 0.3 : 1
|
|
492
|
-
},
|
|
493
|
-
labelStyle: { fontSize: 10, fill: c.labelDefault }
|
|
494
|
-
});
|
|
672
|
+
function registerNode(state, node) {
|
|
673
|
+
if (node.id && node.name) {
|
|
674
|
+
state.idToName.set(node.id, node.name);
|
|
495
675
|
}
|
|
496
676
|
}
|
|
497
|
-
function
|
|
677
|
+
function walkLayout(node, state, x, y) {
|
|
678
|
+
if (!node) return { lastIds: [], bottomY: y };
|
|
679
|
+
registerNode(state, node);
|
|
498
680
|
const id = nid(node);
|
|
499
681
|
if (state.seen.has(id)) {
|
|
500
682
|
return { lastIds: [id], bottomY: y };
|
|
501
683
|
}
|
|
502
684
|
state.seen.add(id);
|
|
503
|
-
const isDecider = node.type === "decider" || node.hasDecider;
|
|
685
|
+
const isDecider = node.type === "decider" || !!node.hasDecider;
|
|
504
686
|
const isFork = node.type === "fork";
|
|
505
|
-
const o = state.overlay;
|
|
506
|
-
const isDone = o ? o.doneStages.has(id) : false;
|
|
507
|
-
const isActive = o ? o.activeStage === id : false;
|
|
508
|
-
const wasExecuted = o ? o.executedStages.has(id) : false;
|
|
509
|
-
const dimmed = o && !wasExecuted;
|
|
510
|
-
let stepNumbers;
|
|
511
|
-
if (o?.executionOrder) {
|
|
512
|
-
const nums = [];
|
|
513
|
-
for (let i = 0; i < o.executionOrder.length; i++) {
|
|
514
|
-
if (o.executionOrder[i] === id) nums.push(i + 1);
|
|
515
|
-
}
|
|
516
|
-
if (nums.length > 0) stepNumbers = nums;
|
|
517
|
-
}
|
|
518
687
|
state.nodes.push({
|
|
519
688
|
id,
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
dimmed,
|
|
531
|
-
stepNumbers,
|
|
532
|
-
isSubflow: !!node.isSubflowRoot
|
|
533
|
-
},
|
|
534
|
-
type: "stage",
|
|
535
|
-
style: dimmed ? { opacity: 0.35 } : void 0
|
|
689
|
+
x,
|
|
690
|
+
y,
|
|
691
|
+
label: node.name,
|
|
692
|
+
isDecider,
|
|
693
|
+
isFork,
|
|
694
|
+
description: node.description,
|
|
695
|
+
icon: node.icon,
|
|
696
|
+
subflowId: node.subflowId,
|
|
697
|
+
isSubflow: !!node.isSubflowRoot,
|
|
698
|
+
isLazy: node.isLazy
|
|
536
699
|
});
|
|
537
700
|
let lastIds = [id];
|
|
538
701
|
let bottomY = y;
|
|
@@ -543,120 +706,257 @@ function walk(node, state, x, y) {
|
|
|
543
706
|
const childResults = [];
|
|
544
707
|
for (let i = 0; i < node.children.length; i++) {
|
|
545
708
|
const child = node.children[i];
|
|
709
|
+
if (!child) continue;
|
|
546
710
|
const childX = startX + i * X_SPREAD;
|
|
547
711
|
const edgeLabel = node.branchIds?.[i];
|
|
548
|
-
|
|
549
|
-
|
|
712
|
+
state.edgeCounter++;
|
|
713
|
+
state.edges.push({ id: `se${state.edgeCounter}`, source: id, target: nid(child), label: edgeLabel, isLoop: false });
|
|
714
|
+
const result = walkLayout(child, state, childX, childY);
|
|
550
715
|
childResults.push(result);
|
|
551
716
|
}
|
|
552
717
|
lastIds = childResults.flatMap((r) => r.lastIds);
|
|
553
718
|
bottomY = Math.max(...childResults.map((r) => r.bottomY));
|
|
554
719
|
}
|
|
555
720
|
if (node.loopTarget) {
|
|
556
|
-
|
|
721
|
+
const resolvedTarget = state.idToName.get(node.loopTarget) ?? node.loopTarget;
|
|
722
|
+
state.edgeCounter++;
|
|
723
|
+
state.edges.push({ id: `se${state.edgeCounter}`, source: id, target: resolvedTarget, label: "loop", isLoop: true });
|
|
557
724
|
}
|
|
558
725
|
if (node.next) {
|
|
726
|
+
const rawNextId = nid(node.next);
|
|
727
|
+
const resolvedNextId = state.idToName.get(rawNextId) ?? rawNextId;
|
|
728
|
+
const isLoopRef = node.loopTarget && state.seen.has(resolvedNextId);
|
|
729
|
+
if (isLoopRef) {
|
|
730
|
+
return { lastIds, bottomY };
|
|
731
|
+
}
|
|
559
732
|
const nextY = bottomY + Y_STEP;
|
|
560
|
-
const nextId = nid(node.next);
|
|
561
733
|
for (const lid of lastIds) {
|
|
562
|
-
|
|
563
|
-
|
|
734
|
+
state.edgeCounter++;
|
|
735
|
+
state.edges.push({ id: `se${state.edgeCounter}`, source: lid, target: resolvedNextId, isLoop: false });
|
|
564
736
|
}
|
|
565
|
-
|
|
566
|
-
return result;
|
|
737
|
+
return walkLayout(node.next, state, x, nextY);
|
|
567
738
|
}
|
|
568
739
|
return { lastIds, bottomY };
|
|
569
740
|
}
|
|
570
|
-
function
|
|
741
|
+
function specToLayout(spec) {
|
|
571
742
|
const state = {
|
|
572
743
|
nodes: [],
|
|
573
744
|
edges: [],
|
|
574
745
|
edgeCounter: 0,
|
|
575
746
|
seen: /* @__PURE__ */ new Set(),
|
|
576
|
-
|
|
577
|
-
colors: { ...DEFAULT_COLORS, ...colors }
|
|
747
|
+
idToName: /* @__PURE__ */ new Map()
|
|
578
748
|
};
|
|
579
|
-
|
|
580
|
-
return { nodes: state.nodes, edges: state.edges };
|
|
749
|
+
walkLayout(spec, state, 300, 0);
|
|
750
|
+
return { nodes: state.nodes, edges: state.edges, idToName: state.idToName };
|
|
581
751
|
}
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
function collectSubflows(node) {
|
|
596
|
-
if (node.isSubflowRoot && node.subflowStructure) {
|
|
597
|
-
const id = node.name || node.id || "";
|
|
598
|
-
map.set(id, node);
|
|
752
|
+
function applyOverlay(layout, overlay, colors) {
|
|
753
|
+
const c = { ...DEFAULT_COLORS, ...colors };
|
|
754
|
+
const o = overlay ?? null;
|
|
755
|
+
const nodes = layout.nodes.map((ln) => {
|
|
756
|
+
const isDone = o ? o.doneStages.has(ln.id) : false;
|
|
757
|
+
const isActive = o ? o.activeStage === ln.id : false;
|
|
758
|
+
const wasExecuted = o ? o.executedStages.has(ln.id) : false;
|
|
759
|
+
const dimmed = o && !wasExecuted;
|
|
760
|
+
let stepNumbers;
|
|
761
|
+
if (o?.executionOrder) {
|
|
762
|
+
const nums = [];
|
|
763
|
+
for (let i = 0; i < o.executionOrder.length; i++) {
|
|
764
|
+
if (o.executionOrder[i] === ln.id) nums.push(i + 1);
|
|
599
765
|
}
|
|
600
|
-
if (
|
|
601
|
-
if (node.next) collectSubflows(node.next);
|
|
766
|
+
if (nums.length > 0) stepNumbers = nums;
|
|
602
767
|
}
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
768
|
+
return {
|
|
769
|
+
id: ln.id,
|
|
770
|
+
position: { x: ln.x, y: ln.y },
|
|
771
|
+
data: {
|
|
772
|
+
label: ln.label,
|
|
773
|
+
active: isActive,
|
|
774
|
+
done: isDone,
|
|
775
|
+
error: false,
|
|
776
|
+
isDecider: ln.isDecider,
|
|
777
|
+
isFork: ln.isFork,
|
|
778
|
+
description: ln.description,
|
|
779
|
+
icon: ln.icon,
|
|
780
|
+
subflowId: ln.subflowId,
|
|
781
|
+
dimmed,
|
|
782
|
+
stepNumbers,
|
|
783
|
+
isSubflow: ln.isSubflow,
|
|
784
|
+
isLazy: ln.isLazy
|
|
785
|
+
},
|
|
786
|
+
type: "stage",
|
|
787
|
+
style: dimmed ? { opacity: 0.35 } : void 0
|
|
611
788
|
};
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
const
|
|
615
|
-
(
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
description: subflowNode.description
|
|
789
|
+
});
|
|
790
|
+
const edges = [];
|
|
791
|
+
for (const le of layout.edges) {
|
|
792
|
+
const executed = o && o.executedStages.has(le.source) && o.executedStages.has(le.target);
|
|
793
|
+
const isLeadingEdge = o && le.source === o.activeStage && !o.doneStages.has(le.target);
|
|
794
|
+
if (le.isLoop) {
|
|
795
|
+
let loopExecuted = false;
|
|
796
|
+
if (o?.executionOrder) {
|
|
797
|
+
const lastSourceIdx = o.executionOrder.lastIndexOf(le.source);
|
|
798
|
+
if (lastSourceIdx >= 0) {
|
|
799
|
+
loopExecuted = o.executionOrder.slice(lastSourceIdx + 1).includes(le.target);
|
|
624
800
|
}
|
|
625
|
-
]);
|
|
626
|
-
return true;
|
|
627
|
-
},
|
|
628
|
-
[subflowMap]
|
|
629
|
-
);
|
|
630
|
-
const navigateTo = useCallback2(
|
|
631
|
-
(level) => {
|
|
632
|
-
if (level === 0) {
|
|
633
|
-
setStack([]);
|
|
634
|
-
} else {
|
|
635
|
-
setStack((prev) => prev.slice(0, level));
|
|
636
801
|
}
|
|
802
|
+
edges.push({
|
|
803
|
+
id: le.id,
|
|
804
|
+
source: le.source,
|
|
805
|
+
target: le.target,
|
|
806
|
+
sourceHandle: "loop-source",
|
|
807
|
+
targetHandle: "loop-target",
|
|
808
|
+
label: le.label ?? "loop",
|
|
809
|
+
type: "smoothstep",
|
|
810
|
+
pathOptions: { offset: 40, borderRadius: 16 },
|
|
811
|
+
style: {
|
|
812
|
+
stroke: c.edgeLoop,
|
|
813
|
+
strokeWidth: loopExecuted ? 3 : 2,
|
|
814
|
+
strokeDasharray: "6 3",
|
|
815
|
+
opacity: o && !loopExecuted ? 0.35 : 1
|
|
816
|
+
},
|
|
817
|
+
labelStyle: { fontSize: 10, fontWeight: 700, fill: c.labelLoop },
|
|
818
|
+
animated: loopExecuted,
|
|
819
|
+
zIndex: 2
|
|
820
|
+
});
|
|
821
|
+
} else if (executed) {
|
|
822
|
+
edges.push({
|
|
823
|
+
id: `${le.id}-glow`,
|
|
824
|
+
source: le.source,
|
|
825
|
+
target: le.target,
|
|
826
|
+
style: { stroke: c.pathGlow, strokeWidth: 8, opacity: 0.4 },
|
|
827
|
+
zIndex: 0,
|
|
828
|
+
selectable: false,
|
|
829
|
+
focusable: false
|
|
830
|
+
});
|
|
831
|
+
edges.push({
|
|
832
|
+
id: le.id,
|
|
833
|
+
source: le.source,
|
|
834
|
+
target: le.target,
|
|
835
|
+
label: le.label,
|
|
836
|
+
style: {
|
|
837
|
+
stroke: isLeadingEdge ? c.edgeActive : c.edgeExecuted,
|
|
838
|
+
strokeWidth: 3.5
|
|
839
|
+
},
|
|
840
|
+
labelStyle: { fontSize: 10, fontWeight: 600, fill: c.labelExecuted },
|
|
841
|
+
animated: !!isLeadingEdge,
|
|
842
|
+
zIndex: 1
|
|
843
|
+
});
|
|
844
|
+
} else {
|
|
845
|
+
edges.push({
|
|
846
|
+
id: le.id,
|
|
847
|
+
source: le.source,
|
|
848
|
+
target: le.target,
|
|
849
|
+
label: le.label,
|
|
850
|
+
style: {
|
|
851
|
+
stroke: c.edgeDefault,
|
|
852
|
+
strokeWidth: 1.5,
|
|
853
|
+
opacity: o ? 0.3 : 1
|
|
854
|
+
},
|
|
855
|
+
labelStyle: { fontSize: 10, fill: c.labelDefault }
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
return { nodes, edges };
|
|
860
|
+
}
|
|
861
|
+
function specToReactFlow(spec, overlay, colors) {
|
|
862
|
+
const layout = specToLayout(spec);
|
|
863
|
+
return applyOverlay(layout, overlay, colors);
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// src/components/FlowchartView/TracedFlowchartView.tsx
|
|
867
|
+
import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
868
|
+
var defaultNodeTypes = { stage: StageNode };
|
|
869
|
+
function FitViewOnResize() {
|
|
870
|
+
const { fitView } = useReactFlow();
|
|
871
|
+
useEffect3(() => {
|
|
872
|
+
const handler = () => {
|
|
873
|
+
requestAnimationFrame(() => fitView({ padding: 0.3 }));
|
|
874
|
+
};
|
|
875
|
+
window.addEventListener("resize", handler);
|
|
876
|
+
return () => window.removeEventListener("resize", handler);
|
|
877
|
+
}, [fitView]);
|
|
878
|
+
return null;
|
|
879
|
+
}
|
|
880
|
+
function TracedFlowchartView({
|
|
881
|
+
spec,
|
|
882
|
+
snapshots,
|
|
883
|
+
snapshotIndex = 0,
|
|
884
|
+
onNodeClick,
|
|
885
|
+
nodeTypes: customNodeTypes,
|
|
886
|
+
unstyled = false,
|
|
887
|
+
className,
|
|
888
|
+
style
|
|
889
|
+
}) {
|
|
890
|
+
const nodeTypes2 = customNodeTypes ?? defaultNodeTypes;
|
|
891
|
+
const overlay = useMemo2(() => {
|
|
892
|
+
if (!snapshots || snapshots.length === 0) return void 0;
|
|
893
|
+
const executionOrder = snapshots.slice(0, snapshotIndex + 1).map((s) => s.stageLabel);
|
|
894
|
+
const doneStages = new Set(
|
|
895
|
+
snapshots.slice(0, snapshotIndex).map((s) => s.stageLabel)
|
|
896
|
+
);
|
|
897
|
+
const activeStage = snapshots[snapshotIndex]?.stageLabel ?? null;
|
|
898
|
+
const executedStages = /* @__PURE__ */ new Set([...doneStages]);
|
|
899
|
+
if (activeStage) executedStages.add(activeStage);
|
|
900
|
+
return { doneStages, activeStage, executedStages, executionOrder };
|
|
901
|
+
}, [snapshots, snapshotIndex]);
|
|
902
|
+
const layout = useMemo2(() => {
|
|
903
|
+
if (!spec) return null;
|
|
904
|
+
return specToLayout(spec);
|
|
905
|
+
}, [spec]);
|
|
906
|
+
const { nodes, edges } = useMemo2(() => {
|
|
907
|
+
if (!layout) return { nodes: [], edges: [] };
|
|
908
|
+
return applyOverlay(layout, overlay);
|
|
909
|
+
}, [layout, overlay]);
|
|
910
|
+
const handleNodeClick = useCallback2(
|
|
911
|
+
(_, node) => {
|
|
912
|
+
if (!onNodeClick) return;
|
|
913
|
+
onNodeClick(node.id);
|
|
637
914
|
},
|
|
638
|
-
[]
|
|
915
|
+
[onNodeClick]
|
|
916
|
+
);
|
|
917
|
+
return /* @__PURE__ */ jsx4(
|
|
918
|
+
"div",
|
|
919
|
+
{
|
|
920
|
+
className,
|
|
921
|
+
style: { width: "100%", height: "100%", ...style },
|
|
922
|
+
"data-fp": "traced-flowchart",
|
|
923
|
+
children: /* @__PURE__ */ jsxs2(
|
|
924
|
+
ReactFlow2,
|
|
925
|
+
{
|
|
926
|
+
nodes,
|
|
927
|
+
edges,
|
|
928
|
+
onNodeClick: handleNodeClick,
|
|
929
|
+
nodeTypes: nodeTypes2,
|
|
930
|
+
fitView: true,
|
|
931
|
+
fitViewOptions: { padding: 0.3 },
|
|
932
|
+
proOptions: { hideAttribution: true },
|
|
933
|
+
panOnDrag: false,
|
|
934
|
+
zoomOnScroll: false,
|
|
935
|
+
zoomOnPinch: false,
|
|
936
|
+
zoomOnDoubleClick: false,
|
|
937
|
+
preventScrolling: false,
|
|
938
|
+
nodesDraggable: false,
|
|
939
|
+
nodesConnectable: false,
|
|
940
|
+
elementsSelectable: !!onNodeClick,
|
|
941
|
+
children: [
|
|
942
|
+
/* @__PURE__ */ jsx4(FitViewOnResize, {}),
|
|
943
|
+
!unstyled && /* @__PURE__ */ jsx4(Background2, { variant: BackgroundVariant2.Dots, gap: 16, size: 1 })
|
|
944
|
+
]
|
|
945
|
+
}
|
|
946
|
+
)
|
|
947
|
+
}
|
|
639
948
|
);
|
|
640
|
-
return {
|
|
641
|
-
breadcrumbs,
|
|
642
|
-
nodes,
|
|
643
|
-
edges,
|
|
644
|
-
handleNodeClick,
|
|
645
|
-
navigateTo,
|
|
646
|
-
isInSubflow: stack.length > 0,
|
|
647
|
-
currentSubflowNodeName: stack.length > 0 ? stack[stack.length - 1].label : null
|
|
648
|
-
};
|
|
649
949
|
}
|
|
650
950
|
|
|
651
951
|
// src/components/FlowchartView/SubflowBreadcrumb.tsx
|
|
652
952
|
import { memo as memo2 } from "react";
|
|
653
|
-
import { jsx as
|
|
953
|
+
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
654
954
|
var SubflowBreadcrumb = memo2(function SubflowBreadcrumb2({
|
|
655
955
|
breadcrumbs,
|
|
656
956
|
onNavigate
|
|
657
957
|
}) {
|
|
658
958
|
if (breadcrumbs.length <= 1) return null;
|
|
659
|
-
return /* @__PURE__ */
|
|
959
|
+
return /* @__PURE__ */ jsx5(
|
|
660
960
|
"div",
|
|
661
961
|
{
|
|
662
962
|
style: {
|
|
@@ -673,10 +973,10 @@ var SubflowBreadcrumb = memo2(function SubflowBreadcrumb2({
|
|
|
673
973
|
},
|
|
674
974
|
children: breadcrumbs.map((crumb, i) => {
|
|
675
975
|
const isLast = i === breadcrumbs.length - 1;
|
|
676
|
-
return /* @__PURE__ */
|
|
677
|
-
i > 0 && /* @__PURE__ */
|
|
678
|
-
isLast ? /* @__PURE__ */
|
|
679
|
-
/* @__PURE__ */
|
|
976
|
+
return /* @__PURE__ */ jsxs3("span", { style: { display: "flex", alignItems: "center", gap: 4 }, children: [
|
|
977
|
+
i > 0 && /* @__PURE__ */ jsx5("span", { style: { color: theme.textMuted, fontSize: 10 }, children: "\u203A" }),
|
|
978
|
+
isLast ? /* @__PURE__ */ jsxs3("span", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
|
|
979
|
+
/* @__PURE__ */ jsx5(
|
|
680
980
|
"span",
|
|
681
981
|
{
|
|
682
982
|
style: {
|
|
@@ -686,7 +986,7 @@ var SubflowBreadcrumb = memo2(function SubflowBreadcrumb2({
|
|
|
686
986
|
children: crumb.label
|
|
687
987
|
}
|
|
688
988
|
),
|
|
689
|
-
crumb.description && /* @__PURE__ */
|
|
989
|
+
crumb.description && /* @__PURE__ */ jsxs3(
|
|
690
990
|
"span",
|
|
691
991
|
{
|
|
692
992
|
style: {
|
|
@@ -700,7 +1000,7 @@ var SubflowBreadcrumb = memo2(function SubflowBreadcrumb2({
|
|
|
700
1000
|
]
|
|
701
1001
|
}
|
|
702
1002
|
)
|
|
703
|
-
] }) : /* @__PURE__ */
|
|
1003
|
+
] }) : /* @__PURE__ */ jsx5(
|
|
704
1004
|
"button",
|
|
705
1005
|
{
|
|
706
1006
|
onClick: () => onNavigate(i),
|
|
@@ -731,13 +1031,83 @@ var SubflowBreadcrumb = memo2(function SubflowBreadcrumb2({
|
|
|
731
1031
|
);
|
|
732
1032
|
});
|
|
733
1033
|
|
|
1034
|
+
// src/components/FlowchartView/useSubflowNavigation.ts
|
|
1035
|
+
import { useState as useState2, useCallback as useCallback3, useMemo as useMemo3 } from "react";
|
|
1036
|
+
function useSubflowNavigation(rootSpec, overlay, colors) {
|
|
1037
|
+
const [stack, setStack] = useState2([]);
|
|
1038
|
+
const currentSpec = stack.length > 0 ? stack[stack.length - 1].spec : rootSpec;
|
|
1039
|
+
const { nodes, edges } = useMemo3(() => {
|
|
1040
|
+
if (!currentSpec) return { nodes: [], edges: [] };
|
|
1041
|
+
return specToReactFlow(currentSpec, overlay, colors);
|
|
1042
|
+
}, [currentSpec, overlay, colors]);
|
|
1043
|
+
const subflowMap = useMemo3(() => {
|
|
1044
|
+
const map = /* @__PURE__ */ new Map();
|
|
1045
|
+
if (!currentSpec) return map;
|
|
1046
|
+
function collectSubflows(node) {
|
|
1047
|
+
if (node.isSubflowRoot && node.subflowStructure) {
|
|
1048
|
+
const id = node.name || node.id || "";
|
|
1049
|
+
map.set(id, node);
|
|
1050
|
+
}
|
|
1051
|
+
if (node.children) node.children.forEach(collectSubflows);
|
|
1052
|
+
if (node.next) collectSubflows(node.next);
|
|
1053
|
+
}
|
|
1054
|
+
collectSubflows(currentSpec);
|
|
1055
|
+
return map;
|
|
1056
|
+
}, [currentSpec]);
|
|
1057
|
+
const breadcrumbs = useMemo3(() => {
|
|
1058
|
+
const root = {
|
|
1059
|
+
label: rootSpec?.name || "Flowchart",
|
|
1060
|
+
spec: rootSpec,
|
|
1061
|
+
description: rootSpec?.description
|
|
1062
|
+
};
|
|
1063
|
+
return [root, ...stack];
|
|
1064
|
+
}, [rootSpec, stack]);
|
|
1065
|
+
const handleNodeClick = useCallback3(
|
|
1066
|
+
(nodeId) => {
|
|
1067
|
+
const subflowNode = subflowMap.get(nodeId);
|
|
1068
|
+
if (!subflowNode?.subflowStructure) return false;
|
|
1069
|
+
setStack((prev) => [
|
|
1070
|
+
...prev,
|
|
1071
|
+
{
|
|
1072
|
+
label: subflowNode.subflowName || subflowNode.name,
|
|
1073
|
+
spec: subflowNode.subflowStructure,
|
|
1074
|
+
description: subflowNode.description
|
|
1075
|
+
}
|
|
1076
|
+
]);
|
|
1077
|
+
return true;
|
|
1078
|
+
},
|
|
1079
|
+
[subflowMap]
|
|
1080
|
+
);
|
|
1081
|
+
const navigateTo = useCallback3(
|
|
1082
|
+
(level) => {
|
|
1083
|
+
if (level === 0) {
|
|
1084
|
+
setStack([]);
|
|
1085
|
+
} else {
|
|
1086
|
+
setStack((prev) => prev.slice(0, level));
|
|
1087
|
+
}
|
|
1088
|
+
},
|
|
1089
|
+
[]
|
|
1090
|
+
);
|
|
1091
|
+
return {
|
|
1092
|
+
breadcrumbs,
|
|
1093
|
+
nodes,
|
|
1094
|
+
edges,
|
|
1095
|
+
handleNodeClick,
|
|
1096
|
+
navigateTo,
|
|
1097
|
+
isInSubflow: stack.length > 0,
|
|
1098
|
+
currentSubflowNodeName: stack.length > 0 ? stack[stack.length - 1].label : null
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
|
|
734
1102
|
// src/components/FlowchartView/SubflowTree.tsx
|
|
735
|
-
import { memo as memo3, useState as
|
|
736
|
-
import { Fragment as Fragment2, jsx as
|
|
1103
|
+
import { memo as memo3, useState as useState3, useCallback as useCallback4, useMemo as useMemo4 } from "react";
|
|
1104
|
+
import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
737
1105
|
function specToTree(node) {
|
|
1106
|
+
if (!node) return [];
|
|
738
1107
|
const entries = [];
|
|
739
1108
|
const seen = /* @__PURE__ */ new Set();
|
|
740
|
-
function
|
|
1109
|
+
function walk(n) {
|
|
1110
|
+
if (!n) return;
|
|
741
1111
|
const id = n.name || n.id || "";
|
|
742
1112
|
if (seen.has(id)) return;
|
|
743
1113
|
seen.add(id);
|
|
@@ -753,14 +1123,14 @@ function specToTree(node) {
|
|
|
753
1123
|
entries.push(entry);
|
|
754
1124
|
if (n.children) {
|
|
755
1125
|
for (const child of n.children) {
|
|
756
|
-
|
|
1126
|
+
if (child) walk(child);
|
|
757
1127
|
}
|
|
758
1128
|
}
|
|
759
1129
|
if (n.next) {
|
|
760
|
-
|
|
1130
|
+
walk(n.next);
|
|
761
1131
|
}
|
|
762
1132
|
}
|
|
763
|
-
|
|
1133
|
+
walk(node);
|
|
764
1134
|
return entries;
|
|
765
1135
|
}
|
|
766
1136
|
var TreeNode = memo3(function TreeNode2({
|
|
@@ -770,18 +1140,18 @@ var TreeNode = memo3(function TreeNode2({
|
|
|
770
1140
|
doneStages,
|
|
771
1141
|
onNodeSelect
|
|
772
1142
|
}) {
|
|
773
|
-
const [expanded, setExpanded] =
|
|
1143
|
+
const [expanded, setExpanded] = useState3(true);
|
|
774
1144
|
const hasChildren = entry.children && entry.children.length > 0;
|
|
775
1145
|
const isActive = activeStage === entry.name;
|
|
776
1146
|
const isDone = doneStages?.has(entry.name);
|
|
777
|
-
const handleClick =
|
|
1147
|
+
const handleClick = useCallback4(() => {
|
|
778
1148
|
if (hasChildren) {
|
|
779
1149
|
setExpanded((prev) => !prev);
|
|
780
1150
|
}
|
|
781
1151
|
onNodeSelect?.(entry.name, !!entry.isSubflow);
|
|
782
1152
|
}, [hasChildren, onNodeSelect, entry.name, entry.isSubflow]);
|
|
783
|
-
return /* @__PURE__ */
|
|
784
|
-
/* @__PURE__ */
|
|
1153
|
+
return /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
1154
|
+
/* @__PURE__ */ jsxs4(
|
|
785
1155
|
"button",
|
|
786
1156
|
{
|
|
787
1157
|
onClick: handleClick,
|
|
@@ -812,7 +1182,7 @@ var TreeNode = memo3(function TreeNode2({
|
|
|
812
1182
|
}
|
|
813
1183
|
},
|
|
814
1184
|
children: [
|
|
815
|
-
hasChildren ? /* @__PURE__ */
|
|
1185
|
+
hasChildren ? /* @__PURE__ */ jsx6(
|
|
816
1186
|
"span",
|
|
817
1187
|
{
|
|
818
1188
|
style: {
|
|
@@ -827,8 +1197,8 @@ var TreeNode = memo3(function TreeNode2({
|
|
|
827
1197
|
},
|
|
828
1198
|
children: "\u25B6"
|
|
829
1199
|
}
|
|
830
|
-
) : /* @__PURE__ */
|
|
831
|
-
/* @__PURE__ */
|
|
1200
|
+
) : /* @__PURE__ */ jsx6("span", { style: { width: 12, flexShrink: 0 } }),
|
|
1201
|
+
/* @__PURE__ */ jsx6(
|
|
832
1202
|
"span",
|
|
833
1203
|
{
|
|
834
1204
|
style: {
|
|
@@ -840,8 +1210,8 @@ var TreeNode = memo3(function TreeNode2({
|
|
|
840
1210
|
}
|
|
841
1211
|
}
|
|
842
1212
|
),
|
|
843
|
-
/* @__PURE__ */
|
|
844
|
-
/* @__PURE__ */
|
|
1213
|
+
/* @__PURE__ */ jsxs4("span", { style: { display: "flex", flexDirection: "column", minWidth: 0 }, children: [
|
|
1214
|
+
/* @__PURE__ */ jsxs4(
|
|
845
1215
|
"span",
|
|
846
1216
|
{
|
|
847
1217
|
style: {
|
|
@@ -853,11 +1223,11 @@ var TreeNode = memo3(function TreeNode2({
|
|
|
853
1223
|
},
|
|
854
1224
|
children: [
|
|
855
1225
|
entry.name,
|
|
856
|
-
entry.isSubflow && /* @__PURE__ */
|
|
1226
|
+
entry.isSubflow && /* @__PURE__ */ jsx6("span", { style: { opacity: 0.5, marginLeft: 4, fontSize: 10 }, children: "\u229E" })
|
|
857
1227
|
]
|
|
858
1228
|
}
|
|
859
1229
|
),
|
|
860
|
-
entry.description && /* @__PURE__ */
|
|
1230
|
+
entry.description && /* @__PURE__ */ jsx6(
|
|
861
1231
|
"span",
|
|
862
1232
|
{
|
|
863
1233
|
style: {
|
|
@@ -874,7 +1244,7 @@ var TreeNode = memo3(function TreeNode2({
|
|
|
874
1244
|
]
|
|
875
1245
|
}
|
|
876
1246
|
),
|
|
877
|
-
hasChildren && expanded && /* @__PURE__ */
|
|
1247
|
+
hasChildren && expanded && /* @__PURE__ */ jsx6("div", { children: entry.children.map((child, i) => /* @__PURE__ */ jsx6(
|
|
878
1248
|
TreeNode2,
|
|
879
1249
|
{
|
|
880
1250
|
entry: child,
|
|
@@ -883,12 +1253,12 @@ var TreeNode = memo3(function TreeNode2({
|
|
|
883
1253
|
doneStages,
|
|
884
1254
|
onNodeSelect
|
|
885
1255
|
},
|
|
886
|
-
`${child.name}-${i}`
|
|
1256
|
+
child.subflowId ?? `${child.name}-${i}`
|
|
887
1257
|
)) })
|
|
888
1258
|
] });
|
|
889
1259
|
});
|
|
890
1260
|
var SectionLabel = memo3(function SectionLabel2({ children }) {
|
|
891
|
-
return /* @__PURE__ */
|
|
1261
|
+
return /* @__PURE__ */ jsx6(
|
|
892
1262
|
"div",
|
|
893
1263
|
{
|
|
894
1264
|
style: {
|
|
@@ -912,21 +1282,10 @@ var SubflowTree = memo3(function SubflowTree2({
|
|
|
912
1282
|
className,
|
|
913
1283
|
style
|
|
914
1284
|
}) {
|
|
915
|
-
const tree =
|
|
916
|
-
const
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
TreeNode,
|
|
920
|
-
{
|
|
921
|
-
entry,
|
|
922
|
-
depth: 0,
|
|
923
|
-
activeStage,
|
|
924
|
-
doneStages,
|
|
925
|
-
onNodeSelect
|
|
926
|
-
},
|
|
927
|
-
`${entry.name}-${i}`
|
|
928
|
-
));
|
|
929
|
-
return /* @__PURE__ */ jsxs3(
|
|
1285
|
+
const tree = useMemo4(() => specToTree(spec), [spec]);
|
|
1286
|
+
const subflowStages = useMemo4(() => tree.filter((e) => e.isSubflow), [tree]);
|
|
1287
|
+
if (subflowStages.length === 0) return null;
|
|
1288
|
+
return /* @__PURE__ */ jsxs4(
|
|
930
1289
|
"div",
|
|
931
1290
|
{
|
|
932
1291
|
className,
|
|
@@ -944,185 +1303,28 @@ var SubflowTree = memo3(function SubflowTree2({
|
|
|
944
1303
|
...style
|
|
945
1304
|
},
|
|
946
1305
|
children: [
|
|
947
|
-
!unstyled && /* @__PURE__ */
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
!unstyled && /* @__PURE__ */ jsx5("div", { style: { height: 1, background: theme.border, margin: "8px 12px" } }),
|
|
951
|
-
!unstyled && /* @__PURE__ */ jsx5(SectionLabel, { children: "Subflows" }),
|
|
952
|
-
renderEntries(subflowStages)
|
|
953
|
-
] })
|
|
954
|
-
]
|
|
955
|
-
}
|
|
956
|
-
);
|
|
957
|
-
});
|
|
958
|
-
|
|
959
|
-
// src/components/FlowchartView/TracedFlowchartView.tsx
|
|
960
|
-
import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
961
|
-
var nodeTypes2 = { stage: StageNode };
|
|
962
|
-
function TracedFlowchartView({
|
|
963
|
-
spec,
|
|
964
|
-
snapshots,
|
|
965
|
-
snapshotIndex = 0,
|
|
966
|
-
onNodeClick,
|
|
967
|
-
onSubflowChange,
|
|
968
|
-
showTree = false,
|
|
969
|
-
treeWidth = 200,
|
|
970
|
-
unstyled = false,
|
|
971
|
-
className,
|
|
972
|
-
style
|
|
973
|
-
}) {
|
|
974
|
-
const [treeVisible, setTreeVisible] = useState3(showTree);
|
|
975
|
-
const subflowNav = useSubflowNavigation(spec);
|
|
976
|
-
const currentSpec = subflowNav.breadcrumbs.length > 0 ? subflowNav.breadcrumbs[subflowNav.breadcrumbs.length - 1].spec : null;
|
|
977
|
-
const overlay = useMemo4(() => {
|
|
978
|
-
if (!snapshots || snapshots.length === 0) return void 0;
|
|
979
|
-
const executionOrder = snapshots.slice(0, snapshotIndex + 1).map((s) => s.stageLabel);
|
|
980
|
-
const doneStages = new Set(
|
|
981
|
-
snapshots.slice(0, snapshotIndex).map((s) => s.stageLabel)
|
|
982
|
-
);
|
|
983
|
-
const activeStage = snapshots[snapshotIndex]?.stageLabel ?? null;
|
|
984
|
-
const executedStages = /* @__PURE__ */ new Set([...doneStages]);
|
|
985
|
-
if (activeStage) executedStages.add(activeStage);
|
|
986
|
-
return { doneStages, activeStage, executedStages, executionOrder };
|
|
987
|
-
}, [snapshots, snapshotIndex]);
|
|
988
|
-
const { nodes, edges } = useMemo4(() => {
|
|
989
|
-
if (!currentSpec) return { nodes: [], edges: [] };
|
|
990
|
-
return specToReactFlow(currentSpec, overlay);
|
|
991
|
-
}, [currentSpec, overlay]);
|
|
992
|
-
const handleNodeClick = useCallback4(
|
|
993
|
-
(_, node) => {
|
|
994
|
-
if (subflowNav.handleNodeClick(node.id)) {
|
|
995
|
-
onSubflowChange?.(true, node.id);
|
|
996
|
-
return;
|
|
997
|
-
}
|
|
998
|
-
if (onNodeClick && snapshots) {
|
|
999
|
-
const idx = snapshots.findIndex((s) => s.stageLabel === node.id);
|
|
1000
|
-
if (idx >= 0) onNodeClick(idx);
|
|
1001
|
-
} else if (onNodeClick) {
|
|
1002
|
-
onNodeClick(node.id);
|
|
1003
|
-
}
|
|
1004
|
-
},
|
|
1005
|
-
[subflowNav, onNodeClick, onSubflowChange, snapshots]
|
|
1006
|
-
);
|
|
1007
|
-
const handleBreadcrumbNavigate = useCallback4(
|
|
1008
|
-
(level) => {
|
|
1009
|
-
subflowNav.navigateTo(level);
|
|
1010
|
-
onSubflowChange?.(level > 0, null);
|
|
1011
|
-
},
|
|
1012
|
-
[subflowNav, onSubflowChange]
|
|
1013
|
-
);
|
|
1014
|
-
const handleTreeNodeSelect = useCallback4(
|
|
1015
|
-
(name, isSubflow) => {
|
|
1016
|
-
if (isSubflow) {
|
|
1017
|
-
if (subflowNav.handleNodeClick(name)) {
|
|
1018
|
-
onSubflowChange?.(true, name);
|
|
1019
|
-
}
|
|
1020
|
-
} else if (onNodeClick && snapshots) {
|
|
1021
|
-
const idx = snapshots.findIndex((s) => s.stageLabel === name);
|
|
1022
|
-
if (idx >= 0) onNodeClick(idx);
|
|
1023
|
-
}
|
|
1024
|
-
},
|
|
1025
|
-
[subflowNav, onNodeClick, onSubflowChange, snapshots]
|
|
1026
|
-
);
|
|
1027
|
-
return /* @__PURE__ */ jsxs4(
|
|
1028
|
-
"div",
|
|
1029
|
-
{
|
|
1030
|
-
className,
|
|
1031
|
-
style: { width: "100%", height: "100%", display: "flex", flexDirection: "row", ...style },
|
|
1032
|
-
"data-fp": "traced-flowchart",
|
|
1033
|
-
children: [
|
|
1034
|
-
showTree && treeVisible && /* @__PURE__ */ jsx6(
|
|
1035
|
-
SubflowTree,
|
|
1306
|
+
!unstyled && /* @__PURE__ */ jsx6(SectionLabel, { children: "Subflows" }),
|
|
1307
|
+
subflowStages.map((entry, i) => /* @__PURE__ */ jsx6(
|
|
1308
|
+
TreeNode,
|
|
1036
1309
|
{
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
}
|
|
1044
|
-
)
|
|
1045
|
-
/* @__PURE__ */ jsxs4("div", { style: { flex: 1, display: "flex", flexDirection: "column", minWidth: 0, height: "100%" }, children: [
|
|
1046
|
-
(subflowNav.isInSubflow || showTree && !treeVisible) && /* @__PURE__ */ jsxs4("div", { style: { display: "flex", alignItems: "stretch", flexShrink: 0 }, children: [
|
|
1047
|
-
showTree && !treeVisible && /* @__PURE__ */ jsx6(
|
|
1048
|
-
"button",
|
|
1049
|
-
{
|
|
1050
|
-
onClick: () => setTreeVisible(true),
|
|
1051
|
-
"data-fp": "tree-toggle",
|
|
1052
|
-
style: unstyled ? {} : {
|
|
1053
|
-
background: "transparent",
|
|
1054
|
-
border: "none",
|
|
1055
|
-
cursor: "pointer",
|
|
1056
|
-
padding: "6px 8px",
|
|
1057
|
-
fontSize: 10,
|
|
1058
|
-
flexShrink: 0
|
|
1059
|
-
},
|
|
1060
|
-
children: "\u25B6"
|
|
1061
|
-
}
|
|
1062
|
-
),
|
|
1063
|
-
/* @__PURE__ */ jsx6("div", { style: { flex: 1 }, children: /* @__PURE__ */ jsx6(
|
|
1064
|
-
SubflowBreadcrumb,
|
|
1065
|
-
{
|
|
1066
|
-
breadcrumbs: subflowNav.breadcrumbs,
|
|
1067
|
-
onNavigate: handleBreadcrumbNavigate
|
|
1068
|
-
}
|
|
1069
|
-
) })
|
|
1070
|
-
] }),
|
|
1071
|
-
showTree && treeVisible && /* @__PURE__ */ jsxs4("div", { style: { display: "flex", alignItems: "stretch", flexShrink: 0 }, children: [
|
|
1072
|
-
/* @__PURE__ */ jsx6(
|
|
1073
|
-
"button",
|
|
1074
|
-
{
|
|
1075
|
-
onClick: () => setTreeVisible(false),
|
|
1076
|
-
"data-fp": "tree-toggle",
|
|
1077
|
-
style: unstyled ? {} : {
|
|
1078
|
-
background: "transparent",
|
|
1079
|
-
border: "none",
|
|
1080
|
-
cursor: "pointer",
|
|
1081
|
-
padding: "6px 8px",
|
|
1082
|
-
fontSize: 10,
|
|
1083
|
-
flexShrink: 0
|
|
1084
|
-
},
|
|
1085
|
-
children: "\u25C0"
|
|
1086
|
-
}
|
|
1087
|
-
),
|
|
1088
|
-
/* @__PURE__ */ jsx6("div", { style: { flex: 1 }, children: subflowNav.isInSubflow && /* @__PURE__ */ jsx6(
|
|
1089
|
-
SubflowBreadcrumb,
|
|
1090
|
-
{
|
|
1091
|
-
breadcrumbs: subflowNav.breadcrumbs,
|
|
1092
|
-
onNavigate: handleBreadcrumbNavigate
|
|
1093
|
-
}
|
|
1094
|
-
) })
|
|
1095
|
-
] }),
|
|
1096
|
-
/* @__PURE__ */ jsx6("div", { style: { flex: 1, minHeight: 0 }, children: /* @__PURE__ */ jsx6(
|
|
1097
|
-
ReactFlow2,
|
|
1098
|
-
{
|
|
1099
|
-
nodes,
|
|
1100
|
-
edges,
|
|
1101
|
-
onNodeClick: handleNodeClick,
|
|
1102
|
-
nodeTypes: nodeTypes2,
|
|
1103
|
-
fitView: true,
|
|
1104
|
-
panOnDrag: false,
|
|
1105
|
-
zoomOnScroll: false,
|
|
1106
|
-
zoomOnPinch: false,
|
|
1107
|
-
zoomOnDoubleClick: false,
|
|
1108
|
-
preventScrolling: false,
|
|
1109
|
-
nodesDraggable: false,
|
|
1110
|
-
nodesConnectable: false,
|
|
1111
|
-
elementsSelectable: !!onNodeClick,
|
|
1112
|
-
children: !unstyled && /* @__PURE__ */ jsx6(Background2, { variant: BackgroundVariant2.Dots, gap: 16, size: 1 })
|
|
1113
|
-
}
|
|
1114
|
-
) })
|
|
1115
|
-
] })
|
|
1310
|
+
entry,
|
|
1311
|
+
depth: 0,
|
|
1312
|
+
activeStage,
|
|
1313
|
+
doneStages,
|
|
1314
|
+
onNodeSelect
|
|
1315
|
+
},
|
|
1316
|
+
entry.subflowId ?? `${entry.name}-${i}`
|
|
1317
|
+
))
|
|
1116
1318
|
]
|
|
1117
1319
|
}
|
|
1118
1320
|
);
|
|
1119
|
-
}
|
|
1321
|
+
});
|
|
1120
1322
|
|
|
1121
1323
|
// src/components/TimeTravelDebugger/TimeTravelDebugger.tsx
|
|
1122
1324
|
import { useState as useState5 } from "react";
|
|
1123
1325
|
|
|
1124
1326
|
// src/components/MemoryInspector/MemoryInspector.tsx
|
|
1125
|
-
import { useMemo as useMemo5 } from "react";
|
|
1327
|
+
import { useMemo as useMemo5, useRef as useRef2 } from "react";
|
|
1126
1328
|
import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1127
1329
|
function MemoryInspector({
|
|
1128
1330
|
data,
|
|
@@ -1135,6 +1337,7 @@ function MemoryInspector({
|
|
|
1135
1337
|
className,
|
|
1136
1338
|
style
|
|
1137
1339
|
}) {
|
|
1340
|
+
const cacheRef = useRef2(null);
|
|
1138
1341
|
const { memory, newKeys } = useMemo5(() => {
|
|
1139
1342
|
if (data) {
|
|
1140
1343
|
return { memory: data, newKeys: /* @__PURE__ */ new Set() };
|
|
@@ -1142,21 +1345,37 @@ function MemoryInspector({
|
|
|
1142
1345
|
if (!snapshots || snapshots.length === 0) {
|
|
1143
1346
|
return { memory: {}, newKeys: /* @__PURE__ */ new Set() };
|
|
1144
1347
|
}
|
|
1145
|
-
const
|
|
1146
|
-
|
|
1147
|
-
|
|
1348
|
+
const safeIdx = Math.min(selectedIndex, snapshots.length - 1);
|
|
1349
|
+
let merged;
|
|
1350
|
+
const cache = cacheRef.current;
|
|
1351
|
+
if (cache && cache.snapshots === snapshots && cache.index <= safeIdx) {
|
|
1352
|
+
merged = { ...cache.accumulated };
|
|
1353
|
+
for (let i = cache.index + 1; i <= safeIdx; i++) {
|
|
1354
|
+
Object.assign(merged, snapshots[i]?.memory);
|
|
1355
|
+
}
|
|
1356
|
+
} else {
|
|
1357
|
+
merged = {};
|
|
1358
|
+
for (let i = 0; i <= safeIdx; i++) {
|
|
1359
|
+
Object.assign(merged, snapshots[i]?.memory);
|
|
1360
|
+
}
|
|
1148
1361
|
}
|
|
1362
|
+
cacheRef.current = { snapshots, index: safeIdx, accumulated: merged };
|
|
1149
1363
|
const nk = /* @__PURE__ */ new Set();
|
|
1150
|
-
if (highlightNew &&
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1364
|
+
if (highlightNew && safeIdx > 0) {
|
|
1365
|
+
let prev;
|
|
1366
|
+
if (cache && cache.snapshots === snapshots && cache.index === safeIdx - 1) {
|
|
1367
|
+
prev = cache.accumulated;
|
|
1368
|
+
} else {
|
|
1369
|
+
prev = {};
|
|
1370
|
+
for (let i = 0; i < safeIdx; i++) {
|
|
1371
|
+
Object.assign(prev, snapshots[i]?.memory);
|
|
1372
|
+
}
|
|
1154
1373
|
}
|
|
1155
|
-
const current = snapshots[
|
|
1374
|
+
const current = snapshots[safeIdx]?.memory ?? {};
|
|
1156
1375
|
for (const k of Object.keys(current)) {
|
|
1157
1376
|
if (!(k in prev)) nk.add(k);
|
|
1158
1377
|
}
|
|
1159
|
-
} else if (highlightNew &&
|
|
1378
|
+
} else if (highlightNew && safeIdx === 0 && snapshots[0]) {
|
|
1160
1379
|
for (const k of Object.keys(snapshots[0].memory)) nk.add(k);
|
|
1161
1380
|
}
|
|
1162
1381
|
return { memory: merged, newKeys: nk };
|
|
@@ -1165,9 +1384,9 @@ function MemoryInspector({
|
|
|
1165
1384
|
const fs = fontSize[size];
|
|
1166
1385
|
const pad = padding[size];
|
|
1167
1386
|
if (unstyled) {
|
|
1168
|
-
return /* @__PURE__ */ jsxs5("div", { className, style, "data-fp": "memory-inspector", children: [
|
|
1387
|
+
return /* @__PURE__ */ jsxs5("div", { className, style, "data-fp": "memory-inspector", role: "region", "aria-label": "Memory state", children: [
|
|
1169
1388
|
/* @__PURE__ */ jsx7("div", { "data-fp": "memory-label", children: "Memory State" }),
|
|
1170
|
-
/* @__PURE__ */ jsx7("pre", { "data-fp": "memory-json", children: JSON.stringify(memory, null, 2) })
|
|
1389
|
+
/* @__PURE__ */ jsx7("pre", { "data-fp": "memory-json", children: /* @__PURE__ */ jsx7("code", { children: JSON.stringify(memory, null, 2) }) })
|
|
1171
1390
|
] });
|
|
1172
1391
|
}
|
|
1173
1392
|
return /* @__PURE__ */ jsxs5(
|
|
@@ -1180,6 +1399,8 @@ function MemoryInspector({
|
|
|
1180
1399
|
...style
|
|
1181
1400
|
},
|
|
1182
1401
|
"data-fp": "memory-inspector",
|
|
1402
|
+
role: "region",
|
|
1403
|
+
"aria-label": "Memory state",
|
|
1183
1404
|
children: [
|
|
1184
1405
|
/* @__PURE__ */ jsx7(
|
|
1185
1406
|
"span",
|
|
@@ -1412,7 +1633,7 @@ function NarrativeLog({
|
|
|
1412
1633
|
}
|
|
1413
1634
|
|
|
1414
1635
|
// src/components/GanttTimeline/GanttTimeline.tsx
|
|
1415
|
-
import { useState as useState4, useMemo as useMemo7, useRef, useEffect } from "react";
|
|
1636
|
+
import { useState as useState4, useMemo as useMemo7, useRef as useRef3, useEffect as useEffect4 } from "react";
|
|
1416
1637
|
import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1417
1638
|
function GanttTimeline({
|
|
1418
1639
|
snapshots,
|
|
@@ -1425,8 +1646,8 @@ function GanttTimeline({
|
|
|
1425
1646
|
maxVisibleRows = 5
|
|
1426
1647
|
}) {
|
|
1427
1648
|
const [expanded, setExpanded] = useState4(false);
|
|
1428
|
-
const activeRowRef =
|
|
1429
|
-
const scrollContainerRef =
|
|
1649
|
+
const activeRowRef = useRef3(null);
|
|
1650
|
+
const scrollContainerRef = useRef3(null);
|
|
1430
1651
|
const totalWallTime = useMemo7(
|
|
1431
1652
|
() => Math.max(...snapshots.map((s) => s.startMs + s.durationMs), 1),
|
|
1432
1653
|
[snapshots]
|
|
@@ -1438,7 +1659,7 @@ function GanttTimeline({
|
|
|
1438
1659
|
const rowHeight = size === "compact" ? 18 : 22;
|
|
1439
1660
|
const collapsible = maxVisibleRows > 0 && snapshots.length > maxVisibleRows;
|
|
1440
1661
|
const showAll = expanded || !collapsible;
|
|
1441
|
-
|
|
1662
|
+
useEffect4(() => {
|
|
1442
1663
|
if (!showAll && activeRowRef.current && scrollContainerRef.current) {
|
|
1443
1664
|
activeRowRef.current.scrollIntoView({
|
|
1444
1665
|
block: "nearest",
|
|
@@ -1447,12 +1668,15 @@ function GanttTimeline({
|
|
|
1447
1668
|
}
|
|
1448
1669
|
}, [selectedIndex, showAll]);
|
|
1449
1670
|
if (unstyled) {
|
|
1450
|
-
return /* @__PURE__ */ jsx9("div", { className, style, "data-fp": "gantt-timeline", children: snapshots.map((snap, idx) => /* @__PURE__ */ jsxs7(
|
|
1671
|
+
return /* @__PURE__ */ jsx9("div", { className, style, "data-fp": "gantt-timeline", role: "listbox", "aria-label": "Execution timeline", children: snapshots.map((snap, idx) => /* @__PURE__ */ jsxs7(
|
|
1451
1672
|
"div",
|
|
1452
1673
|
{
|
|
1453
1674
|
"data-fp": "gantt-bar",
|
|
1454
1675
|
"data-selected": idx === selectedIndex,
|
|
1455
1676
|
"data-visible": idx <= selectedIndex,
|
|
1677
|
+
role: "option",
|
|
1678
|
+
"aria-selected": idx === selectedIndex,
|
|
1679
|
+
"aria-label": `${snap.stageLabel}, ${snap.durationMs}ms`,
|
|
1456
1680
|
onClick: () => onSelect?.(idx),
|
|
1457
1681
|
children: [
|
|
1458
1682
|
/* @__PURE__ */ jsx9("span", { "data-fp": "gantt-label", children: snap.stageLabel }),
|
|
@@ -1462,7 +1686,7 @@ function GanttTimeline({
|
|
|
1462
1686
|
] })
|
|
1463
1687
|
]
|
|
1464
1688
|
},
|
|
1465
|
-
snap.stageName
|
|
1689
|
+
`${snap.stageName}-${idx}`
|
|
1466
1690
|
)) });
|
|
1467
1691
|
}
|
|
1468
1692
|
return /* @__PURE__ */ jsxs7(
|
|
@@ -1518,6 +1742,8 @@ function GanttTimeline({
|
|
|
1518
1742
|
"div",
|
|
1519
1743
|
{
|
|
1520
1744
|
ref: scrollContainerRef,
|
|
1745
|
+
role: "listbox",
|
|
1746
|
+
"aria-label": "Execution timeline",
|
|
1521
1747
|
style: {
|
|
1522
1748
|
marginTop: 8,
|
|
1523
1749
|
display: "flex",
|
|
@@ -1538,6 +1764,9 @@ function GanttTimeline({
|
|
|
1538
1764
|
"div",
|
|
1539
1765
|
{
|
|
1540
1766
|
ref: isSelected ? activeRowRef : void 0,
|
|
1767
|
+
role: "option",
|
|
1768
|
+
"aria-selected": isSelected,
|
|
1769
|
+
"aria-label": `${snap.stageLabel}, ${snap.durationMs}ms`,
|
|
1541
1770
|
onClick: () => onSelect?.(idx),
|
|
1542
1771
|
style: {
|
|
1543
1772
|
display: "flex",
|
|
@@ -1553,6 +1782,7 @@ function GanttTimeline({
|
|
|
1553
1782
|
/* @__PURE__ */ jsx9(
|
|
1554
1783
|
"span",
|
|
1555
1784
|
{
|
|
1785
|
+
title: snap.stageLabel,
|
|
1556
1786
|
style: {
|
|
1557
1787
|
width: labelWidth,
|
|
1558
1788
|
fontSize: fs.small,
|
|
@@ -1612,7 +1842,7 @@ function GanttTimeline({
|
|
|
1612
1842
|
)
|
|
1613
1843
|
]
|
|
1614
1844
|
},
|
|
1615
|
-
snap.stageName
|
|
1845
|
+
`${snap.stageName}-${idx}`
|
|
1616
1846
|
);
|
|
1617
1847
|
})
|
|
1618
1848
|
}
|
|
@@ -1966,6 +2196,8 @@ export {
|
|
|
1966
2196
|
SubflowTree,
|
|
1967
2197
|
TimeTravelDebugger,
|
|
1968
2198
|
TracedFlowchartView,
|
|
2199
|
+
applyOverlay,
|
|
2200
|
+
specToLayout,
|
|
1969
2201
|
specToReactFlow,
|
|
1970
2202
|
useSubflowNavigation
|
|
1971
2203
|
};
|