@aiready/visualizer 0.1.13 → 0.1.18

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.
@@ -0,0 +1,99 @@
1
+ import { ThemeColors } from './types';
2
+
3
+ export const severityColors: Record<string, string> = {
4
+ critical: '#ff4d4f',
5
+ major: '#ff9900',
6
+ minor: '#ffd666',
7
+ info: '#91d5ff',
8
+ default: '#97c2fc',
9
+ };
10
+
11
+ export const edgeColors: Record<string, string> = {
12
+ similarity: '#fb7e81',
13
+ dependency: '#84c1ff',
14
+ reference: '#ffa500',
15
+ related: '#6b7280',
16
+ default: '#848484',
17
+ };
18
+
19
+ // Ensure GRAPH_CONFIG.edgeDistances has all edge types
20
+ const EDGE_DISTANCES = {
21
+ similarity: 80,
22
+ related: 150,
23
+ dependency: 100,
24
+ reference: 120,
25
+ default: 100,
26
+ };
27
+
28
+ const EDGE_STRENGTHS = {
29
+ similarity: 0.5,
30
+ related: 0.1,
31
+ dependency: 0.3,
32
+ reference: 0.2,
33
+ default: 0.3,
34
+ };
35
+
36
+ const EDGE_OPACITIES = {
37
+ similarity: 0.8,
38
+ related: 0.2,
39
+ dependency: 0.5,
40
+ reference: 0.4,
41
+ default: 0.5,
42
+ };
43
+
44
+ export const themeConfig: Record<'dark' | 'light', ThemeColors> = {
45
+ dark: {
46
+ bg: '#000000',
47
+ text: '#e2e8f0',
48
+ textMuted: '#94a3b8',
49
+ panel: '#020617',
50
+ panelBorder: '#1e293b',
51
+ cardBg: '#0f172a',
52
+ cardBorder: '#1e293b',
53
+ grid: 'rgba(30,41,59,0.3)',
54
+ },
55
+ light: {
56
+ bg: '#ffffff',
57
+ text: '#1e293b',
58
+ textMuted: '#64748b',
59
+ panel: '#ffffff',
60
+ panelBorder: '#e2e8f0',
61
+ cardBg: '#f8fafc',
62
+ cardBorder: '#e2e8f0',
63
+ grid: 'rgba(203,213,225,0.4)',
64
+ },
65
+ };
66
+
67
+ export const GRAPH_CONFIG = {
68
+ maxNodes: 200,
69
+ maxEdges: 300,
70
+ nodeBaseRadius: 3,
71
+ collisionRadius: 25,
72
+ zoomMin: 0.1,
73
+ zoomMax: 4,
74
+ edgeDistances: {
75
+ similarity: 80,
76
+ related: 150,
77
+ dependency: 100,
78
+ reference: 120,
79
+ default: 100,
80
+ },
81
+ edgeStrengths: {
82
+ similarity: 0.5,
83
+ related: 0.1,
84
+ dependency: 0.3,
85
+ reference: 0.2,
86
+ default: 0.3,
87
+ },
88
+ edgeOpacities: {
89
+ similarity: 0.8,
90
+ related: 0.2,
91
+ dependency: 0.5,
92
+ reference: 0.4,
93
+ default: 0.5,
94
+ },
95
+ simulation: {
96
+ chargeStrength: -200,
97
+ centerStrength: 0.1,
98
+ },
99
+ };
@@ -0,0 +1,4 @@
1
+ declare module '*.css' {
2
+ const content: string;
3
+ export default content;
4
+ }
@@ -0,0 +1,28 @@
1
+ import { useEffect, useState, useRef } from 'react';
2
+
3
+ export function useDimensions() {
4
+ const containerRef = useRef<HTMLDivElement>(null);
5
+ const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
6
+
7
+ useEffect(() => {
8
+ if (!containerRef.current) return;
9
+
10
+ const updateDimensions = () => {
11
+ if (containerRef.current) {
12
+ const rect = containerRef.current.getBoundingClientRect();
13
+ setDimensions({ width: rect.width, height: rect.height });
14
+ }
15
+ };
16
+
17
+ updateDimensions();
18
+
19
+ const resizeObserver = new ResizeObserver(updateDimensions);
20
+ resizeObserver.observe(containerRef.current);
21
+
22
+ return () => {
23
+ resizeObserver.disconnect();
24
+ };
25
+ }, []);
26
+
27
+ return { containerRef, dimensions };
28
+ }
@@ -0,0 +1,27 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { Theme, EffectiveTheme } from '../types';
3
+
4
+ export function useTheme() {
5
+ const getSystemTheme = (): EffectiveTheme => {
6
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
7
+ };
8
+
9
+ const [theme, setTheme] = useState<Theme>('system');
10
+ const [systemTheme, setSystemTheme] = useState<EffectiveTheme>(getSystemTheme());
11
+
12
+ useEffect(() => {
13
+ const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
14
+ setSystemTheme(mediaQuery.matches ? 'dark' : 'light');
15
+
16
+ const handler = (e: MediaQueryListEvent) => {
17
+ setSystemTheme(e.matches ? 'dark' : 'light');
18
+ };
19
+
20
+ mediaQuery.addEventListener('change', handler);
21
+ return () => mediaQuery.removeEventListener('change', handler);
22
+ }, []);
23
+
24
+ const effectiveTheme = theme === 'system' ? systemTheme : theme;
25
+
26
+ return { theme, setTheme, effectiveTheme };
27
+ }
@@ -0,0 +1,11 @@
1
+ import { StrictMode } from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+ import App from './App';
4
+ import './styles/index.css';
5
+
6
+ const rootEl = document.getElementById('root')!;
7
+ createRoot(rootEl).render(
8
+ <StrictMode>
9
+ <App />
10
+ </StrictMode>
11
+ );
@@ -0,0 +1,2 @@
1
+ html,body,#app{height:100%;margin:0}
2
+ #app{font-family:Inter,system-ui,Segoe UI,Roboto,sans-serif}
@@ -0,0 +1,42 @@
1
+ @import "tailwindcss";
2
+
3
+ /* Base styles */
4
+ * {
5
+ box-sizing: border-box;
6
+ margin: 0;
7
+ padding: 0;
8
+ }
9
+
10
+ html, body, #root {
11
+ height: 100%;
12
+ width: 100%;
13
+ overflow: hidden;
14
+ }
15
+
16
+ body {
17
+ margin: 0;
18
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
19
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
20
+ sans-serif;
21
+ -webkit-font-smoothing: antialiased;
22
+ -moz-osx-font-smoothing: grayscale;
23
+ }
24
+
25
+ #root {
26
+ width: 100%;
27
+ height: 100vh;
28
+ display: flex;
29
+ flex-direction: column;
30
+ }
31
+
32
+ /* Smooth fade-in animation for panel transitions */
33
+ @keyframes fadeIn {
34
+ from {
35
+ opacity: 0;
36
+ transform: translateY(-4px);
37
+ }
38
+ to {
39
+ opacity: 1;
40
+ transform: translateY(0);
41
+ }
42
+ }
@@ -0,0 +1,78 @@
1
+ // Types for the visualization
2
+
3
+ export interface FileNode {
4
+ id: string;
5
+ label: string;
6
+ value: number;
7
+ color: string;
8
+ title: string;
9
+ duplicates?: number;
10
+ tokenCost?: number;
11
+ severity?: string;
12
+ }
13
+
14
+ export interface GraphEdge {
15
+ source: string;
16
+ target: string;
17
+ type: string;
18
+ }
19
+
20
+ export interface GraphData {
21
+ nodes: FileNode[];
22
+ edges: GraphEdge[];
23
+ }
24
+
25
+ // Filter types
26
+ export type SeverityLevel = 'critical' | 'major' | 'minor' | 'info';
27
+ export type EdgeType = 'similarity' | 'dependency' | 'reference' | 'related';
28
+
29
+ export interface FilterState {
30
+ visibleSeverities: Set<SeverityLevel>;
31
+ visibleEdgeTypes: Set<EdgeType>;
32
+ }
33
+
34
+ export interface PatternIssue {
35
+ fileName: string;
36
+ issues: Array<{ type: string; severity: string; message: string }>;
37
+ metrics: { tokenCost: number; consistencyScore: number };
38
+ }
39
+
40
+ export interface Duplicate {
41
+ file1: string;
42
+ file2: string;
43
+ severity: string;
44
+ patternType: string;
45
+ }
46
+
47
+ export interface ContextFile {
48
+ file: string;
49
+ tokenCost: number;
50
+ linesOfCode: number;
51
+ dependencyCount: number;
52
+ dependencyList: string[];
53
+ relatedFiles: string[];
54
+ severity: string;
55
+ issues: string[];
56
+ }
57
+
58
+ export interface ReportData {
59
+ patterns: PatternIssue[];
60
+ duplicates: Duplicate[];
61
+ context: ContextFile[];
62
+ summary: { totalIssues: number };
63
+ }
64
+
65
+ export type Theme = 'dark' | 'light' | 'system';
66
+
67
+ export interface ThemeColors {
68
+ bg: string;
69
+ text: string;
70
+ textMuted: string;
71
+ panel: string;
72
+ panelBorder: string;
73
+ cardBg: string;
74
+ cardBorder: string;
75
+ grid: string;
76
+ }
77
+
78
+ export type EffectiveTheme = 'dark' | 'light';
@@ -0,0 +1,184 @@
1
+ import { FileNode, GraphEdge, GraphData, ReportData } from './types';
2
+ import { severityColors, GRAPH_CONFIG } from './constants';
3
+
4
+ export function getSeverityColor(severity: string | undefined): string {
5
+ if (!severity) return severityColors.default;
6
+ const s = severity.toLowerCase();
7
+ if (s === 'critical') return severityColors.critical;
8
+ if (s === 'major') return severityColors.major;
9
+ if (s === 'minor') return severityColors.minor;
10
+ if (s === 'info') return severityColors.info;
11
+ return severityColors.default;
12
+ }
13
+
14
+ export function transformReportToGraph(report: ReportData): GraphData {
15
+ const nodes: FileNode[] = [];
16
+ const edges: GraphEdge[] = [];
17
+ const nodeMap = new Map<string, FileNode>();
18
+
19
+ const fileIssues = new Map<string, { count: number; severities: Set<string>; maxSeverity: string }>();
20
+
21
+ for (const pattern of report.patterns) {
22
+ const issueCount = pattern.issues?.length || 0;
23
+ if (issueCount > 0) {
24
+ let maxSeverity = 'info';
25
+ const severityPriority: Record<string, number> = { critical: 4, major: 3, minor: 2, info: 1 };
26
+ for (const issue of pattern.issues) {
27
+ if ((severityPriority[issue.severity] || 0) > (severityPriority[maxSeverity] || 0)) {
28
+ maxSeverity = issue.severity;
29
+ }
30
+ }
31
+ fileIssues.set(pattern.fileName, { count: issueCount, severities: new Set(), maxSeverity });
32
+ }
33
+ }
34
+
35
+ for (const ctx of report.context) {
36
+ const issues = fileIssues.get(ctx.file);
37
+ const severity = issues?.maxSeverity || ctx.severity || 'default';
38
+ const tokenCost = ctx.tokenCost || 0;
39
+
40
+ const titleLines = [
41
+ `Token Cost: ${tokenCost}`,
42
+ `Lines of Code: ${ctx.linesOfCode}`,
43
+ `Dependencies: ${ctx.dependencyCount}`,
44
+ ];
45
+
46
+ if (issues) {
47
+ titleLines.push(`Issues: ${issues.count}`);
48
+ titleLines.push(`Severity: ${issues.maxSeverity}`);
49
+ }
50
+
51
+ if (ctx.issues && ctx.issues.length > 0) {
52
+ titleLines.push('', ...ctx.issues.slice(0, 3));
53
+ }
54
+
55
+ const node: FileNode = {
56
+ id: ctx.file,
57
+ label: ctx.file.split('/').pop() || ctx.file,
58
+ value: Math.max(10, Math.sqrt(tokenCost) * 3 + (issues?.count || 0) * 10),
59
+ color: getSeverityColor(severity),
60
+ title: titleLines.join('\n'),
61
+ duplicates: issues?.count,
62
+ tokenCost,
63
+ severity,
64
+ };
65
+
66
+ nodes.push(node);
67
+ nodeMap.set(ctx.file, node);
68
+ }
69
+
70
+ for (const ctx of report.context) {
71
+ const sourceDir = ctx.file.substring(0, ctx.file.lastIndexOf('/'));
72
+ for (const dep of ctx.dependencyList || []) {
73
+ if (dep.startsWith('.') || dep.startsWith('/')) {
74
+ // Try multiple matching strategies
75
+ let targetFile: string | undefined;
76
+
77
+ // Strategy 1: Direct resolve from source file's directory
78
+ const normalizedDep = dep.replace(/^\.\/?/, '');
79
+ const possiblePaths = [
80
+ // With extensions
81
+ `${sourceDir}/${normalizedDep}.ts`,
82
+ `${sourceDir}/${normalizedDep}.tsx`,
83
+ `${sourceDir}/${normalizedDep}/index.ts`,
84
+ `${sourceDir}/${normalizedDep}/index.tsx`,
85
+ // Just the path
86
+ `${sourceDir}/${normalizedDep}`,
87
+ ];
88
+
89
+ for (const p of possiblePaths) {
90
+ if (nodeMap.has(p)) {
91
+ targetFile = p;
92
+ break;
93
+ }
94
+ }
95
+
96
+ // Strategy 2: Fall back to loose endsWith matching
97
+ if (!targetFile) {
98
+ const depBase = normalizedDep.split('/').pop() || normalizedDep;
99
+ targetFile = [...nodeMap.keys()].find(k =>
100
+ k.endsWith(`/${depBase}.ts`) || k.endsWith(`/${depBase}.tsx`) ||
101
+ k.endsWith(`/${depBase}/index.ts`) || k.endsWith(`/${depBase}/index.tsx`)
102
+ );
103
+ }
104
+
105
+ if (targetFile && targetFile !== ctx.file) {
106
+ edges.push({ source: ctx.file, target: targetFile, type: 'dependency' });
107
+ }
108
+ }
109
+ }
110
+
111
+ for (const related of ctx.relatedFiles || []) {
112
+ if (nodeMap.has(related) && related !== ctx.file) {
113
+ const exists = edges.some(
114
+ e =>
115
+ (e.source === ctx.file && e.target === related) ||
116
+ (e.source === related && e.target === ctx.file)
117
+ );
118
+ if (!exists) edges.push({ source: ctx.file, target: related, type: 'related' });
119
+ }
120
+ }
121
+ }
122
+
123
+ for (const dup of report.duplicates || []) {
124
+ if (nodeMap.has(dup.file1) && nodeMap.has(dup.file2)) {
125
+ const exists = edges.some(
126
+ e =>
127
+ (e.source === dup.file1 && e.target === dup.file2) ||
128
+ (e.source === dup.file2 && e.target === dup.file1)
129
+ );
130
+ if (!exists) edges.push({ source: dup.file1, target: dup.file2, type: 'similarity' });
131
+ }
132
+ }
133
+
134
+ // Sort edges by priority: similarity and dependency first (most important for visualization)
135
+ const edgePriority: Record<string, number> = {
136
+ similarity: 1,
137
+ dependency: 2,
138
+ reference: 3,
139
+ related: 4,
140
+ };
141
+ const sortedEdges = [...edges].sort((a, b) => {
142
+ const priorityA = edgePriority[a.type] || 99;
143
+ const priorityB = edgePriority[b.type] || 99;
144
+ return priorityA - priorityB;
145
+ });
146
+
147
+ return {
148
+ nodes: nodes.slice(0, GRAPH_CONFIG.maxNodes),
149
+ edges: sortedEdges.slice(0, GRAPH_CONFIG.maxEdges),
150
+ };
151
+ }
152
+
153
+ export async function loadReportData(): Promise<ReportData | null> {
154
+ const possiblePaths = ['/report-data.json', '../report-data.json', '../../report-data.json'];
155
+
156
+ for (const path of possiblePaths) {
157
+ try {
158
+ const response = await fetch(path);
159
+ if (response.ok) {
160
+ return await response.json();
161
+ }
162
+ } catch {
163
+ continue;
164
+ }
165
+ }
166
+
167
+ return null;
168
+ }
169
+
170
+ export function getEdgeDistance(type: string): number {
171
+ return GRAPH_CONFIG.edgeDistances[type as keyof typeof GRAPH_CONFIG.edgeDistances] ?? GRAPH_CONFIG.edgeDistances.dependency;
172
+ }
173
+
174
+ export function getEdgeStrength(type: string): number {
175
+ return GRAPH_CONFIG.edgeStrengths[type as keyof typeof GRAPH_CONFIG.edgeStrengths] ?? GRAPH_CONFIG.edgeStrengths.dependency;
176
+ }
177
+
178
+ export function getEdgeOpacity(type: string): number {
179
+ return GRAPH_CONFIG.edgeOpacities[type as keyof typeof GRAPH_CONFIG.edgeOpacities] ?? GRAPH_CONFIG.edgeOpacities.dependency;
180
+ }
181
+
182
+ export function getEdgeStrokeWidth(type: string): number {
183
+ return type === 'similarity' ? 2 : 1;
184
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "extends": "../../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "target": "ES2020",
5
+ "useDefineForClassFields": true,
6
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
7
+ "module": "ESNext",
8
+ "skipLibCheck": true,
9
+ "moduleResolution": "bundler",
10
+ "allowImportingTsExtensions": true,
11
+ "isolatedModules": true,
12
+ "moduleDetection": "force",
13
+ "noEmit": true,
14
+ "jsx": "react-jsx",
15
+ "strict": true,
16
+ "noUnusedLocals": true,
17
+ "noUnusedParameters": true,
18
+ "noFallthroughCasesInSwitch": true,
19
+ "noUncheckedSideEffectImports": true
20
+ },
21
+ "include": ["src"]
22
+ }
@@ -0,0 +1,46 @@
1
+ import { defineConfig } from 'vite';
2
+ import { resolve } from 'path';
3
+ import react from '@vitejs/plugin-react';
4
+ import tailwindcss from '@tailwindcss/vite';
5
+ import { existsSync } from 'fs';
6
+
7
+ export default defineConfig(({ command }) => {
8
+ const isDev = command === 'serve';
9
+
10
+ // Resolve path to @aiready/components for alias
11
+ // Try monorepo first, then fall back to installed package
12
+ let componentsPath = resolve(__dirname, '../../components/src');
13
+ if (!existsSync(componentsPath)) {
14
+ // Fallback: try installed package
15
+ try {
16
+ componentsPath = require.resolve('@aiready/components');
17
+ componentsPath = resolve(componentsPath, '..');
18
+ } catch (e) {
19
+ // Use build dist as last resort
20
+ componentsPath = resolve(__dirname, '../../components/dist');
21
+ }
22
+ }
23
+
24
+ return {
25
+ plugins: [react(), tailwindcss()],
26
+ build: {
27
+ outDir: 'dist',
28
+ emptyOutDir: true,
29
+ rollupOptions: {
30
+ output: {
31
+ manualChunks: undefined,
32
+ },
33
+ },
34
+ },
35
+ server: {
36
+ // Use default port (5173); don't hardcode to avoid conflicts
37
+ open: false,
38
+ },
39
+ resolve: {
40
+ alias: {
41
+ // during dev resolve to source for HMR; during build use the built dist
42
+ '@aiready/components': isDev ? componentsPath : resolve(__dirname, '../../components/dist'),
43
+ },
44
+ },
45
+ };
46
+ });