@railtownai/railtracks-visualizer 0.0.3 → 0.0.5

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