@railtownai/railtracks-visualizer 0.0.2 → 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.
- package/LICENSE +201 -201
- package/README.md +52 -23
- package/dist/cjs/AgenticFlowVisualizer.js +750 -0
- package/dist/cjs/App.js +94 -0
- package/dist/cjs/Visualizer.js +16 -0
- package/dist/cjs/components/Edge.js +75 -0
- package/dist/cjs/components/FileSelector.js +28 -0
- package/dist/cjs/components/Node.js +104 -0
- package/dist/cjs/components/Timeline.js +122 -0
- package/dist/cjs/components/VerticalTimeline.js +160 -0
- package/dist/cjs/hooks/index.js +7 -0
- package/dist/cjs/hooks/useApi.js +108 -0
- package/dist/cjs/hooks/useFlowData.js +53 -0
- package/dist/cjs/index.js +40 -0
- package/dist/cjs/test/Visualizer.test.js +257 -0
- package/dist/cjs/test/setup.js +24 -0
- package/dist/{AgenticFlowVisualizer.js → esm/AgenticFlowVisualizer.js} +286 -286
- package/dist/{App.js → esm/App.js} +76 -76
- package/dist/esm/Visualizer.js +11 -0
- package/dist/{components → esm/components}/Edge.js +10 -10
- package/dist/{components → esm/components}/Node.js +70 -70
- package/dist/{components → esm/components}/VerticalTimeline.js +12 -12
- package/dist/{index.js → esm/index.js} +1 -2
- package/dist/esm/test/Visualizer.test.js +243 -0
- package/dist/{test → esm/test}/setup.js +1 -1
- package/dist/{AgenticFlowVisualizer.d.ts → types/AgenticFlowVisualizer.d.ts} +2 -2
- package/dist/types/AgenticFlowVisualizer.d.ts.map +1 -0
- package/dist/types/App.d.ts.map +1 -0
- package/dist/types/Visualizer.d.ts +9 -0
- package/dist/types/Visualizer.d.ts.map +1 -0
- package/dist/types/components/Edge.d.ts.map +1 -0
- package/dist/types/components/FileSelector.d.ts.map +1 -0
- package/dist/types/components/Node.d.ts.map +1 -0
- package/dist/types/components/Timeline.d.ts.map +1 -0
- package/dist/types/components/VerticalTimeline.d.ts.map +1 -0
- package/dist/types/hooks/index.d.ts.map +1 -0
- package/dist/types/hooks/useApi.d.ts.map +1 -0
- package/dist/types/hooks/useFlowData.d.ts.map +1 -0
- package/dist/{index.d.ts → types/index.d.ts} +2 -1
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/test/Visualizer.test.d.ts +2 -0
- package/dist/types/test/Visualizer.test.d.ts.map +1 -0
- package/dist/types/test/setup.d.ts.map +1 -0
- package/package.json +82 -75
- package/dist/AgenticFlowVisualizer.d.ts.map +0 -1
- package/dist/App.d.ts.map +0 -1
- package/dist/components/Edge.d.ts.map +0 -1
- package/dist/components/FileSelector.d.ts.map +0 -1
- package/dist/components/Node.d.ts.map +0 -1
- package/dist/components/Timeline.d.ts.map +0 -1
- package/dist/components/VerticalTimeline.d.ts.map +0 -1
- package/dist/hooks/index.d.ts.map +0 -1
- package/dist/hooks/useApi.d.ts.map +0 -1
- package/dist/hooks/useFlowData.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/test/setup.d.ts.map +0 -1
- /package/dist/{components → esm/components}/FileSelector.js +0 -0
- /package/dist/{components → esm/components}/Timeline.js +0 -0
- /package/dist/{hooks → esm/hooks}/index.js +0 -0
- /package/dist/{hooks → esm/hooks}/useApi.js +0 -0
- /package/dist/{hooks → esm/hooks}/useFlowData.js +0 -0
- /package/dist/{App.d.ts → types/App.d.ts} +0 -0
- /package/dist/{components → types/components}/Edge.d.ts +0 -0
- /package/dist/{components → types/components}/FileSelector.d.ts +0 -0
- /package/dist/{components → types/components}/Node.d.ts +0 -0
- /package/dist/{components → types/components}/Timeline.d.ts +0 -0
- /package/dist/{components → types/components}/VerticalTimeline.d.ts +0 -0
- /package/dist/{hooks → types/hooks}/index.d.ts +0 -0
- /package/dist/{hooks → types/hooks}/useApi.d.ts +0 -0
- /package/dist/{hooks → types/hooks}/useFlowData.d.ts +0 -0
- /package/dist/{test → types/test}/setup.d.ts +0 -0
|
@@ -8,82 +8,82 @@ const App = () => {
|
|
|
8
8
|
const handleFileSelect = (filename) => {
|
|
9
9
|
loadFile(filename);
|
|
10
10
|
};
|
|
11
|
-
return (_jsxs("div", { className: "app", children: [_jsxs("div", { className: "app-header", children: [_jsx("h1", { children: "RailTracks Visualizer" }), _jsx("div", { className: "file-selector-container", children: _jsx(FileSelector, { files: availableFiles, currentFile: currentFile, onFileSelect: handleFileSelect, onRefresh: refreshFiles, loading: loading, disabled: loading }) })] }), error && (_jsx("div", { className: "error-message", children: _jsxs("p", { children: ["Error: ", error] }) })), _jsx("div", { className: "visualizer-container", children: loading ? (_jsxs("div", { className: "loading-state", children: [_jsx("div", { className: "loading-spinner" }), _jsx("p", { children: "Loading flow data..." })] })) : flowData ? (_jsx(ReactFlowProvider, { children: _jsx(AgenticFlowVisualizer, { flowData: flowData }) })) : (_jsx("div", { className: "no-data-state", children: _jsx("p", { children: "Please select a file to visualize the flow data" }) })) }), _jsx("style", { children: `
|
|
12
|
-
.app {
|
|
13
|
-
min-height: 100vh;
|
|
14
|
-
background: #f9fafb;
|
|
15
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
.app-header {
|
|
19
|
-
background: white;
|
|
20
|
-
border-bottom: 1px solid #e5e7eb;
|
|
21
|
-
padding: 5px;
|
|
22
|
-
display: flex;
|
|
23
|
-
align-items: center;
|
|
24
|
-
justify-content: space-between;
|
|
25
|
-
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
.app-header h1 {
|
|
29
|
-
margin: 0;
|
|
30
|
-
font-size: 18px;
|
|
31
|
-
font-weight: 600;
|
|
32
|
-
color: #1f2937;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
.file-selector-container {
|
|
36
|
-
display: flex;
|
|
37
|
-
align-items: center;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
.error-message {
|
|
41
|
-
background: #fee2e2;
|
|
42
|
-
border: 1px solid #fecaca;
|
|
43
|
-
color: #991b1b;
|
|
44
|
-
padding: 12px 20px;
|
|
45
|
-
margin: 20px;
|
|
46
|
-
border-radius: 6px;
|
|
47
|
-
font-size: 14px;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
.visualizer-container {
|
|
51
|
-
padding: 20px;
|
|
52
|
-
min-height: calc(100vh - 100px);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
.loading-state {
|
|
56
|
-
display: flex;
|
|
57
|
-
flex-direction: column;
|
|
58
|
-
align-items: center;
|
|
59
|
-
justify-content: center;
|
|
60
|
-
height: 400px;
|
|
61
|
-
color: #6b7280;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
.loading-spinner {
|
|
65
|
-
width: 40px;
|
|
66
|
-
height: 40px;
|
|
67
|
-
border: 4px solid #e5e7eb;
|
|
68
|
-
border-top: 4px solid #6366f1;
|
|
69
|
-
border-radius: 50%;
|
|
70
|
-
animation: spin 1s linear infinite;
|
|
71
|
-
margin-bottom: 16px;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
@keyframes spin {
|
|
75
|
-
0% { transform: rotate(0deg); }
|
|
76
|
-
100% { transform: rotate(360deg); }
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
.no-data-state {
|
|
80
|
-
display: flex;
|
|
81
|
-
align-items: center;
|
|
82
|
-
justify-content: center;
|
|
83
|
-
height: 400px;
|
|
84
|
-
color: #6b7280;
|
|
85
|
-
font-size: 16px;
|
|
86
|
-
}
|
|
11
|
+
return (_jsxs("div", { className: "app", children: [_jsxs("div", { className: "app-header", children: [_jsx("h1", { children: "RailTracks Visualizer" }), _jsx("div", { className: "file-selector-container", children: _jsx(FileSelector, { files: availableFiles, currentFile: currentFile, onFileSelect: handleFileSelect, onRefresh: refreshFiles, loading: loading, disabled: loading }) })] }), error && (_jsx("div", { className: "error-message", children: _jsxs("p", { children: ["Error: ", error] }) })), _jsx("div", { className: "visualizer-container", children: loading ? (_jsxs("div", { className: "loading-state", children: [_jsx("div", { className: "loading-spinner" }), _jsx("p", { children: "Loading flow data..." })] })) : flowData ? (_jsx(ReactFlowProvider, { children: _jsx(AgenticFlowVisualizer, { flowData: flowData }) })) : (_jsx("div", { className: "no-data-state", children: _jsx("p", { children: "Please select a file to visualize the flow data" }) })) }), _jsx("style", { children: `
|
|
12
|
+
.app {
|
|
13
|
+
min-height: 100vh;
|
|
14
|
+
background: #f9fafb;
|
|
15
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.app-header {
|
|
19
|
+
background: white;
|
|
20
|
+
border-bottom: 1px solid #e5e7eb;
|
|
21
|
+
padding: 5px;
|
|
22
|
+
display: flex;
|
|
23
|
+
align-items: center;
|
|
24
|
+
justify-content: space-between;
|
|
25
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.app-header h1 {
|
|
29
|
+
margin: 0;
|
|
30
|
+
font-size: 18px;
|
|
31
|
+
font-weight: 600;
|
|
32
|
+
color: #1f2937;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.file-selector-container {
|
|
36
|
+
display: flex;
|
|
37
|
+
align-items: center;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.error-message {
|
|
41
|
+
background: #fee2e2;
|
|
42
|
+
border: 1px solid #fecaca;
|
|
43
|
+
color: #991b1b;
|
|
44
|
+
padding: 12px 20px;
|
|
45
|
+
margin: 20px;
|
|
46
|
+
border-radius: 6px;
|
|
47
|
+
font-size: 14px;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.visualizer-container {
|
|
51
|
+
padding: 20px;
|
|
52
|
+
min-height: calc(100vh - 100px);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.loading-state {
|
|
56
|
+
display: flex;
|
|
57
|
+
flex-direction: column;
|
|
58
|
+
align-items: center;
|
|
59
|
+
justify-content: center;
|
|
60
|
+
height: 400px;
|
|
61
|
+
color: #6b7280;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.loading-spinner {
|
|
65
|
+
width: 40px;
|
|
66
|
+
height: 40px;
|
|
67
|
+
border: 4px solid #e5e7eb;
|
|
68
|
+
border-top: 4px solid #6366f1;
|
|
69
|
+
border-radius: 50%;
|
|
70
|
+
animation: spin 1s linear infinite;
|
|
71
|
+
margin-bottom: 16px;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@keyframes spin {
|
|
75
|
+
0% { transform: rotate(0deg); }
|
|
76
|
+
100% { transform: rotate(360deg); }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.no-data-state {
|
|
80
|
+
display: flex;
|
|
81
|
+
align-items: center;
|
|
82
|
+
justify-content: center;
|
|
83
|
+
height: 400px;
|
|
84
|
+
color: #6b7280;
|
|
85
|
+
font-size: 16px;
|
|
86
|
+
}
|
|
87
87
|
` })] }));
|
|
88
88
|
};
|
|
89
89
|
export default App;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { ReactFlowProvider } from "reactflow";
|
|
3
|
+
import AgenticFlowVisualizer from "./AgenticFlowVisualizer";
|
|
4
|
+
/**
|
|
5
|
+
* Visualizer component that wraps AgenticFlowVisualizer with ReactFlowProvider.
|
|
6
|
+
* This is the main component that should be imported from the npm package.
|
|
7
|
+
*/
|
|
8
|
+
const Visualizer = (props) => {
|
|
9
|
+
return (_jsx(ReactFlowProvider, { children: _jsx(AgenticFlowVisualizer, { ...props }) }));
|
|
10
|
+
};
|
|
11
|
+
export default Visualizer;
|
|
@@ -60,15 +60,15 @@ export const Edge = ({ id, sourceX, sourceY, targetX, targetY, sourcePosition, t
|
|
|
60
60
|
cursor: "pointer",
|
|
61
61
|
boxShadow: "0 2px 8px rgba(99,102,241,0.10)",
|
|
62
62
|
transition: "background 0.2s"
|
|
63
|
-
}, children: "Inspect" }) }) })), _jsx("style", { children: `
|
|
64
|
-
.edge-label-renderer {
|
|
65
|
-
z-index: 8001;
|
|
66
|
-
user-select: none;
|
|
67
|
-
pointer-events: auto;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
.edge-label-button {
|
|
71
|
-
pointer-events: auto;
|
|
72
|
-
}
|
|
63
|
+
}, children: "Inspect" }) }) })), _jsx("style", { children: `
|
|
64
|
+
.edge-label-renderer {
|
|
65
|
+
z-index: 8001;
|
|
66
|
+
user-select: none;
|
|
67
|
+
pointer-events: auto;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.edge-label-button {
|
|
71
|
+
pointer-events: auto;
|
|
72
|
+
}
|
|
73
73
|
` })] }));
|
|
74
74
|
};
|
|
@@ -25,76 +25,76 @@ const Node = ({ data, id }) => {
|
|
|
25
25
|
});
|
|
26
26
|
}
|
|
27
27
|
};
|
|
28
|
-
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "agent-node", onClick: handleNodeClick, style: { cursor: "pointer" }, children: [_jsx(Handle, { type: "target", position: Position.Top, style: { background: "#6366f1" } }), _jsxs("div", { className: "agent-header", children: [_jsx("div", { className: "agent-icon", children: data.icon || "📋" }), _jsx("div", { className: "agent-label", children: data.label })] }), data.description && _jsx("div", { className: "agent-description", children: data.description }), data.step && (_jsxs("div", { className: "agent-meta", children: [_jsxs("span", { className: "step", children: ["Step: ", data.step] }), data.time && _jsx("span", { className: "time", children: new Date(data.time * 1000).toLocaleTimeString() })] })), hasOutgoingEdges && _jsx(Handle, { type: "source", position: Position.Bottom, style: { background: "#6366f1" } })] }), _jsx("style", { children: `
|
|
29
|
-
.agent-node {
|
|
30
|
-
padding: 12px;
|
|
31
|
-
border-radius: 8px;
|
|
32
|
-
background: white;
|
|
33
|
-
border: 2px solid #e5e7eb;
|
|
34
|
-
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
35
|
-
min-width: 200px;
|
|
36
|
-
max-width: 250px;
|
|
37
|
-
transition: all 0.2s ease;
|
|
38
|
-
position: relative;
|
|
39
|
-
z-index: -5;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
.agent-node:hover {
|
|
43
|
-
border-color: #6366f1;
|
|
44
|
-
box-shadow: 0 4px 8px rgba(99, 102, 241, 0.2);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
.agent-header {
|
|
48
|
-
display: flex;
|
|
49
|
-
align-items: center;
|
|
50
|
-
gap: 8px;
|
|
51
|
-
margin-bottom: 8px;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
.agent-icon {
|
|
55
|
-
font-size: 20px;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
.agent-label {
|
|
59
|
-
font-weight: 600;
|
|
60
|
-
color: #1f2937;
|
|
61
|
-
font-size: 14px;
|
|
62
|
-
word-break: break-word;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
.agent-description {
|
|
66
|
-
color: #6b7280;
|
|
67
|
-
font-size: 12px;
|
|
68
|
-
line-height: 1.4;
|
|
69
|
-
word-break: break-word;
|
|
70
|
-
white-space: pre-line;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
.agent-meta {
|
|
74
|
-
margin-top: 8px;
|
|
75
|
-
padding-top: 8px;
|
|
76
|
-
border-top: 1px solid #e5e7eb;
|
|
77
|
-
display: flex;
|
|
78
|
-
justify-content: space-between;
|
|
79
|
-
font-size: 10px;
|
|
80
|
-
color: #9ca3af;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
.step {
|
|
84
|
-
background: #f3f4f6;
|
|
85
|
-
padding: 2px 6px;
|
|
86
|
-
border-radius: 4px;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
.time {
|
|
90
|
-
font-family: monospace;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
.react-flow__handle {
|
|
94
|
-
width: 8px;
|
|
95
|
-
height: 8px;
|
|
96
|
-
border: 2px solid #6366f1;
|
|
97
|
-
}
|
|
28
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "agent-node", onClick: handleNodeClick, style: { cursor: "pointer" }, children: [_jsx(Handle, { type: "target", position: Position.Top, style: { background: "#6366f1" } }), _jsxs("div", { className: "agent-header", children: [_jsx("div", { className: "agent-icon", children: data.icon || "📋" }), _jsx("div", { className: "agent-label", children: data.label })] }), data.description && _jsx("div", { className: "agent-description", children: data.description }), data.step && (_jsxs("div", { className: "agent-meta", children: [_jsxs("span", { className: "step", children: ["Step: ", data.step] }), data.time && _jsx("span", { className: "time", children: new Date(data.time * 1000).toLocaleTimeString() })] })), hasOutgoingEdges && _jsx(Handle, { type: "source", position: Position.Bottom, style: { background: "#6366f1" } })] }), _jsx("style", { children: `
|
|
29
|
+
.agent-node {
|
|
30
|
+
padding: 12px;
|
|
31
|
+
border-radius: 8px;
|
|
32
|
+
background: white;
|
|
33
|
+
border: 2px solid #e5e7eb;
|
|
34
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
35
|
+
min-width: 200px;
|
|
36
|
+
max-width: 250px;
|
|
37
|
+
transition: all 0.2s ease;
|
|
38
|
+
position: relative;
|
|
39
|
+
z-index: -5;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.agent-node:hover {
|
|
43
|
+
border-color: #6366f1;
|
|
44
|
+
box-shadow: 0 4px 8px rgba(99, 102, 241, 0.2);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.agent-header {
|
|
48
|
+
display: flex;
|
|
49
|
+
align-items: center;
|
|
50
|
+
gap: 8px;
|
|
51
|
+
margin-bottom: 8px;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.agent-icon {
|
|
55
|
+
font-size: 20px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.agent-label {
|
|
59
|
+
font-weight: 600;
|
|
60
|
+
color: #1f2937;
|
|
61
|
+
font-size: 14px;
|
|
62
|
+
word-break: break-word;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.agent-description {
|
|
66
|
+
color: #6b7280;
|
|
67
|
+
font-size: 12px;
|
|
68
|
+
line-height: 1.4;
|
|
69
|
+
word-break: break-word;
|
|
70
|
+
white-space: pre-line;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.agent-meta {
|
|
74
|
+
margin-top: 8px;
|
|
75
|
+
padding-top: 8px;
|
|
76
|
+
border-top: 1px solid #e5e7eb;
|
|
77
|
+
display: flex;
|
|
78
|
+
justify-content: space-between;
|
|
79
|
+
font-size: 10px;
|
|
80
|
+
color: #9ca3af;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.step {
|
|
84
|
+
background: #f3f4f6;
|
|
85
|
+
padding: 2px 6px;
|
|
86
|
+
border-radius: 4px;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.time {
|
|
90
|
+
font-family: monospace;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.react-flow__handle {
|
|
94
|
+
width: 8px;
|
|
95
|
+
height: 8px;
|
|
96
|
+
border: 2px solid #6366f1;
|
|
97
|
+
}
|
|
98
98
|
` })] }));
|
|
99
99
|
};
|
|
100
100
|
export { Node };
|
|
@@ -139,18 +139,18 @@ const VerticalTimeline = ({ stamps, currentStep, onStepChange, onToggle }) => {
|
|
|
139
139
|
backgroundColor: "#6366f1",
|
|
140
140
|
animation: "pulse 2s infinite"
|
|
141
141
|
} }))] }, step));
|
|
142
|
-
}) }), _jsx("style", { children: `
|
|
143
|
-
@keyframes pulse {
|
|
144
|
-
0% {
|
|
145
|
-
opacity: 1;
|
|
146
|
-
}
|
|
147
|
-
50% {
|
|
148
|
-
opacity: 0.5;
|
|
149
|
-
}
|
|
150
|
-
100% {
|
|
151
|
-
opacity: 1;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
142
|
+
}) }), _jsx("style", { children: `
|
|
143
|
+
@keyframes pulse {
|
|
144
|
+
0% {
|
|
145
|
+
opacity: 1;
|
|
146
|
+
}
|
|
147
|
+
50% {
|
|
148
|
+
opacity: 0.5;
|
|
149
|
+
}
|
|
150
|
+
100% {
|
|
151
|
+
opacity: 1;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
154
|
` })] }));
|
|
155
155
|
};
|
|
156
156
|
export { VerticalTimeline };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// Library entry point for npm package
|
|
2
|
+
export { default as Visualizer } from "./Visualizer";
|
|
2
3
|
export { default as AgenticFlowVisualizer } from "./AgenticFlowVisualizer";
|
|
3
4
|
export { default as App } from "./App";
|
|
4
5
|
// Export components
|
|
@@ -9,5 +10,3 @@ export { Timeline } from "./components/Timeline";
|
|
|
9
10
|
export { VerticalTimeline } from "./components/VerticalTimeline";
|
|
10
11
|
// Export hooks
|
|
11
12
|
export * from "./hooks";
|
|
12
|
-
// Export types
|
|
13
|
-
export * from "./types";
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
4
|
+
import { render, screen, waitFor } from "@testing-library/react";
|
|
5
|
+
import Visualizer from "../Visualizer";
|
|
6
|
+
// Mock ReactFlow and its provider since we're testing the wrapper, not ReactFlow itself
|
|
7
|
+
vi.mock("reactflow", () => ({
|
|
8
|
+
ReactFlowProvider: ({ children }) => (_jsx("div", { "data-testid": "react-flow-provider", children: children })),
|
|
9
|
+
useReactFlow: () => ({
|
|
10
|
+
fitView: vi.fn(),
|
|
11
|
+
getNodes: vi.fn(() => []),
|
|
12
|
+
getEdges: vi.fn(() => [])
|
|
13
|
+
})
|
|
14
|
+
}));
|
|
15
|
+
// Mock the AgenticFlowVisualizer component
|
|
16
|
+
vi.mock("../AgenticFlowVisualizer", () => ({
|
|
17
|
+
default: ({ flowData, width, height, className }) => (_jsxs("div", { "data-testid": "agentic-flow-visualizer", "data-width": width || "", "data-height": height || "", "data-classname": className || "", "data-has-flow-data": !!flowData, children: ["AgenticFlowVisualizer Mock", !flowData && _jsx("div", { "data-testid": "no-data-state", children: "No flow data available" })] }))
|
|
18
|
+
}));
|
|
19
|
+
// Mock data for testing
|
|
20
|
+
const mockFlowData = {
|
|
21
|
+
nodes: [
|
|
22
|
+
{
|
|
23
|
+
identifier: "node-1",
|
|
24
|
+
node_type: "agent",
|
|
25
|
+
stamp: {
|
|
26
|
+
step: 1,
|
|
27
|
+
time: 1634567890,
|
|
28
|
+
identifier: "stamp-1"
|
|
29
|
+
},
|
|
30
|
+
details: {
|
|
31
|
+
internals: {
|
|
32
|
+
llm_details: [
|
|
33
|
+
{
|
|
34
|
+
model_name: "gpt-4",
|
|
35
|
+
model_provider: "openai",
|
|
36
|
+
input: [{ role: "user", content: "test" }],
|
|
37
|
+
output: { role: "assistant", content: "response" },
|
|
38
|
+
input_tokens: 10,
|
|
39
|
+
output_tokens: 5,
|
|
40
|
+
total_cost: 0.001,
|
|
41
|
+
system_fingerprint: "test-fingerprint"
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
parent: null
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
identifier: "node-2",
|
|
50
|
+
node_type: "tool",
|
|
51
|
+
stamp: {
|
|
52
|
+
step: 2,
|
|
53
|
+
time: 1634567891,
|
|
54
|
+
identifier: "stamp-2"
|
|
55
|
+
},
|
|
56
|
+
details: {
|
|
57
|
+
internals: {
|
|
58
|
+
llm_details: []
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
parent: null
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
edges: [
|
|
65
|
+
{
|
|
66
|
+
identifier: "edge-1",
|
|
67
|
+
source: "node-1",
|
|
68
|
+
target: "node-2",
|
|
69
|
+
stamp: {
|
|
70
|
+
step: 2,
|
|
71
|
+
time: 1634567891,
|
|
72
|
+
identifier: "edge-stamp-1"
|
|
73
|
+
},
|
|
74
|
+
details: {
|
|
75
|
+
input_args: [],
|
|
76
|
+
output: null
|
|
77
|
+
},
|
|
78
|
+
parent: null
|
|
79
|
+
}
|
|
80
|
+
],
|
|
81
|
+
stamps: [
|
|
82
|
+
{
|
|
83
|
+
step: 1,
|
|
84
|
+
time: 1634567890,
|
|
85
|
+
identifier: "stamp-1"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
step: 2,
|
|
89
|
+
time: 1634567891,
|
|
90
|
+
identifier: "stamp-2"
|
|
91
|
+
}
|
|
92
|
+
]
|
|
93
|
+
};
|
|
94
|
+
describe("Visualizer", () => {
|
|
95
|
+
beforeEach(() => {
|
|
96
|
+
vi.clearAllMocks();
|
|
97
|
+
});
|
|
98
|
+
describe("Component Rendering", () => {
|
|
99
|
+
it("renders successfully without any props", () => {
|
|
100
|
+
render(_jsx(Visualizer, {}));
|
|
101
|
+
expect(screen.getByTestId("react-flow-provider")).toBeInTheDocument();
|
|
102
|
+
expect(screen.getByTestId("agentic-flow-visualizer")).toBeInTheDocument();
|
|
103
|
+
});
|
|
104
|
+
it("wraps AgenticFlowVisualizer with ReactFlowProvider", () => {
|
|
105
|
+
render(_jsx(Visualizer, { flowData: mockFlowData }));
|
|
106
|
+
const provider = screen.getByTestId("react-flow-provider");
|
|
107
|
+
const visualizer = screen.getByTestId("agentic-flow-visualizer");
|
|
108
|
+
expect(provider).toBeInTheDocument();
|
|
109
|
+
expect(provider).toContainElement(visualizer);
|
|
110
|
+
});
|
|
111
|
+
it("renders with valid flow data", () => {
|
|
112
|
+
render(_jsx(Visualizer, { flowData: mockFlowData }));
|
|
113
|
+
const visualizer = screen.getByTestId("agentic-flow-visualizer");
|
|
114
|
+
expect(visualizer).toHaveAttribute("data-has-flow-data", "true");
|
|
115
|
+
expect(screen.queryByTestId("no-data-state")).not.toBeInTheDocument();
|
|
116
|
+
});
|
|
117
|
+
it("handles null flow data gracefully", () => {
|
|
118
|
+
render(_jsx(Visualizer, { flowData: null }));
|
|
119
|
+
const visualizer = screen.getByTestId("agentic-flow-visualizer");
|
|
120
|
+
expect(visualizer).toHaveAttribute("data-has-flow-data", "false");
|
|
121
|
+
});
|
|
122
|
+
it("handles undefined flow data gracefully", () => {
|
|
123
|
+
render(_jsx(Visualizer, { flowData: undefined }));
|
|
124
|
+
const visualizer = screen.getByTestId("agentic-flow-visualizer");
|
|
125
|
+
expect(visualizer).toHaveAttribute("data-has-flow-data", "false");
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
describe("Props Forwarding", () => {
|
|
129
|
+
it("forwards flowData prop correctly", () => {
|
|
130
|
+
render(_jsx(Visualizer, { flowData: mockFlowData }));
|
|
131
|
+
const visualizer = screen.getByTestId("agentic-flow-visualizer");
|
|
132
|
+
expect(visualizer).toHaveAttribute("data-has-flow-data", "true");
|
|
133
|
+
});
|
|
134
|
+
it("forwards width prop correctly", () => {
|
|
135
|
+
render(_jsx(Visualizer, { flowData: mockFlowData, width: "800px" }));
|
|
136
|
+
const visualizer = screen.getByTestId("agentic-flow-visualizer");
|
|
137
|
+
expect(visualizer).toHaveAttribute("data-width", "800px");
|
|
138
|
+
});
|
|
139
|
+
it("forwards height prop correctly", () => {
|
|
140
|
+
render(_jsx(Visualizer, { flowData: mockFlowData, height: 600 }));
|
|
141
|
+
const visualizer = screen.getByTestId("agentic-flow-visualizer");
|
|
142
|
+
expect(visualizer).toHaveAttribute("data-height", "600");
|
|
143
|
+
});
|
|
144
|
+
it("forwards className prop correctly", () => {
|
|
145
|
+
render(_jsx(Visualizer, { flowData: mockFlowData, className: "custom-class" }));
|
|
146
|
+
const visualizer = screen.getByTestId("agentic-flow-visualizer");
|
|
147
|
+
expect(visualizer).toHaveAttribute("data-classname", "custom-class");
|
|
148
|
+
});
|
|
149
|
+
it("forwards all props together correctly", () => {
|
|
150
|
+
render(_jsx(Visualizer, { flowData: mockFlowData, width: "100%", height: "500px", className: "test-visualizer" }));
|
|
151
|
+
const visualizer = screen.getByTestId("agentic-flow-visualizer");
|
|
152
|
+
expect(visualizer).toHaveAttribute("data-has-flow-data", "true");
|
|
153
|
+
expect(visualizer).toHaveAttribute("data-width", "100%");
|
|
154
|
+
expect(visualizer).toHaveAttribute("data-height", "500px");
|
|
155
|
+
expect(visualizer).toHaveAttribute("data-classname", "test-visualizer");
|
|
156
|
+
});
|
|
157
|
+
it("uses default values when optional props are not provided", () => {
|
|
158
|
+
render(_jsx(Visualizer, { flowData: mockFlowData }));
|
|
159
|
+
const visualizer = screen.getByTestId("agentic-flow-visualizer");
|
|
160
|
+
expect(visualizer).toHaveAttribute("data-width", "");
|
|
161
|
+
expect(visualizer).toHaveAttribute("data-height", "");
|
|
162
|
+
expect(visualizer).toHaveAttribute("data-classname", "");
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
describe("Integration as NPM Package Component", () => {
|
|
166
|
+
it("can be imported and used as a standalone component", () => {
|
|
167
|
+
// This test ensures the component works when imported by users
|
|
168
|
+
const TestApp = () => (_jsxs("div", { children: [_jsx("h1", { children: "My App" }), _jsx(Visualizer, { flowData: mockFlowData })] }));
|
|
169
|
+
render(_jsx(TestApp, {}));
|
|
170
|
+
expect(screen.getByText("My App")).toBeInTheDocument();
|
|
171
|
+
expect(screen.getByTestId("react-flow-provider")).toBeInTheDocument();
|
|
172
|
+
expect(screen.getByTestId("agentic-flow-visualizer")).toBeInTheDocument();
|
|
173
|
+
});
|
|
174
|
+
it("works in a React component with state", async () => {
|
|
175
|
+
const TestComponent = () => {
|
|
176
|
+
const [data, setData] = React.useState(null);
|
|
177
|
+
React.useEffect(() => {
|
|
178
|
+
// Simulate loading data
|
|
179
|
+
setTimeout(() => setData(mockFlowData), 100);
|
|
180
|
+
}, []);
|
|
181
|
+
return (_jsxs("div", { children: [_jsx("button", { onClick: () => setData(null), children: "Clear Data" }), _jsx(Visualizer, { flowData: data })] }));
|
|
182
|
+
};
|
|
183
|
+
render(_jsx(TestComponent, {}));
|
|
184
|
+
// Initially no data
|
|
185
|
+
expect(screen.getByTestId("agentic-flow-visualizer")).toHaveAttribute("data-has-flow-data", "false");
|
|
186
|
+
// Wait for data to load
|
|
187
|
+
await waitFor(() => {
|
|
188
|
+
expect(screen.getByTestId("agentic-flow-visualizer")).toHaveAttribute("data-has-flow-data", "true");
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
it("handles multiple instances on the same page", () => {
|
|
192
|
+
render(_jsxs("div", { children: [_jsx(Visualizer, { flowData: mockFlowData, className: "visualizer-1" }), _jsx(Visualizer, { flowData: null, className: "visualizer-2" })] }));
|
|
193
|
+
const visualizers = screen.getAllByTestId("agentic-flow-visualizer");
|
|
194
|
+
expect(visualizers).toHaveLength(2);
|
|
195
|
+
expect(visualizers[0]).toHaveAttribute("data-classname", "visualizer-1");
|
|
196
|
+
expect(visualizers[0]).toHaveAttribute("data-has-flow-data", "true");
|
|
197
|
+
expect(visualizers[1]).toHaveAttribute("data-classname", "visualizer-2");
|
|
198
|
+
expect(visualizers[1]).toHaveAttribute("data-has-flow-data", "false");
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
describe("Error Handling", () => {
|
|
202
|
+
it("handles malformed flow data gracefully", () => {
|
|
203
|
+
const malformedData = {
|
|
204
|
+
nodes: null,
|
|
205
|
+
edges: undefined
|
|
206
|
+
};
|
|
207
|
+
expect(() => {
|
|
208
|
+
render(_jsx(Visualizer, { flowData: malformedData }));
|
|
209
|
+
}).not.toThrow();
|
|
210
|
+
expect(screen.getByTestId("agentic-flow-visualizer")).toBeInTheDocument();
|
|
211
|
+
});
|
|
212
|
+
it("handles empty flow data", () => {
|
|
213
|
+
const emptyData = {
|
|
214
|
+
nodes: [],
|
|
215
|
+
edges: [],
|
|
216
|
+
stamps: []
|
|
217
|
+
};
|
|
218
|
+
render(_jsx(Visualizer, { flowData: emptyData }));
|
|
219
|
+
const visualizer = screen.getByTestId("agentic-flow-visualizer");
|
|
220
|
+
expect(visualizer).toHaveAttribute("data-has-flow-data", "true");
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
describe("TypeScript Compatibility", () => {
|
|
224
|
+
it("accepts correct prop types", () => {
|
|
225
|
+
// This test ensures TypeScript compatibility
|
|
226
|
+
const props = {
|
|
227
|
+
flowData: mockFlowData,
|
|
228
|
+
width: "100%",
|
|
229
|
+
height: 600,
|
|
230
|
+
className: "test-class"
|
|
231
|
+
};
|
|
232
|
+
render(_jsx(Visualizer, { ...props }));
|
|
233
|
+
expect(screen.getByTestId("agentic-flow-visualizer")).toBeInTheDocument();
|
|
234
|
+
});
|
|
235
|
+
it("works with partial props", () => {
|
|
236
|
+
// Test that optional props work correctly
|
|
237
|
+
render(_jsx(Visualizer, { flowData: mockFlowData, width: "50%" }));
|
|
238
|
+
const visualizer = screen.getByTestId("agentic-flow-visualizer");
|
|
239
|
+
expect(visualizer).toHaveAttribute("data-width", "50%");
|
|
240
|
+
expect(visualizer).toHaveAttribute("data-height", "");
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "@testing-library/jest-dom";
|
|
2
2
|
import { vi } from "vitest";
|
|
3
3
|
// Mock ResizeObserver for React Flow components
|
|
4
|
-
|
|
4
|
+
globalThis.ResizeObserver = class ResizeObserver {
|
|
5
5
|
observe() { }
|
|
6
6
|
unobserve() { }
|
|
7
7
|
disconnect() { }
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import "reactflow/dist/style.css";
|
|
3
3
|
import { DataJsonStructure } from "./hooks";
|
|
4
|
-
interface AgenticFlowVisualizerProps {
|
|
5
|
-
flowData?: DataJsonStructure;
|
|
4
|
+
export interface AgenticFlowVisualizerProps {
|
|
5
|
+
flowData?: DataJsonStructure | null;
|
|
6
6
|
width?: string | number;
|
|
7
7
|
height?: string | number;
|
|
8
8
|
className?: string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AgenticFlowVisualizer.d.ts","sourceRoot":"","sources":["../../src/AgenticFlowVisualizer.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAC;AAcjF,OAAO,0BAA0B,CAAC;AAMlC,OAAO,EAA8B,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAMxE,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACpC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAqJD,QAAA,MAAM,qBAAqB,EAAE,KAAK,CAAC,EAAE,CAAC,0BAA0B,CAwyB/D,CAAC;AAEF,eAAe,qBAAqB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"App.d.ts","sourceRoot":"","sources":["../../src/App.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAM1B,QAAA,MAAM,GAAG,EAAE,KAAK,CAAC,EA6HhB,CAAC;AAEF,eAAe,GAAG,CAAC"}
|