Spaces:
Paused
Paused
| /** | |
| * βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| * β ARCHITECTURE VIEW COMPONENT β | |
| * βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| * β High-level system architecture visualization using Mermaid β | |
| * β Part of the Visual Cortex Layer β | |
| * βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| */ | |
| import { useMemo } from 'react'; | |
| import { MermaidDiagram } from './MermaidDiagram'; | |
| import { cn } from '@/lib/utils'; | |
| import { Server, Database, Cloud, Globe, Shield, Cpu } from 'lucide-react'; | |
| export interface ArchitectureNode { | |
| id: string; | |
| label: string; | |
| type: 'service' | 'database' | 'cloud' | 'api' | 'security' | 'compute' | 'custom'; | |
| group?: string; | |
| description?: string; | |
| } | |
| export interface ArchitectureConnection { | |
| from: string; | |
| to: string; | |
| label?: string; | |
| style?: 'solid' | 'dashed' | 'dotted'; | |
| direction?: 'forward' | 'back' | 'both'; | |
| } | |
| export interface ArchitectureGroup { | |
| id: string; | |
| label: string; | |
| nodes: string[]; | |
| } | |
| export interface ArchitectureViewProps { | |
| /** Architecture title */ | |
| title?: string; | |
| /** Nodes in the architecture */ | |
| nodes: ArchitectureNode[]; | |
| /** Connections between nodes */ | |
| connections: ArchitectureConnection[]; | |
| /** Optional groupings (subgraphs) */ | |
| groups?: ArchitectureGroup[]; | |
| /** Diagram direction: TB, BT, LR, RL */ | |
| direction?: 'TB' | 'BT' | 'LR' | 'RL'; | |
| /** Show node type icons in legend */ | |
| showLegend?: boolean; | |
| /** Custom class */ | |
| className?: string; | |
| /** Callback when rendered */ | |
| onRender?: (svg: string) => void; | |
| } | |
| // Node type to Mermaid shape mapping | |
| const NODE_SHAPES: Record<ArchitectureNode['type'], { prefix: string; suffix: string }> = { | |
| service: { prefix: '[', suffix: ']' }, // Rectangle | |
| database: { prefix: '[(', suffix: ')]' }, // Cylinder | |
| cloud: { prefix: '((', suffix: '))' }, // Circle | |
| api: { prefix: '{{', suffix: '}}' }, // Hexagon | |
| security: { prefix: '[/', suffix: '/]' }, // Parallelogram | |
| compute: { prefix: '([', suffix: '])' }, // Stadium | |
| custom: { prefix: '[', suffix: ']' }, // Rectangle | |
| }; | |
| // Node type icons | |
| const NODE_ICONS: Record<ArchitectureNode['type'], React.ReactNode> = { | |
| service: <Server className="w-3 h-3" />, | |
| database: <Database className="w-3 h-3" />, | |
| cloud: <Cloud className="w-3 h-3" />, | |
| api: <Globe className="w-3 h-3" />, | |
| security: <Shield className="w-3 h-3" />, | |
| compute: <Cpu className="w-3 h-3" />, | |
| custom: <Server className="w-3 h-3" />, | |
| }; | |
| // Connection style to Mermaid arrow | |
| const CONNECTION_ARROWS: Record<string, string> = { | |
| 'solid-forward': '-->', | |
| 'solid-back': '<--', | |
| 'solid-both': '<-->', | |
| 'dashed-forward': '-.->', | |
| 'dashed-back': '<-.-', | |
| 'dashed-both': '<-.->', | |
| 'dotted-forward': '-.->', | |
| 'dotted-back': '<-.-', | |
| 'dotted-both': '<-.->', | |
| }; | |
| export function ArchitectureView({ | |
| title, | |
| nodes, | |
| connections, | |
| groups = [], | |
| direction = 'TB', | |
| showLegend = true, | |
| className, | |
| onRender, | |
| }: ArchitectureViewProps) { | |
| // Generate Mermaid code from architecture definition | |
| const mermaidCode = useMemo(() => { | |
| const lines: string[] = [`flowchart ${direction}`]; | |
| // Create node ID map for groups | |
| const nodeGroups = new Map<string, string>(); | |
| groups.forEach(g => { | |
| g.nodes.forEach(nodeId => nodeGroups.set(nodeId, g.id)); | |
| }); | |
| // Generate subgraphs | |
| const processedNodes = new Set<string>(); | |
| groups.forEach(group => { | |
| lines.push(` subgraph ${group.id}["${group.label}"]`); | |
| group.nodes.forEach(nodeId => { | |
| const node = nodes.find(n => n.id === nodeId); | |
| if (node) { | |
| const shape = NODE_SHAPES[node.type]; | |
| lines.push(` ${node.id}${shape.prefix}"${node.label}"${shape.suffix}`); | |
| processedNodes.add(node.id); | |
| } | |
| }); | |
| lines.push(' end'); | |
| }); | |
| // Generate standalone nodes | |
| nodes.forEach(node => { | |
| if (!processedNodes.has(node.id)) { | |
| const shape = NODE_SHAPES[node.type]; | |
| lines.push(` ${node.id}${shape.prefix}"${node.label}"${shape.suffix}`); | |
| } | |
| }); | |
| // Generate connections | |
| connections.forEach(conn => { | |
| const style = conn.style || 'solid'; | |
| const dir = conn.direction || 'forward'; | |
| const arrow = CONNECTION_ARROWS[`${style}-${dir}`] || '-->'; | |
| if (conn.label) { | |
| lines.push(` ${conn.from} ${arrow}|"${conn.label}"| ${conn.to}`); | |
| } else { | |
| lines.push(` ${conn.from} ${arrow} ${conn.to}`); | |
| } | |
| }); | |
| // Add styling | |
| lines.push(''); | |
| lines.push(' %% Styling'); | |
| // Style by node type | |
| const typeGroups: Record<string, string[]> = {}; | |
| nodes.forEach(node => { | |
| if (!typeGroups[node.type]) typeGroups[node.type] = []; | |
| typeGroups[node.type].push(node.id); | |
| }); | |
| const typeStyles: Record<string, string> = { | |
| service: 'fill:#3b82f6,stroke:#1d4ed8,color:#fff', | |
| database: 'fill:#8b5cf6,stroke:#6d28d9,color:#fff', | |
| cloud: 'fill:#06b6d4,stroke:#0891b2,color:#fff', | |
| api: 'fill:#f59e0b,stroke:#d97706,color:#000', | |
| security: 'fill:#ef4444,stroke:#dc2626,color:#fff', | |
| compute: 'fill:#22c55e,stroke:#16a34a,color:#fff', | |
| custom: 'fill:#64748b,stroke:#475569,color:#fff', | |
| }; | |
| Object.entries(typeGroups).forEach(([type, ids]) => { | |
| if (ids.length > 0) { | |
| lines.push(` style ${ids.join(',')} ${typeStyles[type as ArchitectureNode['type']]}`); | |
| } | |
| }); | |
| return lines.join('\n'); | |
| }, [nodes, connections, groups, direction]); | |
| // Get unique node types for legend | |
| const nodeTypes = useMemo(() => { | |
| const types = new Set(nodes.map(n => n.type)); | |
| return Array.from(types); | |
| }, [nodes]); | |
| return ( | |
| <div className={cn('architecture-view', className)}> | |
| {/* Legend */} | |
| {showLegend && nodeTypes.length > 1 && ( | |
| <div className="flex flex-wrap gap-3 mb-3 px-2"> | |
| {nodeTypes.map(type => ( | |
| <div | |
| key={type} | |
| className="flex items-center gap-1.5 text-xs text-muted-foreground" | |
| > | |
| <span className="flex items-center justify-center w-5 h-5 rounded bg-muted/50"> | |
| {NODE_ICONS[type]} | |
| </span> | |
| <span className="capitalize">{type}</span> | |
| </div> | |
| ))} | |
| </div> | |
| )} | |
| {/* Mermaid Diagram */} | |
| <MermaidDiagram | |
| code={mermaidCode} | |
| title={title} | |
| theme="dark" | |
| zoomable={true} | |
| onRender={onRender} | |
| /> | |
| </div> | |
| ); | |
| } | |
| export default ArchitectureView; | |