@railtownai/railtracks-visualizer 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +40 -11
  2. package/dist/cjs/AgenticFlowVisualizer.js +750 -0
  3. package/dist/cjs/App.js +94 -0
  4. package/dist/cjs/Visualizer.js +16 -0
  5. package/dist/cjs/components/Edge.js +75 -0
  6. package/dist/cjs/components/FileSelector.js +28 -0
  7. package/dist/cjs/components/Node.js +104 -0
  8. package/dist/cjs/components/Timeline.js +122 -0
  9. package/dist/cjs/components/VerticalTimeline.js +160 -0
  10. package/dist/cjs/hooks/index.js +7 -0
  11. package/dist/cjs/hooks/useApi.js +108 -0
  12. package/dist/cjs/hooks/useFlowData.js +53 -0
  13. package/dist/cjs/index.js +40 -0
  14. package/dist/cjs/test/Visualizer.test.js +257 -0
  15. package/dist/cjs/test/setup.js +24 -0
  16. package/dist/esm/Visualizer.js +11 -0
  17. package/dist/{index.js → esm/index.js} +1 -2
  18. package/dist/esm/test/Visualizer.test.js +243 -0
  19. package/dist/esm/test/setup.js +22 -0
  20. package/dist/{AgenticFlowVisualizer.d.ts → types/AgenticFlowVisualizer.d.ts} +2 -2
  21. package/dist/types/AgenticFlowVisualizer.d.ts.map +1 -0
  22. package/dist/types/App.d.ts.map +1 -0
  23. package/dist/types/Visualizer.d.ts +9 -0
  24. package/dist/types/Visualizer.d.ts.map +1 -0
  25. package/dist/types/components/Edge.d.ts.map +1 -0
  26. package/dist/types/components/FileSelector.d.ts.map +1 -0
  27. package/dist/types/components/Node.d.ts.map +1 -0
  28. package/dist/types/components/Timeline.d.ts.map +1 -0
  29. package/dist/types/components/VerticalTimeline.d.ts.map +1 -0
  30. package/dist/types/hooks/index.d.ts.map +1 -0
  31. package/dist/types/hooks/useApi.d.ts.map +1 -0
  32. package/dist/types/hooks/useFlowData.d.ts.map +1 -0
  33. package/dist/{index.d.ts → types/index.d.ts} +2 -1
  34. package/dist/types/index.d.ts.map +1 -0
  35. package/dist/types/test/Visualizer.test.d.ts +2 -0
  36. package/dist/types/test/Visualizer.test.d.ts.map +1 -0
  37. package/dist/types/test/setup.d.ts +2 -0
  38. package/dist/types/test/setup.d.ts.map +1 -0
  39. package/package.json +20 -13
  40. package/dist/AgenticFlowVisualizer.d.ts.map +0 -1
  41. package/dist/App.d.ts.map +0 -1
  42. package/dist/components/Edge.d.ts.map +0 -1
  43. package/dist/components/FileSelector.d.ts.map +0 -1
  44. package/dist/components/Node.d.ts.map +0 -1
  45. package/dist/components/Timeline.d.ts.map +0 -1
  46. package/dist/components/VerticalTimeline.d.ts.map +0 -1
  47. package/dist/hooks/index.d.ts.map +0 -1
  48. package/dist/hooks/useApi.d.ts.map +0 -1
  49. package/dist/hooks/useFlowData.d.ts.map +0 -1
  50. package/dist/index.d.ts.map +0 -1
  51. /package/dist/{AgenticFlowVisualizer.js → esm/AgenticFlowVisualizer.js} +0 -0
  52. /package/dist/{App.js → esm/App.js} +0 -0
  53. /package/dist/{components → esm/components}/Edge.js +0 -0
  54. /package/dist/{components → esm/components}/FileSelector.js +0 -0
  55. /package/dist/{components → esm/components}/Node.js +0 -0
  56. /package/dist/{components → esm/components}/Timeline.js +0 -0
  57. /package/dist/{components → esm/components}/VerticalTimeline.js +0 -0
  58. /package/dist/{hooks → esm/hooks}/index.js +0 -0
  59. /package/dist/{hooks → esm/hooks}/useApi.js +0 -0
  60. /package/dist/{hooks → esm/hooks}/useFlowData.js +0 -0
  61. /package/dist/{App.d.ts → types/App.d.ts} +0 -0
  62. /package/dist/{components → types/components}/Edge.d.ts +0 -0
  63. /package/dist/{components → types/components}/FileSelector.d.ts +0 -0
  64. /package/dist/{components → types/components}/Node.d.ts +0 -0
  65. /package/dist/{components → types/components}/Timeline.d.ts +0 -0
  66. /package/dist/{components → types/components}/VerticalTimeline.d.ts +0 -0
  67. /package/dist/{hooks → types/hooks}/index.d.ts +0 -0
  68. /package/dist/{hooks → types/hooks}/useApi.d.ts +0 -0
  69. /package/dist/{hooks → types/hooks}/useFlowData.d.ts +0 -0
@@ -0,0 +1,750 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const jsx_runtime_1 = require("react/jsx-runtime");
37
+ const react_1 = require("react");
38
+ const reactflow_1 = require("reactflow");
39
+ const reactflow_2 = __importStar(require("reactflow"));
40
+ require("reactflow/dist/style.css");
41
+ const lucide_react_1 = require("lucide-react");
42
+ const Edge_1 = require("./components/Edge");
43
+ const Node_1 = require("./components/Node");
44
+ const Timeline_1 = require("./components/Timeline");
45
+ const VerticalTimeline_1 = require("./components/VerticalTimeline");
46
+ // ============================================================================
47
+ // UTILITY FUNCTIONS
48
+ // ============================================================================
49
+ /**
50
+ * Calculates a clean tree layout: parents centered above children, siblings spaced evenly, no overlap.
51
+ */
52
+ const calculateAutoLayout = (nodes, edges) => {
53
+ // Build maps for fast lookup
54
+ const nodeMap = new Map(nodes.map((n) => [n.identifier, n]));
55
+ const childrenMap = new Map();
56
+ const parentMap = new Map();
57
+ nodes.forEach((n) => childrenMap.set(n.identifier, []));
58
+ // Handle edges if they exist
59
+ if (edges.length > 0) {
60
+ edges.forEach((e) => {
61
+ var _a;
62
+ if (e.source && e.target) {
63
+ (_a = childrenMap.get(e.source)) === null || _a === void 0 ? void 0 : _a.push(e.target);
64
+ parentMap.set(e.target, e.source);
65
+ }
66
+ });
67
+ }
68
+ else {
69
+ // If no edges, try to infer relationships from parent field
70
+ nodes.forEach((node) => {
71
+ var _a;
72
+ if (node.parent && node.parent.identifier !== node.identifier) {
73
+ (_a = childrenMap.get(node.parent.identifier)) === null || _a === void 0 ? void 0 : _a.push(node.identifier);
74
+ parentMap.set(node.identifier, node.parent.identifier);
75
+ }
76
+ });
77
+ }
78
+ // Find root nodes (no parent)
79
+ const roots = nodes.filter((n) => !parentMap.has(n.identifier));
80
+ // Assign levels (depths) - now vertical levels from top to bottom
81
+ const levelMap = new Map();
82
+ const assignLevels = (nodeId, level) => {
83
+ levelMap.set(nodeId, level);
84
+ for (const childId of childrenMap.get(nodeId) || []) {
85
+ assignLevels(childId, level + 1);
86
+ }
87
+ };
88
+ roots.forEach((root) => assignLevels(root.identifier, 0));
89
+ // Group nodes by level
90
+ const levels = [];
91
+ nodes.forEach((n) => {
92
+ var _a;
93
+ const lvl = (_a = levelMap.get(n.identifier)) !== null && _a !== void 0 ? _a : 0;
94
+ if (!levels[lvl])
95
+ levels[lvl] = [];
96
+ levels[lvl].push(n.identifier);
97
+ });
98
+ // Calculate subtree widths for each node (for centering horizontally)
99
+ const subtreeWidth = new Map();
100
+ const nodeWidth = 200; // Approximate node width
101
+ const nodeSpacing = 100; // Horizontal spacing between nodes
102
+ const calcSubtreeWidth = (nodeId) => {
103
+ const children = childrenMap.get(nodeId) || [];
104
+ if (children.length === 0) {
105
+ subtreeWidth.set(nodeId, nodeWidth);
106
+ return nodeWidth;
107
+ }
108
+ let width = 0;
109
+ for (const childId of children) {
110
+ width += calcSubtreeWidth(childId);
111
+ }
112
+ width += (children.length - 1) * nodeSpacing;
113
+ subtreeWidth.set(nodeId, width);
114
+ return width;
115
+ };
116
+ roots.forEach((root) => calcSubtreeWidth(root.identifier));
117
+ // Assign positions recursively - now top to bottom
118
+ const positions = new Map();
119
+ const levelSpacing = 300; // Increased vertical spacing between levels
120
+ const verticalMargin = 100; // Add margin to the top
121
+ const assignPositions = (nodeId, xLeft, level) => {
122
+ const width = subtreeWidth.get(nodeId) || nodeWidth;
123
+ const y = level * levelSpacing + verticalMargin; // Add margin here
124
+ const children = childrenMap.get(nodeId) || [];
125
+ if (children.length === 0) {
126
+ // Leaf node: center in its width
127
+ positions.set(nodeId, { x: xLeft + width / 2 - nodeWidth / 2, y });
128
+ }
129
+ else {
130
+ // Internal node: center above its children
131
+ let childX = xLeft;
132
+ for (const childId of children) {
133
+ const childWidth = subtreeWidth.get(childId) || nodeWidth;
134
+ assignPositions(childId, childX, level + 1);
135
+ childX += childWidth + nodeSpacing;
136
+ }
137
+ // Center parent above children
138
+ const firstChild = children[0];
139
+ const lastChild = children[children.length - 1];
140
+ const firstPos = positions.get(firstChild);
141
+ const lastPos = positions.get(lastChild);
142
+ const parentX = (firstPos.x + lastPos.x) / 2;
143
+ positions.set(nodeId, { x: parentX, y });
144
+ }
145
+ };
146
+ // Lay out each tree
147
+ let xCursor = 0;
148
+ for (const root of roots) {
149
+ assignPositions(root.identifier, xCursor, 0);
150
+ xCursor += (subtreeWidth.get(root.identifier) || nodeWidth) + nodeSpacing * 2;
151
+ }
152
+ return positions;
153
+ };
154
+ /**
155
+ * Extracts LLM details from node data for display
156
+ */
157
+ const extractLLMDetails = (node) => {
158
+ var _a, _b;
159
+ let description = node.node_type;
160
+ let modelInfo = "";
161
+ if ((_b = (_a = node.details) === null || _a === void 0 ? void 0 : _a.internals) === null || _b === void 0 ? void 0 : _b.llm_details) {
162
+ const llmDetails = node.details.internals.llm_details;
163
+ if (llmDetails.length > 0) {
164
+ const lastLLM = llmDetails[llmDetails.length - 1];
165
+ modelInfo = `${lastLLM.model_name} (${lastLLM.model_provider})`;
166
+ description = `${node.node_type}\n${modelInfo}`;
167
+ }
168
+ }
169
+ return { description, modelInfo };
170
+ };
171
+ /**
172
+ * Truncates text to specified length with ellipsis
173
+ */
174
+ const truncateText = (text, maxLength) => {
175
+ if (text.length <= maxLength)
176
+ return text;
177
+ return text.substring(0, maxLength) + "...";
178
+ };
179
+ const nodeTypes = {
180
+ agent: Node_1.Node
181
+ };
182
+ const edgeTypes = {
183
+ default: Edge_1.Edge
184
+ };
185
+ const AgenticFlowVisualizer = ({ flowData: propFlowData, width = "100%", height = "1000px", className = "" }) => {
186
+ var _a, _b, _c, _d;
187
+ // Use prop data directly, no hooks needed
188
+ const flowData = propFlowData;
189
+ // Show no data state if no flowData is available
190
+ if (!flowData) {
191
+ return ((0, jsx_runtime_1.jsx)("div", { style: {
192
+ width: typeof width === "number" ? `${width}px` : width,
193
+ height: typeof height === "number" ? `${height}px` : height,
194
+ border: "1px solid #e5e7eb",
195
+ borderRadius: "8px",
196
+ background: "#f9fafb",
197
+ position: "relative",
198
+ minWidth: "800px",
199
+ minHeight: "600px",
200
+ display: "flex",
201
+ alignItems: "center",
202
+ justifyContent: "center"
203
+ }, className: className, children: (0, jsx_runtime_1.jsxs)("div", { style: { textAlign: "center" }, children: [(0, jsx_runtime_1.jsx)("div", { style: { fontSize: "16px", color: "#6b7280", marginBottom: "8px" }, children: "No flow data available" }), (0, jsx_runtime_1.jsx)("div", { style: { fontSize: "14px", color: "#9ca3af" }, children: "Please provide flowData prop" })] }) }));
204
+ }
205
+ const containerRef = (0, react_1.useRef)(null);
206
+ const svgRef = (0, react_1.useRef)(null);
207
+ const [containerDimensions, setContainerDimensions] = (0, react_1.useState)({
208
+ width: typeof width === "number" ? width : 800,
209
+ height: typeof height === "number" ? height : 600
210
+ });
211
+ // Timeline state
212
+ const [currentStep, setCurrentStep] = (0, react_1.useState)(0);
213
+ const [isPlaying, setIsPlaying] = (0, react_1.useState)(false);
214
+ const playIntervalRef = (0, react_1.useRef)(null);
215
+ // Timeline visibility state
216
+ const [showTimelines, setShowTimelines] = (0, react_1.useState)(false);
217
+ // Drawer state
218
+ const [isDrawerOpen, setIsDrawerOpen] = (0, react_1.useState)(false);
219
+ const [selectedData, setSelectedData] = (0, react_1.useState)(null);
220
+ // Get max step from stamps or steps
221
+ const maxStep = (0, react_1.useMemo)(() => {
222
+ const stamps = flowData.stamps || flowData.steps || [];
223
+ return stamps.length > 0 ? Math.max(...stamps.map((s) => s.step)) : 0;
224
+ }, [flowData.stamps, flowData.steps]);
225
+ // Auto-play functionality
226
+ (0, react_1.useEffect)(() => {
227
+ if (isPlaying) {
228
+ playIntervalRef.current = setInterval(() => {
229
+ setCurrentStep((prev) => {
230
+ if (prev >= maxStep) {
231
+ setIsPlaying(false);
232
+ return prev;
233
+ }
234
+ return prev + 1;
235
+ });
236
+ }, 250); // 1 second per step
237
+ }
238
+ else {
239
+ if (playIntervalRef.current) {
240
+ clearInterval(playIntervalRef.current);
241
+ playIntervalRef.current = null;
242
+ }
243
+ }
244
+ return () => {
245
+ if (playIntervalRef.current) {
246
+ clearInterval(playIntervalRef.current);
247
+ }
248
+ };
249
+ }, [isPlaying, maxStep]);
250
+ // Initialize current step to last step and pan to hub node
251
+ (0, react_1.useEffect)(() => {
252
+ const stamps = flowData.stamps || flowData.steps || [];
253
+ if (stamps.length > 0) {
254
+ setCurrentStep(Math.max(...stamps.map((s) => s.step)));
255
+ }
256
+ }, [flowData.stamps, flowData.steps]);
257
+ // Update dimensions when width/height props change
258
+ (0, react_1.useEffect)(() => {
259
+ const updateDimensions = () => {
260
+ if (containerRef.current) {
261
+ const rect = containerRef.current.getBoundingClientRect();
262
+ setContainerDimensions({
263
+ width: rect.width || (typeof width === "number" ? width : 800),
264
+ height: rect.height || (typeof height === "number" ? height : 600)
265
+ });
266
+ }
267
+ };
268
+ updateDimensions();
269
+ // Use ResizeObserver if available, otherwise fallback to window resize
270
+ if (window.ResizeObserver && containerRef.current) {
271
+ const resizeObserver = new ResizeObserver(updateDimensions);
272
+ resizeObserver.observe(containerRef.current);
273
+ return () => resizeObserver.disconnect();
274
+ }
275
+ else {
276
+ window.addEventListener("resize", updateDimensions);
277
+ return () => window.removeEventListener("resize", updateDimensions);
278
+ }
279
+ }, [width, height]);
280
+ // Calculate auto-layout positions
281
+ const positions = (0, react_1.useMemo)(() => {
282
+ return calculateAutoLayout(flowData.nodes, flowData.edges || []);
283
+ }, [flowData.nodes, flowData.edges]);
284
+ // Get nodes and edges for current step
285
+ const getNodesForStep = (0, react_1.useCallback)((step) => {
286
+ return flowData.nodes.filter((node) => node.stamp.step <= step);
287
+ }, [flowData.nodes]);
288
+ const getEdgesForStep = (0, react_1.useCallback)((step) => {
289
+ return (flowData.edges || []).filter((edge) => edge.stamp.step <= step);
290
+ }, [flowData.edges]);
291
+ // Handle node inspection
292
+ const handleNodeInspect = (0, react_1.useCallback)((nodeData) => {
293
+ setSelectedData({ type: "node", data: nodeData });
294
+ setIsDrawerOpen(true);
295
+ }, []);
296
+ // Handle edge inspection
297
+ const handleEdgeInspect = (0, react_1.useCallback)((edgeData) => {
298
+ // Find the edge in the current edges to get the ID
299
+ const currentEdges = getEdgesForStep(currentStep);
300
+ const edge = currentEdges.find((e) => e.source === edgeData.source && e.target === edgeData.target);
301
+ const edgeWithId = Object.assign(Object.assign({}, edgeData), { id: (edge === null || edge === void 0 ? void 0 : edge.identifier) || "N/A" });
302
+ setSelectedData({ type: "edge", data: edgeWithId });
303
+ setIsDrawerOpen(true);
304
+ }, [getEdgesForStep, currentStep]);
305
+ // Convert flow data to ReactFlow format with step filtering
306
+ const nodes = (0, react_1.useMemo)(() => {
307
+ const stepNodes = getNodesForStep(currentStep);
308
+ const stepEdges = getEdgesForStep(currentStep);
309
+ return stepNodes.map((node) => {
310
+ var _a, _b;
311
+ const position = positions.get(node.identifier) || { x: 0, y: 0 };
312
+ const { description } = extractLLMDetails(node);
313
+ const isActive = node.stamp.step === currentStep;
314
+ return {
315
+ id: node.identifier,
316
+ type: "agent",
317
+ position,
318
+ data: {
319
+ label: node.node_type,
320
+ description,
321
+ nodeType: node.node_type,
322
+ step: (_a = node.stamp) === null || _a === void 0 ? void 0 : _a.step,
323
+ time: (_b = node.stamp) === null || _b === void 0 ? void 0 : _b.time,
324
+ isActive,
325
+ onInspect: handleNodeInspect,
326
+ id: node.identifier, // Add id for zoom functionality
327
+ edges: stepEdges // Pass edges to the node
328
+ },
329
+ style: {
330
+ filter: isActive ? "drop-shadow(0 4px 8px rgba(99, 102, 241, 0.3))" : "none"
331
+ }
332
+ };
333
+ });
334
+ }, [positions, currentStep, getNodesForStep, getEdgesForStep, handleNodeInspect]);
335
+ const edges = (0, react_1.useMemo)(() => {
336
+ const stepEdges = getEdgesForStep(currentStep);
337
+ return stepEdges
338
+ .filter((edge) => edge.source && edge.target)
339
+ .map((edge) => {
340
+ var _a, _b, _c, _d;
341
+ const isActive = edge.stamp.step === currentStep;
342
+ return {
343
+ id: edge.identifier,
344
+ type: "default",
345
+ source: edge.source,
346
+ target: edge.target,
347
+ animated: isActive,
348
+ bidirectional: true, // Enable bidirectional edges with arrow heads
349
+ style: {
350
+ stroke: isActive ? "#6366f1" : "#9ca3af",
351
+ strokeWidth: isActive ? 3 : 2
352
+ },
353
+ label: ((_a = edge.stamp) === null || _a === void 0 ? void 0 : _a.identifier) ? truncateText(String(edge.stamp.identifier), 50) : undefined,
354
+ data: {
355
+ label: ((_b = edge.stamp) === null || _b === void 0 ? void 0 : _b.identifier) ? truncateText(String(edge.stamp.identifier), 50) : undefined,
356
+ source: edge.source,
357
+ target: edge.target,
358
+ step: (_c = edge.stamp) === null || _c === void 0 ? void 0 : _c.step,
359
+ time: (_d = edge.stamp) === null || _d === void 0 ? void 0 : _d.time,
360
+ details: edge.details
361
+ }
362
+ };
363
+ });
364
+ }, [currentStep, getEdgesForStep]);
365
+ const [nodesState, setNodes, onNodesChange] = (0, reactflow_2.useNodesState)(nodes);
366
+ const [edgesState, setEdges, onEdgesChange] = (0, reactflow_2.useEdgesState)(edges);
367
+ // Update nodes and edges when currentStep changes
368
+ (0, react_1.useEffect)(() => {
369
+ setNodes(nodes);
370
+ }, [nodes, setNodes]);
371
+ (0, react_1.useEffect)(() => {
372
+ setEdges(edges);
373
+ }, [edges, setEdges]);
374
+ const onConnect = (0, react_1.useCallback)((params) => setEdges((eds) => (0, reactflow_2.addEdge)(params, eds)), [setEdges]);
375
+ const handleStepChange = (0, react_1.useCallback)((step) => {
376
+ setCurrentStep(step);
377
+ setIsPlaying(false);
378
+ }, []);
379
+ const handlePlayPause = (0, react_1.useCallback)(() => {
380
+ setIsPlaying((prev) => !prev);
381
+ }, []);
382
+ // Function to convert client (screen) coordinates to SVG coordinates
383
+ const clientToSvgCoords = (clientX, clientY) => {
384
+ var _a;
385
+ if (!svgRef.current)
386
+ return { x: 0, y: 0 };
387
+ const pt = svgRef.current.createSVGPoint();
388
+ pt.x = clientX;
389
+ pt.y = clientY;
390
+ const svgP = pt.matrixTransform((_a = svgRef.current.getScreenCTM()) === null || _a === void 0 ? void 0 : _a.inverse());
391
+ return { x: svgP.x, y: svgP.y };
392
+ };
393
+ const reactFlowInstance = (0, reactflow_1.useReactFlow)();
394
+ // Pan to hub node (most connected node) on initial load
395
+ (0, react_1.useEffect)(() => {
396
+ if (nodes.length > 0 && reactFlowInstance) {
397
+ // Find the node with the most connections
398
+ const nodeConnectionCounts = new Map();
399
+ // Count incoming and outgoing connections for each node
400
+ edges.forEach((edge) => {
401
+ // Count outgoing connections (source)
402
+ const sourceCount = nodeConnectionCounts.get(edge.source) || 0;
403
+ nodeConnectionCounts.set(edge.source, sourceCount + 1);
404
+ // Count incoming connections (target)
405
+ const targetCount = nodeConnectionCounts.get(edge.target) || 0;
406
+ nodeConnectionCounts.set(edge.target, targetCount + 1);
407
+ });
408
+ // Find the node with the highest connection count
409
+ let hubNodeId = "";
410
+ let maxConnections = 0;
411
+ nodeConnectionCounts.forEach((count, nodeId) => {
412
+ if (count > maxConnections) {
413
+ maxConnections = count;
414
+ hubNodeId = nodeId;
415
+ }
416
+ });
417
+ // If no hub node found (no edges), use the first node
418
+ if (!hubNodeId && nodes.length > 0) {
419
+ hubNodeId = nodes[0].id;
420
+ }
421
+ // Pan to the hub node
422
+ if (hubNodeId) {
423
+ setTimeout(() => {
424
+ reactFlowInstance.fitView({
425
+ nodes: [{ id: hubNodeId }],
426
+ duration: 1000,
427
+ padding: 0.2,
428
+ minZoom: 0.2,
429
+ maxZoom: 1.5
430
+ });
431
+ }, 500); // Small delay to ensure nodes are rendered
432
+ }
433
+ }
434
+ }, [nodes, edges, reactFlowInstance]);
435
+ return ((0, jsx_runtime_1.jsxs)("div", { ref: containerRef, style: {
436
+ width: typeof width === "number" ? `${width}px` : width,
437
+ height: typeof height === "number" ? `${height}px` : height,
438
+ border: "1px solid #e5e7eb",
439
+ borderRadius: "8px",
440
+ overflow: "hidden",
441
+ position: "relative",
442
+ minWidth: "800px",
443
+ minHeight: "600px"
444
+ }, className: className, children: [(0, jsx_runtime_1.jsxs)("div", { className: `side-panel ${showTimelines ? "expanded" : "collapsed"}`, children: [!showTimelines && ((0, jsx_runtime_1.jsx)("button", { className: "panel-toggle", onClick: () => setShowTimelines(!showTimelines), title: "Expand Panel", children: (0, jsx_runtime_1.jsx)(lucide_react_1.PanelLeft, { size: 20 }) })), showTimelines && ((0, jsx_runtime_1.jsx)("div", { className: "panel-content", children: (0, jsx_runtime_1.jsx)(VerticalTimeline_1.VerticalTimeline, { stamps: flowData.stamps || flowData.steps || [], currentStep: currentStep, onStepChange: handleStepChange, onToggle: () => setShowTimelines(false) }) }))] }), (0, jsx_runtime_1.jsxs)(reactflow_2.default, { nodes: nodesState, edges: edgesState, onNodesChange: onNodesChange, onEdgesChange: onEdgesChange, onConnect: onConnect, nodeTypes: nodeTypes, edgeTypes: {
445
+ default: (edgeProps) => ((0, jsx_runtime_1.jsx)(Edge_1.Edge, Object.assign({}, edgeProps, { clientToSvgCoords: clientToSvgCoords, svgRef: svgRef, onInspect: handleEdgeInspect })))
446
+ }, attributionPosition: "bottom-left", style: {
447
+ width: showTimelines ? "calc(100% - 280px)" : "100%", // Account for side panel width when visible
448
+ height: "calc(100% - 60px)", // Account for timeline height
449
+ marginLeft: showTimelines ? "280px" : "0" // Push content to the right of side panel when visible
450
+ }, defaultViewport: { x: 0, y: 0, zoom: 1 }, onInit: (instance) => {
451
+ if (containerRef.current) {
452
+ const svg = containerRef.current.querySelector("svg");
453
+ if (svg)
454
+ svgRef.current = svg;
455
+ }
456
+ }, children: [(0, jsx_runtime_1.jsx)(reactflow_2.Controls, {}), (0, jsx_runtime_1.jsx)(reactflow_2.Background, { color: "#f3f4f6", gap: 16 })] }), (0, jsx_runtime_1.jsxs)("div", { className: `right-drawer ${isDrawerOpen ? "open" : ""}`, children: [(0, jsx_runtime_1.jsx)("div", { className: "drawer-toggle", onClick: () => setIsDrawerOpen(!isDrawerOpen), children: (0, jsx_runtime_1.jsx)(lucide_react_1.PanelRight, { size: 20 }) }), isDrawerOpen && selectedData && ((0, jsx_runtime_1.jsxs)("div", { className: "drawer-content", children: [(0, jsx_runtime_1.jsx)("div", { className: "drawer-header", children: (0, jsx_runtime_1.jsx)("h3", { children: selectedData.type === "node" ? "Node Details" : "Edge Details" }) }), (0, jsx_runtime_1.jsx)("div", { className: "drawer-body", children: selectedData.type === "node" ? (
457
+ // Node Details
458
+ (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "detail-row", children: [(0, jsx_runtime_1.jsx)("span", { className: "detail-label", children: "Label:" }), (0, jsx_runtime_1.jsx)("span", { className: "detail-value", children: selectedData.data.label || "N/A" })] }), selectedData.data.description && ((0, jsx_runtime_1.jsxs)("div", { className: "detail-row", children: [(0, jsx_runtime_1.jsx)("span", { className: "detail-label", children: "Description:" }), (0, jsx_runtime_1.jsx)("span", { className: "detail-value", children: selectedData.data.description })] })), selectedData.data.nodeType && ((0, jsx_runtime_1.jsxs)("div", { className: "detail-row", children: [(0, jsx_runtime_1.jsx)("span", { className: "detail-label", children: "Type:" }), (0, jsx_runtime_1.jsx)("span", { className: "detail-value", children: selectedData.data.nodeType })] })), selectedData.data.step && ((0, jsx_runtime_1.jsxs)("div", { className: "detail-row", children: [(0, jsx_runtime_1.jsx)("span", { className: "detail-label", children: "Step:" }), (0, jsx_runtime_1.jsx)("span", { className: "detail-value", children: selectedData.data.step })] })), selectedData.data.time && ((0, jsx_runtime_1.jsxs)("div", { className: "detail-row", children: [(0, jsx_runtime_1.jsx)("span", { className: "detail-label", children: "Time:" }), (0, jsx_runtime_1.jsx)("span", { className: "detail-value", children: new Date(selectedData.data.time * 1000).toLocaleString() })] })), selectedData.data.icon && ((0, jsx_runtime_1.jsxs)("div", { className: "detail-row", children: [(0, jsx_runtime_1.jsx)("span", { className: "detail-label", children: "Icon:" }), (0, jsx_runtime_1.jsx)("span", { className: "detail-value", children: selectedData.data.icon })] }))] })) : (
459
+ // Edge Details
460
+ (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "detail-row", children: [(0, jsx_runtime_1.jsx)("span", { className: "detail-label", children: "ID:" }), (0, jsx_runtime_1.jsx)("span", { className: "detail-value", children: selectedData.data.id || "N/A" })] }), selectedData.data.source && ((0, jsx_runtime_1.jsxs)("div", { className: "detail-row", children: [(0, jsx_runtime_1.jsx)("span", { className: "detail-label", children: "Source:" }), (0, jsx_runtime_1.jsx)("span", { className: "detail-value", children: selectedData.data.source })] })), selectedData.data.target && ((0, jsx_runtime_1.jsxs)("div", { className: "detail-row", children: [(0, jsx_runtime_1.jsx)("span", { className: "detail-label", children: "Target:" }), (0, jsx_runtime_1.jsx)("span", { className: "detail-value", children: selectedData.data.target })] })), selectedData.data.label && ((0, jsx_runtime_1.jsxs)("div", { className: "detail-row", children: [(0, jsx_runtime_1.jsx)("span", { className: "detail-label", children: "Label:" }), (0, jsx_runtime_1.jsx)("span", { className: "detail-value", children: selectedData.data.label })] })), selectedData.data.step && ((0, jsx_runtime_1.jsxs)("div", { className: "detail-row", children: [(0, jsx_runtime_1.jsx)("span", { className: "detail-label", children: "Step:" }), (0, jsx_runtime_1.jsx)("span", { className: "detail-value", children: selectedData.data.step })] })), selectedData.data.time && ((0, jsx_runtime_1.jsxs)("div", { className: "detail-row", children: [(0, jsx_runtime_1.jsx)("span", { className: "detail-label", children: "Time:" }), (0, jsx_runtime_1.jsx)("span", { className: "detail-value", children: new Date(selectedData.data.time * 1000).toLocaleString() })] })), ((_b = (_a = selectedData.data) === null || _a === void 0 ? void 0 : _a.details) === null || _b === void 0 ? void 0 : _b.input_args) &&
461
+ Array.isArray(selectedData.data.details.input_args) &&
462
+ selectedData.data.details.input_args.length > 0 && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "detail-row", children: [(0, jsx_runtime_1.jsx)("span", { className: "detail-label", children: "Inputs" }), (0, jsx_runtime_1.jsx)("span", { className: "detail-value", style: { overflowY: "auto", maxHeight: "300px" }, children: Array.isArray(selectedData.data.details.input_args[0]) ? (selectedData.data.details.input_args[0].map((arg, index) => ((0, jsx_runtime_1.jsxs)("div", { style: { marginBottom: 8 }, children: [(0, jsx_runtime_1.jsx)("span", { className: "detail-label", children: "Role:" }), (0, jsx_runtime_1.jsx)("span", { className: "detail-value", children: (arg === null || arg === void 0 ? void 0 : arg.role) || "Unknown" }), (0, jsx_runtime_1.jsx)("span", { className: "detail-label", children: "Content:" }), (0, jsx_runtime_1.jsx)("span", { className: "detail-value", children: (arg === null || arg === void 0 ? void 0 : arg.content) || "No content" })] }, (arg === null || arg === void 0 ? void 0 : arg.role) || index)))) : ((0, jsx_runtime_1.jsx)("span", { className: "detail-value", children: JSON.stringify(selectedData.data.details.input_args[0], null, 2) })) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "detail-row", children: [(0, jsx_runtime_1.jsx)("span", { className: "detail-label", children: "Outputs" }), (0, jsx_runtime_1.jsx)("span", { className: "detail-value", children: JSON.stringify((_d = (_c = selectedData.data) === null || _c === void 0 ? void 0 : _c.details) === null || _d === void 0 ? void 0 : _d.output, null, 2) })] })] }))] })) })] }))] }), showTimelines && ((0, jsx_runtime_1.jsx)("div", { style: { marginLeft: "280px", marginTop: "100px" }, children: (0, jsx_runtime_1.jsx)(Timeline_1.Timeline, { stamps: flowData.stamps || flowData.steps || [], currentStep: currentStep, isPlaying: isPlaying, onStepChange: handleStepChange, onPlayPause: handlePlayPause }) })), (0, jsx_runtime_1.jsx)("style", { children: `
463
+ .react-flow__edge-label {
464
+ font-size: 10px;
465
+ background: white;
466
+ padding: 2px 4px;
467
+ border-radius: 4px;
468
+ border: 1px solid #e5e7eb;
469
+ max-width: 150px;
470
+ overflow: hidden;
471
+ text-overflow: ellipsis;
472
+ white-space: nowrap;
473
+ }
474
+
475
+ /* Right Drawer Styles */
476
+ .right-drawer {
477
+ position: absolute;
478
+ top: 0;
479
+ right: 0;
480
+ height: 100%;
481
+ z-index: 1000;
482
+ display: flex;
483
+ align-items: flex-start;
484
+ transition: transform 0.3s ease;
485
+ margin-left: ${showTimelines ? "280px" : "0"}; /* Account for vertical timeline when visible */
486
+ }
487
+
488
+ .right-drawer:not(.open) {
489
+ transform: translateX(calc(100% - 50px));
490
+ }
491
+
492
+ .right-drawer.open {
493
+ transform: translateX(0);
494
+ }
495
+
496
+ .drawer-toggle {
497
+ width: 50px;
498
+ height: 50px;
499
+ background: none;
500
+ color: #6b7280;
501
+ border: none;
502
+ cursor: pointer;
503
+ display: flex;
504
+ align-items: center;
505
+ justify-content: center;
506
+ transition: color 0.2s ease;
507
+ margin-top: 20px;
508
+ }
509
+
510
+ .drawer-toggle:hover {
511
+ color: #374151;
512
+ }
513
+
514
+ .drawer-content {
515
+ background: white;
516
+ border: 1px solid #e5e7eb;
517
+ border-radius: 8px 0 0 8px;
518
+ width: 400px;
519
+ height: calc(100% - 40px);
520
+ margin-top: 20px;
521
+ display: flex;
522
+ flex-direction: column;
523
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
524
+ animation: drawerSlideIn 0.3s ease-out;
525
+ }
526
+
527
+ .drawer-header {
528
+ padding: 16px 20px;
529
+ border-bottom: 1px solid #e5e7eb;
530
+ background: #f9fafb;
531
+ display: flex;
532
+ justify-content: space-between;
533
+ align-items: center;
534
+ flex-shrink: 0;
535
+ }
536
+
537
+ .drawer-header h3 {
538
+ margin: 0;
539
+ font-size: 16px;
540
+ font-weight: 600;
541
+ color: #1f2937;
542
+ }
543
+
544
+ .close-button {
545
+ background: none;
546
+ border: none;
547
+ font-size: 20px;
548
+ color: #6b7280;
549
+ cursor: pointer;
550
+ padding: 4px;
551
+ width: 28px;
552
+ height: 28px;
553
+ display: flex;
554
+ align-items: center;
555
+ justify-content: center;
556
+ border-radius: 6px;
557
+ transition: all 0.2s ease;
558
+ }
559
+
560
+ .close-button:hover {
561
+ background: #e5e7eb;
562
+ color: #1f2937;
563
+ }
564
+
565
+ .drawer-body {
566
+ padding: 20px;
567
+ overflow-y: auto;
568
+ flex: 1;
569
+ width: 100%;
570
+ box-sizing: border-box;
571
+ }
572
+
573
+ .detail-row {
574
+ display: grid;
575
+ grid-template-columns: 100px 1fr;
576
+ margin-bottom: 12px;
577
+ align-items: flex-start;
578
+ gap: 8px;
579
+ }
580
+
581
+ .detail-row:last-child {
582
+ margin-bottom: 0;
583
+ }
584
+
585
+ .detail-label {
586
+ font-weight: 600;
587
+ color: #6b7280;
588
+ font-size: 13px;
589
+ word-break: break-word;
590
+ }
591
+
592
+ .detail-value {
593
+ color: #1f2937;
594
+ font-size: 13px;
595
+ word-break: break-word;
596
+ flex: 1;
597
+ width: 100%;
598
+ overflow: visible;
599
+ text-overflow: unset;
600
+ max-width: unset;
601
+ white-space: pre-line;
602
+ line-height: 1.4;
603
+ }
604
+
605
+ @keyframes drawerSlideIn {
606
+ from {
607
+ opacity: 0;
608
+ transform: translateX(20px);
609
+ }
610
+ to {
611
+ opacity: 1;
612
+ transform: translateX(0);
613
+ }
614
+ }
615
+
616
+ /* Scoreboard Styles */
617
+ .scoreboard {
618
+ position: absolute;
619
+ top: 20px;
620
+ left: 50%;
621
+ transform: translateX(-50%);
622
+ background: white;
623
+ border: 1px solid #e5e7eb;
624
+ border-radius: 8px;
625
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
626
+ z-index: 1000;
627
+ min-width: 400px;
628
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
629
+ }
630
+
631
+ .scoreboard-header {
632
+ padding: 12px 16px;
633
+ border-bottom: 1px solid #e5e7eb;
634
+ background: #f9fafb;
635
+ text-align: center;
636
+ }
637
+
638
+ .scoreboard-header h3 {
639
+ margin: 0;
640
+ font-size: 14px;
641
+ font-weight: 600;
642
+ color: #1f2937;
643
+ }
644
+
645
+ .scoreboard-content {
646
+ padding: 16px;
647
+ display: flex;
648
+ justify-content: space-around;
649
+ align-items: center;
650
+ gap: 20px;
651
+ }
652
+
653
+ .scoreboard-item {
654
+ display: flex;
655
+ flex-direction: column;
656
+ align-items: center;
657
+ gap: 4px;
658
+ }
659
+
660
+ .scoreboard-label {
661
+ font-size: 12px;
662
+ color: #6b7280;
663
+ font-weight: 500;
664
+ text-transform: uppercase;
665
+ letter-spacing: 0.5px;
666
+ }
667
+
668
+ .scoreboard-count {
669
+ font-size: 18px;
670
+ font-weight: 700;
671
+ padding: 6px 12px;
672
+ border-radius: 6px;
673
+ min-width: 40px;
674
+ text-align: center;
675
+ }
676
+
677
+ .scoreboard-count.open {
678
+ background: #fef3c7;
679
+ color: #92400e;
680
+ }
681
+
682
+ .scoreboard-count.completed {
683
+ background: #d1fae5;
684
+ color: #065f46;
685
+ }
686
+
687
+ .scoreboard-count.error {
688
+ background: #fee2e2;
689
+ color: #991b1b;
690
+ }
691
+
692
+ .scoreboard-count.error.clickable {
693
+ transition: all 0.2s ease;
694
+ }
695
+
696
+ .scoreboard-count.error.clickable:hover {
697
+ background: #fecaca;
698
+ transform: scale(1.05);
699
+ box-shadow: 0 2px 8px rgba(239, 68, 68, 0.3);
700
+ }
701
+
702
+ /* Side Panel Styles */
703
+ .side-panel {
704
+ position: absolute;
705
+ top: 0;
706
+ left: 0;
707
+ height: 100%;
708
+ z-index: 1000;
709
+ display: flex;
710
+ flex-direction: column;
711
+ transition: all 0.3s ease;
712
+ }
713
+
714
+ .side-panel.collapsed {
715
+ width: 60px;
716
+ }
717
+
718
+ .side-panel.expanded {
719
+ width: 280px;
720
+ }
721
+
722
+ .panel-toggle {
723
+ display: flex;
724
+ align-items: center;
725
+ justify-content: center;
726
+ border: none;
727
+ background: none;
728
+ cursor: pointer;
729
+ color: #6b7280;
730
+ transition: color 0.2s ease;
731
+ padding: 8px;
732
+ margin: 20px 0 0 0;
733
+ }
734
+
735
+ .panel-toggle:hover {
736
+ color: #374151;
737
+ }
738
+
739
+ .panel-content {
740
+ flex: 1;
741
+ background: white;
742
+ border-right: 1px solid #e5e7eb;
743
+ box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1);
744
+ overflow: hidden;
745
+ }
746
+
747
+
748
+ ` })] }));
749
+ };
750
+ exports.default = AgenticFlowVisualizer;