Kraft102's picture
fix: sql.js Docker/Alpine compatibility layer for PatternMemory and FailureMemory
5a81b95
/**
* ╔═══════════════════════════════════════════════════════════════════════════╗
* β•‘ 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;