@railtownai/railtracks-visualizer 0.0.28 → 0.0.30

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 CHANGED
@@ -1,201 +1,21 @@
1
- Apache License
2
- Version 2.0, January 2004
3
- http://www.apache.org/licenses/
4
-
5
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
-
7
- 1. Definitions.
8
-
9
- "License" shall mean the terms and conditions for use, reproduction,
10
- and distribution as defined by Sections 1 through 9 of this document.
11
-
12
- "Licensor" shall mean the copyright owner or entity authorized by
13
- the copyright owner that is granting the License.
14
-
15
- "Legal Entity" shall mean the union of the acting entity and all
16
- other entities that control, are controlled by, or are under common
17
- control with that entity. For the purposes of this definition,
18
- "control" means (i) the power, direct or indirect, to cause the
19
- direction or management of such entity, whether by contract or
20
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
- outstanding shares, or (iii) beneficial ownership of such entity.
22
-
23
- "You" (or "Your") shall mean an individual or Legal Entity
24
- exercising permissions granted by this License.
25
-
26
- "Source" form shall mean the preferred form for making modifications,
27
- including but not limited to software source code, documentation
28
- source, and configuration files.
29
-
30
- "Object" form shall mean any form resulting from mechanical
31
- transformation or translation of a Source form, including but
32
- not limited to compiled object code, generated documentation,
33
- and conversions to other media types.
34
-
35
- "Work" shall mean the work of authorship, whether in Source or
36
- Object form, made available under the License, as indicated by a
37
- copyright notice that is included in or attached to the work
38
- (an example is provided in the Appendix below).
39
-
40
- "Derivative Works" shall mean any work, whether in Source or Object
41
- form, that is based on (or derived from) the Work and for which the
42
- editorial revisions, annotations, elaborations, or other modifications
43
- represent, as a whole, an original work of authorship. For the purposes
44
- of this License, Derivative Works shall not include works that remain
45
- separable from, or merely link (or bind by name) to the interfaces of,
46
- the Work and Derivative Works thereof.
47
-
48
- "Contribution" shall mean any work of authorship, including
49
- the original version of the Work and any modifications or additions
50
- to that Work or Derivative Works thereof, that is intentionally
51
- submitted to Licensor for inclusion in the Work by the copyright owner
52
- or by an individual or Legal Entity authorized to submit on behalf of
53
- the copyright owner. For the purposes of this definition, "submitted"
54
- means any form of electronic, verbal, or written communication sent
55
- to the Licensor or its representatives, including but not limited to
56
- communication on electronic mailing lists, source code control systems,
57
- and issue tracking systems that are managed by, or on behalf of, the
58
- Licensor for the purpose of discussing and improving the Work, but
59
- excluding communication that is conspicuously marked or otherwise
60
- designated in writing by the copyright owner as "Not a Contribution."
61
-
62
- "Contributor" shall mean Licensor and any individual or Legal Entity
63
- on behalf of whom a Contribution has been received by Licensor and
64
- subsequently incorporated within the Work.
65
-
66
- 2. Grant of Copyright License. Subject to the terms and conditions of
67
- this License, each Contributor hereby grants to You a perpetual,
68
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
- copyright license to reproduce, prepare Derivative Works of,
70
- publicly display, publicly perform, sublicense, and distribute the
71
- Work and such Derivative Works in Source or Object form.
72
-
73
- 3. Grant of Patent License. Subject to the terms and conditions of
74
- this License, each Contributor hereby grants to You a perpetual,
75
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
- (except as stated in this section) patent license to make, have made,
77
- use, offer to sell, sell, import, and otherwise transfer the Work,
78
- where such license applies only to those patent claims licensable
79
- by such Contributor that are necessarily infringed by their
80
- Contribution(s) alone or by combination of their Contribution(s)
81
- with the Work to which such Contribution(s) was submitted. If You
82
- institute patent litigation against any entity (including a
83
- cross-claim or counterclaim in a lawsuit) alleging that the Work
84
- or a Contribution incorporated within the Work constitutes direct
85
- or contributory patent infringement, then any patent licenses
86
- granted to You under this License for that Work shall terminate
87
- as of the date such litigation is filed.
88
-
89
- 4. Redistribution. You may reproduce and distribute copies of the
90
- Work or Derivative Works thereof in any medium, with or without
91
- modifications, and in Source or Object form, provided that You
92
- meet the following conditions:
93
-
94
- (a) You must give any other recipients of the Work or
95
- Derivative Works a copy of this License; and
96
-
97
- (b) You must cause any modified files to carry prominent notices
98
- stating that You changed the files; and
99
-
100
- (c) You must retain, in the Source form of any Derivative Works
101
- that You distribute, all copyright, patent, trademark, and
102
- attribution notices from the Source form of the Work,
103
- excluding those notices that do not pertain to any part of
104
- the Derivative Works; and
105
-
106
- (d) If the Work includes a "NOTICE" text file as part of its
107
- distribution, then any Derivative Works that You distribute must
108
- include a readable copy of the attribution notices contained
109
- within such NOTICE file, excluding those notices that do not
110
- pertain to any part of the Derivative Works, in at least one
111
- of the following places: within a NOTICE text file distributed
112
- as part of the Derivative Works; within the Source form or
113
- documentation, if provided along with the Derivative Works; or,
114
- within a display generated by the Derivative Works, if and
115
- wherever such third-party notices normally appear. The contents
116
- of the NOTICE file are for informational purposes only and
117
- do not modify the License. You may add Your own attribution
118
- notices within Derivative Works that You distribute, alongside
119
- or as an addendum to the NOTICE text from the Work, provided
120
- that such additional attribution notices cannot be construed
121
- as modifying the License.
122
-
123
- You may add Your own copyright statement to Your modifications and
124
- may provide additional or different license terms and conditions
125
- for use, reproduction, or distribution of Your modifications, or
126
- for any such Derivative Works as a whole, provided Your use,
127
- reproduction, and distribution of the Work otherwise complies with
128
- the conditions stated in this License.
129
-
130
- 5. Submission of Contributions. Unless You explicitly state otherwise,
131
- any Contribution intentionally submitted for inclusion in the Work
132
- by You to the Licensor shall be under the terms and conditions of
133
- this License, without any additional terms or conditions.
134
- Notwithstanding the above, nothing herein shall supersede or modify
135
- the terms of any separate license agreement you may have executed
136
- with Licensor regarding such Contributions.
137
-
138
- 6. Trademarks. This License does not grant permission to use the trade
139
- names, trademarks, service marks, or product names of the Licensor,
140
- except as required for reasonable and customary use in describing the
141
- origin of the Work and reproducing the content of the NOTICE file.
142
-
143
- 7. Disclaimer of Warranty. Unless required by applicable law or
144
- agreed to in writing, Licensor provides the Work (and each
145
- Contributor provides its Contributions) on an "AS IS" BASIS,
146
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
- implied, including, without limitation, any warranties or conditions
148
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
- PARTICULAR PURPOSE. You are solely responsible for determining the
150
- appropriateness of using or redistributing the Work and assume any
151
- risks associated with Your exercise of permissions under this License.
152
-
153
- 8. Limitation of Liability. In no event and under no legal theory,
154
- whether in tort (including negligence), contract, or otherwise,
155
- unless required by applicable law (such as deliberate and grossly
156
- negligent acts) or agreed to in writing, shall any Contributor be
157
- liable to You for damages, including any direct, indirect, special,
158
- incidental, or consequential damages of any character arising as a
159
- result of this License or out of the use or inability to use the
160
- Work (including but not limited to damages for loss of goodwill,
161
- work stoppage, computer failure or malfunction, or any and all
162
- other commercial damages or losses), even if such Contributor
163
- has been advised of the possibility of such damages.
164
-
165
- 9. Accepting Warranty or Additional Liability. While redistributing
166
- the Work or Derivative Works thereof, You may choose to offer,
167
- and charge a fee for, acceptance of support, warranty, indemnity,
168
- or other liability obligations and/or rights consistent with this
169
- License. However, in accepting such obligations, You may act only
170
- on Your own behalf and on Your sole responsibility, not on behalf
171
- of any other Contributor, and only if You agree to indemnify,
172
- defend, and hold each Contributor harmless for any liability
173
- incurred by, or claims asserted against, such Contributor by reason
174
- of your accepting any such warranty or additional liability.
175
-
176
- END OF TERMS AND CONDITIONS
177
-
178
- APPENDIX: How to apply the Apache License to your work.
179
-
180
- To apply the Apache License to your work, attach the following
181
- boilerplate notice, with the fields enclosed by brackets "[]"
182
- replaced with your own identifying information. (Don't include
183
- the brackets!) The text should be enclosed in the appropriate
184
- comment syntax for the file format. We also recommend that a
185
- file or class name and description of purpose be included on the
186
- same "printed page" as the copyright notice for easier
187
- identification within third-party archives.
188
-
189
- Copyright 2025 Railtown AI
190
-
191
- Licensed under the Apache License, Version 2.0 (the "License");
192
- you may not use this file except in compliance with the License.
193
- You may obtain a copy of the License at
194
-
195
- http://www.apache.org/licenses/LICENSE-2.0
196
-
197
- Unless required by applicable law or agreed to in writing, software
198
- distributed under the License is distributed on an "AS IS" BASIS,
199
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
- See the License for the specific language governing permissions and
201
- limitations under the License.
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Railtown AI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # RailTracks Visualizer
2
2
 
3
+ [![Tests](https://github.com/RailtownAI/railtracks-visualizer/actions/workflows/ci.yaml/badge.svg)](https://github.com/RailtownAI/railtracks-visualizer/actions/workflows/ci.yaml)
4
+
3
5
  A React-based visualizer for RailTracks agentic flows
4
6
 
5
7
  ## 🚀 Quick Start
@@ -40,15 +42,11 @@ A React-based visualizer for RailTracks agentic flows
40
42
  ## 🎨 Features
41
43
 
42
44
  - **Interactive Flow Visualization**: Visualize agentic flows with nodes and edges
43
- - **Timeline Navigation**: Step through the flow execution timeline
44
45
  - **Auto-layout**: Automatic positioning of nodes in a tree structure
45
- - **Responsive Design**: Works on different screen sizes
46
46
  - **Inspection Panel**: Detailed view of nodes and edges
47
- - **Playback Controls**: Play/pause timeline progression
48
47
  - **Portable Styling**: Uses emotion for self-contained, portable styles
49
48
  - **Graceful Theme Handling**: Works out of the box with default light theme, no ThemeProvider required
50
49
  - **Dark Mode Support**: Full dark mode compatibility with automatic theme detection
51
- - **Icon Integration**: Support for OpenAI, Anthropic, and Google AI provider icons
52
50
 
53
51
  ### Basic Usage (No ThemeProvider Required)
54
52
 
@@ -59,17 +57,6 @@ import { Visualizer } from "@railtownai/railtracks-visualizer";
59
57
  <Visualizer flowData={data} />;
60
58
  ```
61
59
 
62
- ### Custom Theming (Optional)
63
-
64
- ```tsx
65
- import { Visualizer, ThemeProvider, darkTheme } from "@railtownai/railtracks-visualizer";
66
-
67
- // Wrap with ThemeProvider for custom themes
68
- <ThemeProvider theme={darkTheme}>
69
- <Visualizer flowData={data} />
70
- </ThemeProvider>;
71
- ```
72
-
73
60
  ## 🔧 Development
74
61
 
75
62
  1. `npm install`
package/dist/cjs/index.js CHANGED
@@ -16351,6 +16351,8 @@ const InputItemTextarea = styled(index$1)`
16351
16351
  `;
16352
16352
 
16353
16353
  const DEFAULT_COUNTUP_DURATION = 0.6;
16354
+ const DEFAULT_POPOVER_WIDTH = 350;
16355
+ const DEFAULT_POPOVER_HEIGHT = 500;
16354
16356
  // Helper component for animating currency values
16355
16357
  const CountUpCurrency = ({ value, prefix = "", suffix = "" })=>{
16356
16358
  return /*#__PURE__*/ React.createElement(CountUp, {
@@ -16453,8 +16455,8 @@ const NodeDetailsPopover = ({ isVisible, onClose, nodeData, triggerRef })=>{
16453
16455
  const rect = triggerRef.current.getBoundingClientRect();
16454
16456
  const viewportWidth = window.innerWidth;
16455
16457
  const viewportHeight = window.innerHeight;
16456
- const popoverWidth = 400; // Match the width from styled component
16457
- const popoverHeight = 600; // Approximate max height
16458
+ const popoverWidth = DEFAULT_POPOVER_WIDTH; // Match the width from styled component
16459
+ const popoverHeight = DEFAULT_POPOVER_HEIGHT; // Approximate max height
16458
16460
  const gap = 12; // Gap between node and popover
16459
16461
  // Check if the rect is valid (has non-zero dimensions and is in viewport)
16460
16462
  const isValidRect = rect.width > 0 && rect.height > 0 && rect.top >= 0 && rect.left >= 0;
@@ -16702,8 +16704,8 @@ const PopoverPortal = styled.div`
16702
16704
  pointer-events: none;
16703
16705
  `;
16704
16706
  const PopoverContent = styled.div`
16705
- width: 400px;
16706
- max-height: 600px;
16707
+ width: ${DEFAULT_POPOVER_WIDTH}px;
16708
+ max-height: ${DEFAULT_POPOVER_HEIGHT}px;
16707
16709
  background-color: ${(props)=>props.theme?.colors?.background || "hsl(0 0% 100%)"};
16708
16710
  border: 1px solid ${(props)=>props.theme?.colors?.border || "hsl(214.3 31.8% 91.4%)"};
16709
16711
  border-radius: 0.5rem;
@@ -20149,7 +20151,6 @@ SheetDescription.displayName = Description.displayName;
20149
20151
  }
20150
20152
  // Lay out each tree, centered
20151
20153
  // Calculate the starting position to center the entire layout in the container
20152
- // Ensure we don't have negative startX which would cause rightward offset
20153
20154
  const startX = Math.max(0, (containerWidth - totalWidth) / 2);
20154
20155
  let xCursor = startX;
20155
20156
  for (const root of roots){
@@ -20195,16 +20196,28 @@ const nodeTypes = {
20195
20196
  Tool: Node$1,
20196
20197
  Coordinator: Node$1
20197
20198
  };
20198
- const AgenticFlowVisualizer = ({ flowData: propFlowData, width = "100%", height = "1000px", className = "", defaultZoom = 1, defaultPan = {
20199
+ const AgenticFlowVisualizer = ({ flowData: propFlowData, width = "100dvw", height = "100dvh", className = "", defaultZoom = 1, defaultPan = {
20199
20200
  x: 0,
20200
20201
  y: 0
20201
- }, disableAutoFit = true, showTimeline = false, minNodeSpacing = 400, onInspect })=>{
20202
+ }, disableAutoFit = true, defaultAutoFitDuration = 1000, defaultAutoFitDelay = 250, showTimeline = false, minNodeSpacing = 300, onInspect })=>{
20202
20203
  const { theme, isDarkMode } = useTheme();
20203
20204
  const themeColors = theme.colors;
20205
+ // Helper function to determine if we should apply minimum dimensions
20206
+ const shouldApplyMinDimensions = React.useCallback(()=>{
20207
+ const widthStr = typeof width === "string" ? width : `${width}px`;
20208
+ const heightStr = typeof height === "string" ? height : `${height}px`;
20209
+ // Don't apply minimum dimensions if using viewport units
20210
+ const hasViewportUnits = /\b\d*\.?\d+(vw|vh|vmin|vmax)\b/.test(widthStr) || /\b\d*\.?\d+(vw|vh|vmin|vmax)\b/.test(heightStr);
20211
+ return !hasViewportUnits;
20212
+ }, [
20213
+ width,
20214
+ height
20215
+ ]);
20204
20216
  // Use prop data directly, no hooks needed
20205
20217
  const flowData = propFlowData;
20206
20218
  // Show no data state if no flowData is available
20207
20219
  if (!flowData) {
20220
+ const applyMinDimensions = shouldApplyMinDimensions();
20208
20221
  return /*#__PURE__*/ React.createElement("div", {
20209
20222
  style: {
20210
20223
  width: typeof width === "number" ? `${width}px` : width,
@@ -20213,8 +20226,10 @@ const AgenticFlowVisualizer = ({ flowData: propFlowData, width = "100%", height
20213
20226
  borderRadius: "8px",
20214
20227
  background: themeColors.muted,
20215
20228
  position: "relative",
20216
- minWidth: "800px",
20217
- minHeight: "600px",
20229
+ ...applyMinDimensions && {
20230
+ minWidth: "800px",
20231
+ minHeight: "600px"
20232
+ },
20218
20233
  display: "flex",
20219
20234
  alignItems: "center",
20220
20235
  justifyContent: "center"
@@ -20324,9 +20339,8 @@ const AgenticFlowVisualizer = ({ flowData: propFlowData, width = "100%", height
20324
20339
  if (!flowData.nodes) {
20325
20340
  return new Map();
20326
20341
  }
20327
- // Use container dimensions for centering, fallback to reasonable defaults
20328
- // Ensure we have a reasonable minimum width for proper centering
20329
- const containerWidth = Math.max(containerDimensions.width || 800, 1200);
20342
+ // Use actual container dimensions for proper centering
20343
+ const containerWidth = containerDimensions.width || 800;
20330
20344
  return calculateAutoLayout(flowData.nodes, flowData.edges || [], {
20331
20345
  minNodeSpacing,
20332
20346
  containerWidth
@@ -20351,6 +20365,22 @@ const AgenticFlowVisualizer = ({ flowData: propFlowData, width = "100%", height
20351
20365
  }, [
20352
20366
  flowData.edges
20353
20367
  ]);
20368
+ const reactFlowInstance = useReactFlow();
20369
+ // Pan function that can be called by child components
20370
+ React.useCallback((direction, amount)=>{
20371
+ if (!reactFlowInstance) return;
20372
+ const currentViewport = reactFlowInstance.getViewport();
20373
+ const panAmount = direction === "right" ? amount : -amount;
20374
+ reactFlowInstance.setViewport({
20375
+ x: currentViewport.x - panAmount,
20376
+ y: currentViewport.y,
20377
+ zoom: currentViewport.zoom
20378
+ }, {
20379
+ duration: 300
20380
+ });
20381
+ }, [
20382
+ reactFlowInstance
20383
+ ]);
20354
20384
  // Convert flow data to ReactFlow format with step filtering
20355
20385
  const nodes = React.useMemo(()=>{
20356
20386
  const stepNodes = getNodesForStep(currentStep);
@@ -20461,7 +20491,6 @@ const AgenticFlowVisualizer = ({ flowData: propFlowData, width = "100%", height
20461
20491
  y: svgP.y
20462
20492
  };
20463
20493
  };
20464
- const reactFlowInstance = useReactFlow();
20465
20494
  // Pan to hub node (most connected node) on initial load, unless disabled or custom settings provided
20466
20495
  React.useEffect(()=>{
20467
20496
  if (disableAutoFit || !reactFlowInstance || nodes.length === 0) {
@@ -20500,31 +20529,34 @@ const AgenticFlowVisualizer = ({ flowData: propFlowData, width = "100%", height
20500
20529
  id: hubNodeId
20501
20530
  }
20502
20531
  ],
20503
- duration: 1000,
20532
+ duration: defaultAutoFitDuration,
20504
20533
  padding: 0.2,
20505
20534
  minZoom: defaultZoom,
20506
- maxZoom: 1
20535
+ maxZoom: defaultZoom
20507
20536
  });
20508
- }, 500); // Small delay to ensure nodes are rendered
20537
+ }, defaultAutoFitDelay); // Small delay to ensure nodes are rendered
20509
20538
  }
20510
20539
  }, [
20511
20540
  nodes,
20512
20541
  edges,
20513
20542
  reactFlowInstance,
20514
20543
  disableAutoFit,
20515
- defaultZoom
20544
+ defaultZoom,
20545
+ defaultAutoFitDuration,
20546
+ defaultAutoFitDelay
20516
20547
  ]);
20517
20548
  return /*#__PURE__*/ React.createElement("div", {
20518
20549
  ref: containerRef,
20519
20550
  style: {
20520
20551
  width: typeof width === "number" ? `${width}px` : width,
20521
20552
  height: typeof height === "number" ? `${height}px` : height,
20553
+ minWidth: "800px",
20554
+ minHeight: "600px",
20555
+ boxSizing: "border-box",
20522
20556
  border: `1px solid ${themeColors.border}`,
20523
20557
  borderRadius: "8px",
20524
20558
  overflow: "hidden",
20525
20559
  position: "relative",
20526
- minWidth: "800px",
20527
- minHeight: "600px",
20528
20560
  background: themeColors.background
20529
20561
  },
20530
20562
  className: className
package/dist/esm/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import React__default, { memo, useState, useCallback, useMemo, forwardRef, createContext, useContext, useRef, useLayoutEffect, useEffect, createElement } from 'react';
2
+ import React__default, { memo, useMemo, useState, useCallback, forwardRef, createContext, useContext, useRef, useLayoutEffect, useEffect, createElement } from 'react';
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
4
  import * as ReactDOM from 'react-dom';
5
5
  import ReactDOM__default, { createPortal } from 'react-dom';
@@ -16331,6 +16331,8 @@ const InputItemTextarea = styled(index$1)`
16331
16331
  `;
16332
16332
 
16333
16333
  const DEFAULT_COUNTUP_DURATION = 0.6;
16334
+ const DEFAULT_POPOVER_WIDTH = 350;
16335
+ const DEFAULT_POPOVER_HEIGHT = 500;
16334
16336
  // Helper component for animating currency values
16335
16337
  const CountUpCurrency = ({ value, prefix = "", suffix = "" })=>{
16336
16338
  return /*#__PURE__*/ React__default.createElement(CountUp, {
@@ -16433,8 +16435,8 @@ const NodeDetailsPopover = ({ isVisible, onClose, nodeData, triggerRef })=>{
16433
16435
  const rect = triggerRef.current.getBoundingClientRect();
16434
16436
  const viewportWidth = window.innerWidth;
16435
16437
  const viewportHeight = window.innerHeight;
16436
- const popoverWidth = 400; // Match the width from styled component
16437
- const popoverHeight = 600; // Approximate max height
16438
+ const popoverWidth = DEFAULT_POPOVER_WIDTH; // Match the width from styled component
16439
+ const popoverHeight = DEFAULT_POPOVER_HEIGHT; // Approximate max height
16438
16440
  const gap = 12; // Gap between node and popover
16439
16441
  // Check if the rect is valid (has non-zero dimensions and is in viewport)
16440
16442
  const isValidRect = rect.width > 0 && rect.height > 0 && rect.top >= 0 && rect.left >= 0;
@@ -16682,8 +16684,8 @@ const PopoverPortal = styled.div`
16682
16684
  pointer-events: none;
16683
16685
  `;
16684
16686
  const PopoverContent = styled.div`
16685
- width: 400px;
16686
- max-height: 600px;
16687
+ width: ${DEFAULT_POPOVER_WIDTH}px;
16688
+ max-height: ${DEFAULT_POPOVER_HEIGHT}px;
16687
16689
  background-color: ${(props)=>props.theme?.colors?.background || "hsl(0 0% 100%)"};
16688
16690
  border: 1px solid ${(props)=>props.theme?.colors?.border || "hsl(214.3 31.8% 91.4%)"};
16689
16691
  border-radius: 0.5rem;
@@ -20129,7 +20131,6 @@ SheetDescription.displayName = Description.displayName;
20129
20131
  }
20130
20132
  // Lay out each tree, centered
20131
20133
  // Calculate the starting position to center the entire layout in the container
20132
- // Ensure we don't have negative startX which would cause rightward offset
20133
20134
  const startX = Math.max(0, (containerWidth - totalWidth) / 2);
20134
20135
  let xCursor = startX;
20135
20136
  for (const root of roots){
@@ -20175,16 +20176,28 @@ const nodeTypes = {
20175
20176
  Tool: Node$1,
20176
20177
  Coordinator: Node$1
20177
20178
  };
20178
- const AgenticFlowVisualizer = ({ flowData: propFlowData, width = "100%", height = "1000px", className = "", defaultZoom = 1, defaultPan = {
20179
+ const AgenticFlowVisualizer = ({ flowData: propFlowData, width = "100dvw", height = "100dvh", className = "", defaultZoom = 1, defaultPan = {
20179
20180
  x: 0,
20180
20181
  y: 0
20181
- }, disableAutoFit = true, showTimeline = false, minNodeSpacing = 400, onInspect })=>{
20182
+ }, disableAutoFit = true, defaultAutoFitDuration = 1000, defaultAutoFitDelay = 250, showTimeline = false, minNodeSpacing = 300, onInspect })=>{
20182
20183
  const { theme, isDarkMode } = useTheme();
20183
20184
  const themeColors = theme.colors;
20185
+ // Helper function to determine if we should apply minimum dimensions
20186
+ const shouldApplyMinDimensions = useCallback(()=>{
20187
+ const widthStr = typeof width === "string" ? width : `${width}px`;
20188
+ const heightStr = typeof height === "string" ? height : `${height}px`;
20189
+ // Don't apply minimum dimensions if using viewport units
20190
+ const hasViewportUnits = /\b\d*\.?\d+(vw|vh|vmin|vmax)\b/.test(widthStr) || /\b\d*\.?\d+(vw|vh|vmin|vmax)\b/.test(heightStr);
20191
+ return !hasViewportUnits;
20192
+ }, [
20193
+ width,
20194
+ height
20195
+ ]);
20184
20196
  // Use prop data directly, no hooks needed
20185
20197
  const flowData = propFlowData;
20186
20198
  // Show no data state if no flowData is available
20187
20199
  if (!flowData) {
20200
+ const applyMinDimensions = shouldApplyMinDimensions();
20188
20201
  return /*#__PURE__*/ React__default.createElement("div", {
20189
20202
  style: {
20190
20203
  width: typeof width === "number" ? `${width}px` : width,
@@ -20193,8 +20206,10 @@ const AgenticFlowVisualizer = ({ flowData: propFlowData, width = "100%", height
20193
20206
  borderRadius: "8px",
20194
20207
  background: themeColors.muted,
20195
20208
  position: "relative",
20196
- minWidth: "800px",
20197
- minHeight: "600px",
20209
+ ...applyMinDimensions && {
20210
+ minWidth: "800px",
20211
+ minHeight: "600px"
20212
+ },
20198
20213
  display: "flex",
20199
20214
  alignItems: "center",
20200
20215
  justifyContent: "center"
@@ -20304,9 +20319,8 @@ const AgenticFlowVisualizer = ({ flowData: propFlowData, width = "100%", height
20304
20319
  if (!flowData.nodes) {
20305
20320
  return new Map();
20306
20321
  }
20307
- // Use container dimensions for centering, fallback to reasonable defaults
20308
- // Ensure we have a reasonable minimum width for proper centering
20309
- const containerWidth = Math.max(containerDimensions.width || 800, 1200);
20322
+ // Use actual container dimensions for proper centering
20323
+ const containerWidth = containerDimensions.width || 800;
20310
20324
  return calculateAutoLayout(flowData.nodes, flowData.edges || [], {
20311
20325
  minNodeSpacing,
20312
20326
  containerWidth
@@ -20331,6 +20345,22 @@ const AgenticFlowVisualizer = ({ flowData: propFlowData, width = "100%", height
20331
20345
  }, [
20332
20346
  flowData.edges
20333
20347
  ]);
20348
+ const reactFlowInstance = useReactFlow();
20349
+ // Pan function that can be called by child components
20350
+ useCallback((direction, amount)=>{
20351
+ if (!reactFlowInstance) return;
20352
+ const currentViewport = reactFlowInstance.getViewport();
20353
+ const panAmount = direction === "right" ? amount : -amount;
20354
+ reactFlowInstance.setViewport({
20355
+ x: currentViewport.x - panAmount,
20356
+ y: currentViewport.y,
20357
+ zoom: currentViewport.zoom
20358
+ }, {
20359
+ duration: 300
20360
+ });
20361
+ }, [
20362
+ reactFlowInstance
20363
+ ]);
20334
20364
  // Convert flow data to ReactFlow format with step filtering
20335
20365
  const nodes = useMemo(()=>{
20336
20366
  const stepNodes = getNodesForStep(currentStep);
@@ -20441,7 +20471,6 @@ const AgenticFlowVisualizer = ({ flowData: propFlowData, width = "100%", height
20441
20471
  y: svgP.y
20442
20472
  };
20443
20473
  };
20444
- const reactFlowInstance = useReactFlow();
20445
20474
  // Pan to hub node (most connected node) on initial load, unless disabled or custom settings provided
20446
20475
  useEffect(()=>{
20447
20476
  if (disableAutoFit || !reactFlowInstance || nodes.length === 0) {
@@ -20480,31 +20509,34 @@ const AgenticFlowVisualizer = ({ flowData: propFlowData, width = "100%", height
20480
20509
  id: hubNodeId
20481
20510
  }
20482
20511
  ],
20483
- duration: 1000,
20512
+ duration: defaultAutoFitDuration,
20484
20513
  padding: 0.2,
20485
20514
  minZoom: defaultZoom,
20486
- maxZoom: 1
20515
+ maxZoom: defaultZoom
20487
20516
  });
20488
- }, 500); // Small delay to ensure nodes are rendered
20517
+ }, defaultAutoFitDelay); // Small delay to ensure nodes are rendered
20489
20518
  }
20490
20519
  }, [
20491
20520
  nodes,
20492
20521
  edges,
20493
20522
  reactFlowInstance,
20494
20523
  disableAutoFit,
20495
- defaultZoom
20524
+ defaultZoom,
20525
+ defaultAutoFitDuration,
20526
+ defaultAutoFitDelay
20496
20527
  ]);
20497
20528
  return /*#__PURE__*/ React__default.createElement("div", {
20498
20529
  ref: containerRef,
20499
20530
  style: {
20500
20531
  width: typeof width === "number" ? `${width}px` : width,
20501
20532
  height: typeof height === "number" ? `${height}px` : height,
20533
+ minWidth: "800px",
20534
+ minHeight: "600px",
20535
+ boxSizing: "border-box",
20502
20536
  border: `1px solid ${themeColors.border}`,
20503
20537
  borderRadius: "8px",
20504
20538
  overflow: "hidden",
20505
20539
  position: "relative",
20506
- minWidth: "800px",
20507
- minHeight: "600px",
20508
20540
  background: themeColors.background
20509
20541
  },
20510
20542
  className: className
@@ -12,6 +12,8 @@ export interface AgenticFlowVisualizerProps {
12
12
  y: number;
13
13
  };
14
14
  disableAutoFit?: boolean;
15
+ defaultAutoFitDuration?: number;
16
+ defaultAutoFitDelay?: number;
15
17
  showTimeline?: boolean;
16
18
  minNodeSpacing?: number;
17
19
  onInspect?: (nodeData: any) => void;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@railtownai/railtracks-visualizer",
3
- "version": "0.0.28",
4
- "license": "Apache-2.0",
3
+ "version": "0.0.30",
4
+ "license": "MIT",
5
5
  "author": "Railtown AI",
6
6
  "description": "A visualizer for RailTracks agentic flows",
7
7
  "main": "dist/cjs/index.js",
@@ -53,18 +53,19 @@
53
53
  },
54
54
  "scripts": {
55
55
  "build:lib": "rollup -c",
56
+ "build:size": "npm pack --dry-run",
56
57
  "build:spa": "vite build",
57
58
  "build:types": "tsc -p tsconfig.types.json",
58
- "build:size": "npm pack --dry-run",
59
59
  "build": "npm run build:types && npm run build:lib && npm run build:spa",
60
60
  "clean": "rm -rf dist build test-results .railtracks/ui/*",
61
61
  "dev": "rm -rf .railtracks/ui/* && vite build && cp -r build/* .railtracks/ui",
62
62
  "lint:fix": "prettier --write \"src/**/*.{js,jsx,ts,tsx,json,css,md}\"",
63
+ "lint:license": "license-checker --summary --excludePrivatePackages --failOn 'AGPL-1.0;AGPL-3.0;EPL-1.0;EPL-2.0;GPL-1.0;GPL-2.0;GPL-3.0;LGPL-2.0;LGPL-2.1;LGPL-3.0;'",
63
64
  "lint": "prettier --check \"src/**/*.{js,jsx,ts,tsx,json,css,md}\"",
64
65
  "start": "storybook dev -p 6006",
65
66
  "storybook:build": "cross-env NODE_ENV=production storybook build",
66
- "storybook:preview": "npx http-server ./storybook-static",
67
67
  "storybook:deploy": "gh-pages -d storybook-static",
68
+ "storybook:preview": "npx http-server ./storybook-static",
68
69
  "test:ui": "vitest --ui",
69
70
  "test": "vitest run --silent",
70
71
  "up": "ncu -u -x react -x react-dom -x @types/react -x @types/react-dom && npm install"
@@ -95,6 +96,7 @@
95
96
  "cross-env": "^10.0.0",
96
97
  "gh-pages": "6.3.0",
97
98
  "jsdom": "26.1.0",
99
+ "license-checker": "25.0.1",
98
100
  "npm-check-updates": "18.1.0",
99
101
  "playwright": "^1.55.0",
100
102
  "prettier": "3.6.2",