@majdibo/flow-visualizer 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,339 @@
1
+ # Flow Visualizer
2
+
3
+ <p align="center">
4
+ <img src="./public/logo.svg" alt="Flow Visualizer Logo" width="120" height="120">
5
+ </p>
6
+
7
+ <p align="center">
8
+ <strong>Beautiful React components for visualizing execution flows and timelines</strong>
9
+ </p>
10
+
11
+ <p align="center">
12
+ <a href="https://www.npmjs.com/package/@majdibo/flow-visualizer">
13
+ <img src="https://img.shields.io/npm/v/@majdibo/flow-visualizer.svg" alt="npm version">
14
+ </a>
15
+ <a href="https://github.com/majdibo/flow-visualizer/blob/main/LICENSE">
16
+ <img src="https://img.shields.io/npm/l/@majdibo/flow-visualizer.svg" alt="license">
17
+ </a>
18
+ <a href="https://www.npmjs.com/package/@majdibo/flow-visualizer">
19
+ <img src="https://img.shields.io/npm/dm/@majdibo/flow-visualizer.svg" alt="downloads">
20
+ </a>
21
+ </p>
22
+
23
+ <p align="center">
24
+ <a href="https://majdibo.github.io/flow-visualizer">🎮 Try the Interactive Playground</a>
25
+ </p>
26
+
27
+ ## Why Flow Visualizer?
28
+
29
+ Visualizing complex execution flows shouldn't require building a custom graph renderer from scratch. Flow Visualizer gives you production-ready React components to display:
30
+
31
+ - **Workflow executions** - Show users how their automations run
32
+ - **State machines** - Visualize state transitions and flows
33
+ - **Process traces** - Debug and monitor system processes
34
+ - **AI agent reasoning** - Display agent decision trees and tool usage
35
+ - **Build pipelines** - Show CI/CD execution flows
36
+ - **Any trace-based execution** - Generic enough for any sequential process
37
+
38
+ Built on top of [`@majdibo/flow-engine`](https://github.com/majdibo/flow-engine), it handles the hard parts (graph layout, state management, timeline tracking) so you can focus on your application.
39
+
40
+ ## ✨ Features
41
+
42
+ - 🎨 **Beautiful by default** - Modern UI with light/dark mode support
43
+ - ⚛️ **React native** - Drop-in components, fully typed with TypeScript
44
+ - 🖱️ **Interactive** - Zoom, pan, and explore with smooth animations
45
+ - 📊 **Auto-layout** - Powered by Dagre for perfect graph positioning
46
+ - ⏱️ **Timeline support** - Replay executions step-by-step
47
+ - 🎯 **Customizable** - Override styles and customize node displays
48
+ - 📦 **Zero config** - Works out of the box with sensible defaults
49
+
50
+ ## 🚀 Installation
51
+
52
+ ```bash
53
+ npm install @majdibo/flow-visualizer
54
+ ```
55
+
56
+ Or with yarn:
57
+
58
+ ```bash
59
+ yarn add @majdibo/flow-visualizer
60
+ ```
61
+
62
+ > **Note:** `@majdibo/flow-engine` is a required peer dependency for graph construction and state management.
63
+
64
+ ## 📖 Quick Start
65
+
66
+ ### Basic Example
67
+
68
+ ```tsx
69
+ import { FlowVisualizer, computeLayout } from '@majdibo/flow-visualizer';
70
+ import { buildGraphFromTraces, computeVisualState } from '@majdibo/flow-engine';
71
+ import { useState } from 'react';
72
+
73
+ function MyFlowApp() {
74
+ // Your execution traces
75
+ const traces = [
76
+ { step: 'start', nodeType: 'action', data: { msg: 'Starting' }, timestamp: Date.now() },
77
+ { step: 'process', nodeType: 'action', data: { msg: 'Processing' }, timestamp: Date.now() },
78
+ { step: 'end', nodeType: 'action', data: { msg: 'Done' }, timestamp: Date.now() }
79
+ ];
80
+
81
+ const [currentIndex, setCurrentIndex] = useState(0);
82
+
83
+ // Build graph from traces
84
+ const graph = buildGraphFromTraces(traces);
85
+ const timeline = traces.map(t => t.step);
86
+
87
+ // Compute layout
88
+ const { nodes, edges } = computeLayout(graph);
89
+
90
+ // Compute visual state
91
+ const visualState = computeVisualState(graph, timeline, currentIndex);
92
+
93
+ return (
94
+ <div>
95
+ <FlowVisualizer
96
+ nodes={nodes}
97
+ edges={edges}
98
+ state={visualState}
99
+ onNodeClick={(nodeId) => console.log('Clicked:', nodeId)}
100
+ />
101
+
102
+ {/* Timeline controls */}
103
+ <button onClick={() => setCurrentIndex(Math.max(0, currentIndex - 1))}>
104
+ Previous
105
+ </button>
106
+ <button onClick={() => setCurrentIndex(Math.min(timeline.length - 1, currentIndex + 1))}>
107
+ Next
108
+ </button>
109
+ </div>
110
+ );
111
+ }
112
+ ```
113
+
114
+ ### With Custom Node Details
115
+
116
+ Show detailed information when users click on nodes:
117
+
118
+ ```tsx
119
+ import { FlowVisualizer, NodeDetailModal } from '@majdibo/flow-visualizer';
120
+ import { useState } from 'react';
121
+
122
+ function MyApp() {
123
+ const [selectedNode, setSelectedNode] = useState<string | null>(null);
124
+
125
+ // Customize how trace details are displayed
126
+ const renderTraceDetails = (trace: any, traceIndex: number) => (
127
+ <div className="custom-trace-card">
128
+ <h4>Step {traceIndex + 1}</h4>
129
+ <p>{trace.step}</p>
130
+ <pre>{JSON.stringify(trace.data, null, 2)}</pre>
131
+ </div>
132
+ );
133
+
134
+ return (
135
+ <>
136
+ <FlowVisualizer
137
+ nodes={nodes}
138
+ edges={edges}
139
+ state={visualState}
140
+ onNodeClick={setSelectedNode}
141
+ />
142
+
143
+ {selectedNode && (
144
+ <NodeDetailModal
145
+ nodeId={selectedNode}
146
+ nodes={nodes}
147
+ graph={graph}
148
+ traces={traces}
149
+ visualState={visualState}
150
+ icons={{}}
151
+ onClose={() => setSelectedNode(null)}
152
+ renderTraceDetails={renderTraceDetails}
153
+ />
154
+ )}
155
+ </>
156
+ );
157
+ }
158
+ ```
159
+
160
+ ### Custom Layout Options
161
+
162
+ Control how your graph is laid out:
163
+
164
+ ```tsx
165
+ const { nodes, edges } = computeLayout(graph, {
166
+ direction: 'LR', // Left-to-right layout
167
+ nodeSep: 100, // Horizontal spacing
168
+ rankSep: 150, // Vertical spacing
169
+ edgeStyle: 'orthogonal' // Right-angle edges
170
+ });
171
+ ```
172
+
173
+ ## 📚 API Reference
174
+
175
+ ### `<FlowVisualizer />`
176
+
177
+ The main component for rendering interactive flow diagrams.
178
+
179
+ | Prop | Type | Description |
180
+ |------|------|-------------|
181
+ | `nodes` | `VisualNode[]` | Array of nodes with positions (from `computeLayout`) |
182
+ | `edges` | `VisualEdge[]` | Array of edges with paths (from `computeLayout`) |
183
+ | `state` | `VisualState` | Current state (from `computeVisualState`) |
184
+ | `onNodeClick?` | `(nodeId: string) => void` | Called when a node is clicked |
185
+
186
+ **Interactions:**
187
+ - **Mouse wheel** - Zoom in/out
188
+ - **Click + drag** - Pan around the graph
189
+ - **Double-click** - Reset zoom and position
190
+ - **Click node** - Trigger `onNodeClick` callback
191
+
192
+ ### `<NodeDetailModal />`
193
+
194
+ Display detailed information about a selected node in a modal.
195
+
196
+ | Prop | Type | Description |
197
+ |------|------|-------------|
198
+ | `nodeId` | `string` | ID of the node to display |
199
+ | `nodes` | `VisualNode[]` | Array of visual nodes |
200
+ | `graph` | `FlowGraph` | The flow graph |
201
+ | `traces` | `Trace[]` | Array of execution traces |
202
+ | `visualState` | `VisualState` | Current visual state |
203
+ | `icons` | `Record<string, ReactNode>` | Icon mapping for node types |
204
+ | `onClose` | `() => void` | Called when modal is closed |
205
+ | `renderTraceDetails?` | `(trace, index) => ReactNode` | Custom trace renderer |
206
+
207
+ ### `computeLayout(graph, options?)`
208
+
209
+ Compute positions for nodes and paths for edges using automatic layout.
210
+
211
+ **Parameters:**
212
+ - `graph` - FlowGraph from `buildGraphFromTraces`
213
+ - `options?` - Layout configuration:
214
+ - `direction` - Layout direction: `'TB'` (top-bottom), `'LR'` (left-right), `'BT'` (bottom-top), `'RL'` (right-left). Default: `'TB'`
215
+ - `nodeSep` - Horizontal spacing between nodes. Default: `80`
216
+ - `rankSep` - Vertical spacing between ranks. Default: `120`
217
+ - `edgeStyle` - Edge routing style: `'polyline'` or `'orthogonal'`. Default: `'polyline'`
218
+
219
+ **Returns:** `{ nodes: VisualNode[], edges: VisualEdge[] }`
220
+
221
+ ## 🎨 Styling & Theming
222
+
223
+ Flow Visualizer includes beautiful default styles and automatically adapts to light/dark mode based on user preferences (`prefers-color-scheme`).
224
+
225
+ ### Customizing Colors
226
+
227
+ Override CSS variables to match your brand:
228
+
229
+ ```css
230
+ :root {
231
+ /* Light mode */
232
+ --flow-node-current-stroke: #171717;
233
+ --flow-node-traversed-fill: #f5f5f5;
234
+ --flow-edge-current: #171717;
235
+ --flow-text-primary: #171717;
236
+ }
237
+
238
+ @media (prefers-color-scheme: dark) {
239
+ :root {
240
+ /* Dark mode */
241
+ --flow-node-current-stroke: #f5f5f5;
242
+ --flow-node-traversed-fill: #262626;
243
+ --flow-edge-current: #f5f5f5;
244
+ --flow-text-primary: #f5f5f5;
245
+ }
246
+ }
247
+ ```
248
+
249
+ ### Available CSS Variables
250
+
251
+ | Variable | Description |
252
+ |----------|-------------|
253
+ | `--flow-bg` | Background color |
254
+ | `--flow-node-current-fill` | Current node background |
255
+ | `--flow-node-current-stroke` | Current node border |
256
+ | `--flow-node-traversed-fill` | Completed node background |
257
+ | `--flow-node-untraversed-fill` | Pending node background |
258
+ | `--flow-edge-current` | Current edge color |
259
+ | `--flow-edge-traversed` | Completed edge color |
260
+ | `--flow-text-primary` | Primary text color |
261
+ | `--flow-text-secondary` | Secondary text color |
262
+ | `--flow-gradient-start` | Gradient start color |
263
+ | `--flow-gradient-end` | Gradient end color |
264
+
265
+ ## 🔧 TypeScript Support
266
+
267
+ Flow Visualizer is written in TypeScript and includes full type definitions:
268
+
269
+ ```typescript
270
+ import type {
271
+ VisualNode,
272
+ VisualEdge,
273
+ LayoutOptions,
274
+ LayoutResult,
275
+ FlowVisualizerProps,
276
+ NodeDetailModalProps
277
+ } from '@majdibo/flow-visualizer';
278
+ ```
279
+
280
+ All components and functions are fully typed for the best developer experience.
281
+
282
+ ## 🤝 Contributing
283
+
284
+ We welcome contributions! Here's how you can help:
285
+
286
+ 1. **Fork the repository**
287
+ 2. **Create a feature branch** (`git checkout -b feature/amazing-feature`)
288
+ 3. **Commit your changes** (`git commit -m 'Add amazing feature'`)
289
+ 4. **Push to the branch** (`git push origin feature/amazing-feature`)
290
+ 5. **Open a Pull Request**
291
+
292
+ ### Development Setup
293
+
294
+ ```bash
295
+ # Clone the repo
296
+ git clone https://github.com/majdibo/flow-visualizer.git
297
+ cd flow-visualizer
298
+
299
+ # Install dependencies
300
+ npm install
301
+
302
+ # Start development server
303
+ npm run dev
304
+
305
+ # Build the library
306
+ npm run build
307
+ ```
308
+
309
+ ### Reporting Issues
310
+
311
+ Found a bug or have a feature request? [Open an issue](https://github.com/majdibo/flow-visualizer/issues) on GitHub.
312
+
313
+ ## 📦 Related Projects
314
+
315
+ - [`@majdibo/flow-engine`](https://github.com/majdibo/flow-engine) - Core graph engine (required dependency)
316
+
317
+ ## 🙏 Acknowledgments
318
+
319
+ Built with:
320
+ - [React](https://react.dev/) - UI library
321
+ - [Dagre](https://github.com/dagrejs/dagre) - Graph layout
322
+ - [TypeScript](https://www.typescriptlang.org/) - Type safety
323
+ - [Tailwind CSS](https://tailwindcss.com/) - Styling
324
+
325
+ ## 📄 License
326
+
327
+ MIT License - see [LICENSE](LICENSE) for details.
328
+
329
+ ---
330
+
331
+ <p align="center">
332
+ Made with ❤️ by <a href="https://github.com/majdibo">majdibo</a>
333
+ </p>
334
+
335
+ <p align="center">
336
+ <a href="https://github.com/majdibo/flow-visualizer">GitHub</a> •
337
+ <a href="https://www.npmjs.com/package/@majdibo/flow-visualizer">npm</a> •
338
+ <a href="https://github.com/majdibo/flow-visualizer/issues">Issues</a>
339
+ </p>
@@ -0,0 +1 @@
1
+ :root{--flow-bg: transparent;--flow-node-current-fill: url(#gradient-current);--flow-node-current-stroke: #0F766E;--flow-node-current-shadow: drop-shadow(0 4px 16px rgba(42, 157, 143, .4)) drop-shadow(0 0 24px rgba(42, 157, 143, .2));--flow-node-traversed-fill: #F1F5F9;--flow-node-traversed-stroke: #CBD5E1;--flow-node-traversed-shadow: drop-shadow(0 2px 4px rgba(15, 23, 42, .08));--flow-node-untraversed-fill: #FFFFFF;--flow-node-untraversed-stroke: #E2E8F0;--flow-node-untraversed-shadow: drop-shadow(0 1px 3px rgba(15, 23, 42, .06));--flow-edge-current: #14B8A6;--flow-edge-current-glow: rgba(20, 184, 166, .3);--flow-edge-traversed: #94A3B8;--flow-edge-untraversed: #E2E8F0;--flow-text-primary: #0F172A;--flow-text-secondary: #64748B;--flow-text-current: #FFFFFF;--flow-gradient-start: #14B8A6;--flow-gradient-end: #0D9488;--flow-node-action: #2A9D8F;--flow-node-decision: #457B9D;--flow-node-tool: #E76F51;--flow-node-memory: #F4A261}.dark{--flow-node-current-fill: url(#gradient-current);--flow-node-current-stroke: #5EEAD4;--flow-node-current-shadow: drop-shadow(0 4px 20px rgba(61, 186, 162, .5)) drop-shadow(0 0 32px rgba(61, 186, 162, .3));--flow-node-traversed-fill: #334155;--flow-node-traversed-stroke: #475569;--flow-node-traversed-shadow: drop-shadow(0 2px 6px rgba(0, 0, 0, .4));--flow-node-untraversed-fill: #1E293B;--flow-node-untraversed-stroke: #334155;--flow-node-untraversed-shadow: drop-shadow(0 1px 3px rgba(0, 0, 0, .5));--flow-edge-current: #5EEAD4;--flow-edge-current-glow: rgba(94, 234, 212, .4);--flow-edge-traversed: #64748B;--flow-edge-untraversed: #334155;--flow-text-primary: #F1F5F9;--flow-text-secondary: #94A3B8;--flow-text-current: #0F172A;--flow-gradient-start: #5EEAD4;--flow-gradient-end: #2DD4BF;--flow-node-action: #3DBAA2;--flow-node-decision: #5A9FBF;--flow-node-tool: #F4A261;--flow-node-memory: #FFD166}@media(prefers-color-scheme:dark){:root{--flow-node-current-fill: url(#gradient-current);--flow-node-current-stroke: #5EEAD4;--flow-node-current-shadow: drop-shadow(0 4px 20px rgba(61, 186, 162, .5)) drop-shadow(0 0 32px rgba(61, 186, 162, .3));--flow-node-traversed-fill: #334155;--flow-node-traversed-stroke: #475569;--flow-node-traversed-shadow: drop-shadow(0 2px 6px rgba(0, 0, 0, .4));--flow-node-untraversed-fill: #1E293B;--flow-node-untraversed-stroke: #334155;--flow-node-untraversed-shadow: drop-shadow(0 1px 3px rgba(0, 0, 0, .5));--flow-edge-current: #5EEAD4;--flow-edge-current-glow: rgba(94, 234, 212, .4);--flow-edge-traversed: #64748B;--flow-edge-untraversed: #334155;--flow-text-primary: #F1F5F9;--flow-text-secondary: #94A3B8;--flow-text-current: #0F172A;--flow-gradient-start: #5EEAD4;--flow-gradient-end: #2DD4BF;--flow-node-action: #3DBAA2;--flow-node-decision: #5A9FBF;--flow-node-tool: #F4A261;--flow-node-memory: #FFD166}}*{transition:fill .3s ease,stroke .3s ease,filter .3s ease}.flow-node{cursor:pointer;transition:all .2s ease}.flow-node:hover{filter:brightness(1.05);transform:scale(1.02)}.flow-node-current{animation:pulse-node 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes pulse-node{0%,to{filter:drop-shadow(0 4px 16px rgba(42,157,143,.4)) drop-shadow(0 0 24px rgba(42,157,143,.2))}50%{filter:drop-shadow(0 4px 20px rgba(42,157,143,.6)) drop-shadow(0 0 32px rgba(42,157,143,.4))}}.dark .flow-node-current{animation:pulse-node-dark 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes pulse-node-dark{0%,to{filter:drop-shadow(0 4px 20px rgba(61,186,162,.5)) drop-shadow(0 0 32px rgba(61,186,162,.3))}50%{filter:drop-shadow(0 4px 24px rgba(61,186,162,.7)) drop-shadow(0 0 40px rgba(61,186,162,.5))}}.flow-edge-current{stroke-width:2.5;filter:drop-shadow(0 0 8px var(--flow-edge-current-glow));animation:flow-pulse 2s ease-in-out infinite}@keyframes flow-pulse{0%,to{opacity:1}50%{opacity:.7}}.flow-edge-traversed{stroke-width:2;stroke-dasharray:none}.flow-edge-untraversed{stroke-width:1.5;stroke-dasharray:5,5;opacity:.5}.flow-arrow-current{fill:var(--flow-edge-current);filter:drop-shadow(0 0 4px var(--flow-edge-current-glow))}.flow-arrow-traversed{fill:var(--flow-edge-traversed)}.flow-arrow-untraversed{fill:var(--flow-edge-untraversed);opacity:.5}.flow-node-label{font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;font-weight:600;font-size:13px;letter-spacing:-.01em}.flow-node-sublabel{font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;font-weight:500;font-size:11px;opacity:.7}.flow-node-badge{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em}.flow-modal-overlay{position:fixed;top:0;left:0;width:100vw;height:100vh;display:flex;align-items:center;justify-content:center;z-index:9999}.flow-modal-backdrop{position:absolute;top:0;left:0;width:100%;height:100%;z-index:1;background-color:#00000080;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.flow-modal-container{position:relative;z-index:2;display:flex;align-items:center;justify-content:center;padding:1rem;max-width:100%;max-height:100%}.flow-modal-container>div{background:#fff;border-radius:.75rem;box-shadow:0 20px 25px -5px #0000001a,0 10px 10px -5px #0000000a;max-width:600px;width:100%;max-height:80vh;overflow-y:auto}@media(prefers-color-scheme:dark){.flow-modal-container>div{background:#0f172a;box-shadow:0 20px 25px -5px #0000004d,0 10px 10px -5px #0003}}.flow-modal-content{display:flex;flex-direction:column;max-height:80vh;overflow:hidden}.flow-modal-header{display:flex;align-items:center;justify-content:space-between}.flow-modal-title{display:flex;align-items:center;gap:.75rem}.flow-modal-close{cursor:pointer;border:none;background:none;font-size:1.5rem;line-height:1;padding:.5rem}.flow-modal-body{flex:1;overflow-y:auto}.flow-modal-section{margin-bottom:1rem}.flow-modal-section-title{font-weight:600;margin-bottom:.5rem}.flow-modal-section-content{display:flex;align-items:center;gap:.5rem}.flow-modal-state-indicator{width:.5rem;height:.5rem;border-radius:50%;display:inline-block}.flow-modal-traces{display:flex;flex-direction:column;gap:.75rem}.flow-modal-trace{border:1px solid #e5e7eb;border-radius:.5rem;padding:.75rem}.flow-modal-trace-header{display:flex;justify-content:space-between;margin-bottom:.5rem;font-size:.875rem}.flow-modal-trace-content{margin-bottom:.5rem}.flow-modal-trace-details summary{cursor:pointer;font-size:.875rem}.flow-modal-trace-data{margin-top:.5rem;padding:.5rem;font-size:.75rem;overflow-x:auto;border-radius:.25rem}.flow-modal-empty{text-align:center;padding:2rem;color:#9ca3af}