system-design-visualizer-tool
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSystem Design Visualizer Tool
系统设计可视化工具
Skill by ara.so — Design Skills collection.
System Design Visualizer is an AI-powered tool that transforms static system design images (architecture diagrams, flowcharts, etc.) into interactive, explorable visualizations. It uses OpenAI's GPT-4o Vision to analyze uploaded images, generates Mermaid.js diagrams, and converts them into interactive React Flow graphs with detailed component information.
由ara.so提供的技能——设计技能合集。
System Design Visualizer是一款AI驱动的工具,可将静态系统设计图像(架构图、流程图等)转换为可交互、可探索的可视化内容。它利用OpenAI的GPT-4o Vision分析上传的图像,生成Mermaid.js图表,并将其转换为带有详细组件信息的交互式React Flow图形。
Installation
安装
bash
git clone https://github.com/mallahyari/system-design-visualizer.git
cd system-design-visualizer
npm installbash
git clone https://github.com/mallahyari/system-design-visualizer.git
cd system-design-visualizer
npm installEnvironment Configuration
环境配置
Create a file in the root directory:
.envenv
VITE_OPENAI_API_KEY=your_openai_api_key_hereNote: If no API key is provided, the app runs in Mock Mode with sample data.
在根目录创建文件:
.envenv
VITE_OPENAI_API_KEY=your_openai_api_key_here注意:如果未提供API密钥,应用将使用示例数据在模拟模式下运行。
Start Development Server
启动开发服务器
bash
npm run devThe app will be available at .
http://localhost:5173bash
npm run dev应用将在地址可用。
http://localhost:5173Core Architecture
核心架构
The project uses a React + Vite setup with three main visualization stages:
- Image Upload: Accepts system design diagrams
- Mermaid Generation: AI converts image to Mermaid.js code
- Interactive Graph: Converts Mermaid to React Flow visualization
该项目采用React + Vite架构,包含三个主要可视化阶段:
- 图像上传:接收系统设计图
- Mermaid生成:AI将图像转换为Mermaid.js代码
- 交互式图形:将Mermaid转换为React Flow可视化图形
Key Components
关键组件
Image Upload Component
图像上传组件
javascript
import { useState } from 'react';
import { Upload } from 'lucide-react';
function ImageUploader({ onImageUpload }) {
const [isDragging, setIsDragging] = useState(false);
const handleDrop = (e) => {
e.preventDefault();
setIsDragging(false);
const file = e.dataTransfer.files[0];
if (file && file.type.startsWith('image/')) {
const reader = new FileReader();
reader.onload = (e) => onImageUpload(e.target.result);
reader.readAsDataURL(file);
}
};
return (
<div
onDrop={handleDrop}
onDragOver={(e) => { e.preventDefault(); setIsDragging(true); }}
onDragLeave={() => setIsDragging(false)}
className={`border-2 border-dashed rounded-lg p-12 text-center ${
isDragging ? 'border-blue-500 bg-blue-50' : 'border-gray-300'
}`}
>
<Upload className="mx-auto h-12 w-12 text-gray-400" />
<p className="mt-4 text-sm text-gray-600">
Drag and drop your system design image here
</p>
</div>
);
}javascript
import { useState } from 'react';
import { Upload } from 'lucide-react';
function ImageUploader({ onImageUpload }) {
const [isDragging, setIsDragging] = useState(false);
const handleDrop = (e) => {
e.preventDefault();
setIsDragging(false);
const file = e.dataTransfer.files[0];
if (file && file.type.startsWith('image/')) {
const reader = new FileReader();
reader.onload = (e) => onImageUpload(e.target.result);
reader.readAsDataURL(file);
}
};
return (
<div
onDrop={handleDrop}
onDragOver={(e) => { e.preventDefault(); setIsDragging(true); }}
onDragLeave={() => setIsDragging(false)}
className={`border-2 border-dashed rounded-lg p-12 text-center ${
isDragging ? 'border-blue-500 bg-blue-50' : 'border-gray-300'
}`}
>
<Upload className="mx-auto h-12 w-12 text-gray-400" />
<p className="mt-4 text-sm text-gray-600">
Drag and drop your system design image here
</p>
</div>
);
}OpenAI Integration for Image Analysis
OpenAI图像分析集成
javascript
import OpenAI from 'openai';
const openai = new OpenAI({
apiKey: import.meta.env.VITE_OPENAI_API_KEY,
dangerouslyAllowBrowser: true
});
async function analyzeSystemDesign(imageDataUrl) {
const response = await openai.chat.completions.create({
model: "gpt-4o",
messages: [
{
role: "user",
content: [
{
type: "text",
text: "Analyze this system design diagram and generate a Mermaid.js diagram representing the architecture. Include all components, connections, and relationships."
},
{
type: "image_url",
image_url: { url: imageDataUrl }
}
]
}
],
max_tokens: 2000
});
return response.choices[0].message.content;
}javascript
import OpenAI from 'openai';
const openai = new OpenAI({
apiKey: import.meta.env.VITE_OPENAI_API_KEY,
dangerouslyAllowBrowser: true
});
async function analyzeSystemDesign(imageDataUrl) {
const response = await openai.chat.completions.create({
model: "gpt-4o",
messages: [
{
role: "user",
content: [
{
type: "text",
text: "Analyze this system design diagram and generate a Mermaid.js diagram representing the architecture. Include all components, connections, and relationships."
},
{
type: "image_url",
image_url: { url: imageDataUrl }
}
]
}
],
max_tokens: 2000
});
return response.choices[0].message.content;
}Mermaid to React Flow Conversion
Mermaid转React Flow转换
javascript
function parseMermaidToReactFlow(mermaidCode) {
const nodes = [];
const edges = [];
// Parse mermaid syntax (simplified example)
const lines = mermaidCode.split('\n');
const nodeMap = new Map();
let nodeId = 0;
lines.forEach((line, index) => {
// Match patterns like: A[Load Balancer] --> B[Web Server]
const connectionMatch = line.match(/(\w+)\[(.*?)\]\s*-->\s*(\w+)\[(.*?)\]/);
if (connectionMatch) {
const [_, sourceId, sourceLabel, targetId, targetLabel] = connectionMatch;
// Add source node if not exists
if (!nodeMap.has(sourceId)) {
nodes.push({
id: sourceId,
type: 'custom',
position: { x: 100, y: nodeId * 100 },
data: { label: sourceLabel }
});
nodeMap.set(sourceId, true);
nodeId++;
}
// Add target node if not exists
if (!nodeMap.has(targetId)) {
nodes.push({
id: targetId,
type: 'custom',
position: { x: 400, y: nodeId * 100 },
data: { label: targetLabel }
});
nodeMap.set(targetId, true);
nodeId++;
}
// Add edge
edges.push({
id: `${sourceId}-${targetId}`,
source: sourceId,
target: targetId,
type: 'smoothstep'
});
}
});
return { nodes, edges };
}javascript
function parseMermaidToReactFlow(mermaidCode) {
const nodes = [];
const edges = [];
// Parse mermaid syntax (simplified example)
const lines = mermaidCode.split('\n');
const nodeMap = new Map();
let nodeId = 0;
lines.forEach((line, index) => {
// Match patterns like: A[Load Balancer] --> B[Web Server]
const connectionMatch = line.match(/(\w+)\[(.*?)\]\s*-->\s*(\w+)\[(.*?)\]/);
if (connectionMatch) {
const [_, sourceId, sourceLabel, targetId, targetLabel] = connectionMatch;
// Add source node if not exists
if (!nodeMap.has(sourceId)) {
nodes.push({
id: sourceId,
type: 'custom',
position: { x: 100, y: nodeId * 100 },
data: { label: sourceLabel }
});
nodeMap.set(sourceId, true);
nodeId++;
}
// Add target node if not exists
if (!nodeMap.has(targetId)) {
nodes.push({
id: targetId,
type: 'custom',
position: { x: 400, y: nodeId * 100 },
data: { label: targetLabel }
});
nodeMap.set(targetId, true);
nodeId++;
}
// Add edge
edges.push({
id: `${sourceId}-${targetId}`,
source: sourceId,
target: targetId,
type: 'smoothstep'
});
}
});
return { nodes, edges };
}Interactive React Flow Graph
交互式React Flow图形
javascript
import ReactFlow, {
MiniMap,
Controls,
Background,
useNodesState,
useEdgesState
} from 'reactflow';
import 'reactflow/dist/style.css';
function InteractiveGraph({ mermaidCode }) {
const { nodes: initialNodes, edges: initialEdges } = parseMermaidToReactFlow(mermaidCode);
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const [selectedNode, setSelectedNode] = useState(null);
const onNodeClick = async (event, node) => {
setSelectedNode(node);
// Get detailed info about the component using AI
const details = await getComponentDetails(node.data.label);
setSelectedNode({ ...node, details });
};
return (
<div className="h-screen">
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onNodeClick={onNodeClick}
fitView
>
<Background />
<Controls />
<MiniMap />
</ReactFlow>
{selectedNode && (
<ComponentDetailsPanel node={selectedNode} />
)}
</div>
);
}javascript
import ReactFlow, {
MiniMap,
Controls,
Background,
useNodesState,
useEdgesState
} from 'reactflow';
import 'reactflow/dist/style.css';
function InteractiveGraph({ mermaidCode }) {
const { nodes: initialNodes, edges: initialEdges } = parseMermaidToReactFlow(mermaidCode);
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const [selectedNode, setSelectedNode] = useState(null);
const onNodeClick = async (event, node) => {
setSelectedNode(node);
// Get detailed info about the component using AI
const details = await getComponentDetails(node.data.label);
setSelectedNode({ ...node, details });
};
return (
<div className="h-screen">
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onNodeClick={onNodeClick}
fitView
>
<Background />
<Controls />
<MiniMap />
</ReactFlow>
{selectedNode && (
<ComponentDetailsPanel node={selectedNode} />
)}
</div>
);
}Component Details Analysis
组件详情分析
javascript
async function getComponentDetails(componentName) {
const response = await openai.chat.completions.create({
model: "gpt-4o",
messages: [
{
role: "user",
content: `Given a system design component named "${componentName}", provide:
1. Likely technology stack
2. Primary role in the architecture
3. Common configuration considerations
4. Best practices
Format as JSON with keys: technologies, role, configuration, bestPractices`
}
],
max_tokens: 500
});
return JSON.parse(response.choices[0].message.content);
}javascript
async function getComponentDetails(componentName) {
const response = await openai.chat.completions.create({
model: "gpt-4o",
messages: [
{
role: "user",
content: `Given a system design component named "${componentName}", provide:
1. Likely technology stack
2. Primary role in the architecture
3. Common configuration considerations
4. Best practices
Format as JSON with keys: technologies, role, configuration, bestPractices`
}
],
max_tokens: 500
});
return JSON.parse(response.choices[0].message.content);
}Custom Node Component
自定义节点组件
javascript
import { Handle, Position } from 'reactflow';
function CustomNode({ data, selected }) {
return (
<div className={`px-4 py-2 rounded-lg border-2 bg-white shadow-md ${
selected ? 'border-blue-500' : 'border-gray-300'
}`}>
<Handle type="target" position={Position.Top} />
<div className="font-semibold text-sm">{data.label}</div>
{data.description && (
<div className="text-xs text-gray-500 mt-1">{data.description}</div>
)}
<Handle type="source" position={Position.Bottom} />
</div>
);
}
const nodeTypes = {
custom: CustomNode
};javascript
import { Handle, Position } from 'reactflow';
function CustomNode({ data, selected }) {
return (
<div className={`px-4 py-2 rounded-lg border-2 bg-white shadow-md ${
selected ? 'border-blue-500' : 'border-gray-300'
}`}>
<Handle type="target" position={Position.Top} />
<div className="font-semibold text-sm">{data.label}</div>
{data.description && (
<div className="text-xs text-gray-500 mt-1">{data.description}</div>
)}
<Handle type="source" position={Position.Bottom} />
</div>
);
}
const nodeTypes = {
custom: CustomNode
};Mock Mode (No API Key)
模拟模式(无需API密钥)
When running without an OpenAI API key, use mock data:
javascript
function getMockMermaidDiagram() {
return `graph TB
A[Load Balancer] --> B[Web Server 1]
A --> C[Web Server 2]
B --> D[Application Server]
C --> D
D --> E[Database Master]
D --> F[Cache Layer]
E --> G[Database Replica]`;
}
function getMockComponentDetails(componentName) {
const mockDetails = {
'Load Balancer': {
technologies: ['NGINX', 'HAProxy', 'AWS ELB'],
role: 'Distributes incoming traffic across multiple servers',
configuration: 'Health checks, SSL termination, routing rules',
bestPractices: 'Use multiple availability zones, enable auto-scaling'
},
// Add more mock data as needed
};
return mockDetails[componentName] || {
technologies: ['Generic'],
role: 'System component',
configuration: 'Standard setup',
bestPractices: 'Follow industry standards'
};
}当未提供OpenAI API密钥时,使用模拟数据:
javascript
function getMockMermaidDiagram() {
return `graph TB
A[Load Balancer] --> B[Web Server 1]
A --> C[Web Server 2]
B --> D[Application Server]
C --> D
D --> E[Database Master]
D --> F[Cache Layer]
E --> G[Database Replica]`;
}
function getMockComponentDetails(componentName) {
const mockDetails = {
'Load Balancer': {
technologies: ['NGINX', 'HAProxy', 'AWS ELB'],
role: 'Distributes incoming traffic across multiple servers',
configuration: 'Health checks, SSL termination, routing rules',
bestPractices: 'Use multiple availability zones, enable auto-scaling'
},
// Add more mock data as needed
};
return mockDetails[componentName] || {
technologies: ['Generic'],
role: 'System component',
configuration: 'Standard setup',
bestPractices: 'Follow industry standards'
};
}Full App Integration Example
完整应用集成示例
javascript
import { useState } from 'react';
import ReactFlow from 'reactflow';
import mermaid from 'mermaid';
function App() {
const [step, setStep] = useState('upload'); // upload, mermaid, interactive
const [imageData, setImageData] = useState(null);
const [mermaidCode, setMermaidCode] = useState('');
const [flowData, setFlowData] = useState({ nodes: [], edges: [] });
const handleImageUpload = async (dataUrl) => {
setImageData(dataUrl);
setStep('mermaid');
// Generate Mermaid diagram
const code = await analyzeSystemDesign(dataUrl);
setMermaidCode(code);
};
const handleConvertToInteractive = () => {
const data = parseMermaidToReactFlow(mermaidCode);
setFlowData(data);
setStep('interactive');
};
return (
<div className="min-h-screen bg-gray-900 text-white">
<header className="p-4 border-b border-gray-800">
<h1 className="text-2xl font-bold">System Design Visualizer</h1>
</header>
<main className="p-8">
{step === 'upload' && (
<ImageUploader onImageUpload={handleImageUpload} />
)}
{step === 'mermaid' && (
<div className="grid grid-cols-2 gap-4">
<div>
<h2 className="text-xl mb-4">Original Image</h2>
<img src={imageData} alt="Original" className="w-full" />
</div>
<div>
<h2 className="text-xl mb-4">Mermaid Diagram</h2>
<pre className="bg-gray-800 p-4 rounded overflow-auto">
{mermaidCode}
</pre>
<button
onClick={handleConvertToInteractive}
className="mt-4 px-6 py-2 bg-blue-600 rounded hover:bg-blue-700"
>
Convert to Interactive
</button>
</div>
</div>
)}
{step === 'interactive' && (
<InteractiveGraph mermaidCode={mermaidCode} />
)}
</main>
</div>
);
}javascript
import { useState } from 'react';
import ReactFlow from 'reactflow';
import mermaid from 'mermaid';
function App() {
const [step, setStep] = useState('upload'); // upload, mermaid, interactive
const [imageData, setImageData] = useState(null);
const [mermaidCode, setMermaidCode] = useState('');
const [flowData, setFlowData] = useState({ nodes: [], edges: [] });
const handleImageUpload = async (dataUrl) => {
setImageData(dataUrl);
setStep('mermaid');
// Generate Mermaid diagram
const code = await analyzeSystemDesign(dataUrl);
setMermaidCode(code);
};
const handleConvertToInteractive = () => {
const data = parseMermaidToReactFlow(mermaidCode);
setFlowData(data);
setStep('interactive');
};
return (
<div className="min-h-screen bg-gray-900 text-white">
<header className="p-4 border-b border-gray-800">
<h1 className="text-2xl font-bold">System Design Visualizer</h1>
</header>
<main className="p-8">
{step === 'upload' && (
<ImageUploader onImageUpload={handleImageUpload} />
)}
{step === 'mermaid' && (
<div className="grid grid-cols-2 gap-4">
<div>
<h2 className="text-xl mb-4">原始图像</h2>
<img src={imageData} alt="Original" className="w-full" />
</div>
<div>
<h2 className="text-xl mb-4">Mermaid图表</h2>
<pre className="bg-gray-800 p-4 rounded overflow-auto">
{mermaidCode}
</pre>
<button
onClick={handleConvertToInteractive}
className="mt-4 px-6 py-2 bg-blue-600 rounded hover:bg-blue-700"
>
转换为交互式图形
</button>
</div>
</div>
)}
{step === 'interactive' && (
<InteractiveGraph mermaidCode={mermaidCode} />
)}
</main>
</div>
);
}Common Patterns
常见模式
Copying Mermaid Code to Clipboard
复制Mermaid代码到剪贴板
javascript
import { Copy } from 'lucide-react';
function CopyButton({ text }) {
const handleCopy = () => {
navigator.clipboard.writeText(text);
// Show toast notification
};
return (
<button
onClick={handleCopy}
className="flex items-center gap-2 px-3 py-1 bg-gray-700 rounded hover:bg-gray-600"
>
<Copy size={16} />
Copy Code
</button>
);
}javascript
import { Copy } from 'lucide-react';
function CopyButton({ text }) {
const handleCopy = () => {
navigator.clipboard.writeText(text);
// Show toast notification
};
return (
<button
onClick={handleCopy}
className="flex items-center gap-2 px-3 py-1 bg-gray-700 rounded hover:bg-gray-600"
>
<Copy size={16} />
Copy Code
</button>
);
}Auto-Layout for React Flow Nodes
React Flow节点自动布局
javascript
import dagre from 'dagre';
function getLayoutedElements(nodes, edges) {
const dagreGraph = new dagre.graphlib.Graph();
dagreGraph.setDefaultEdgeLabel(() => ({}));
dagreGraph.setGraph({ rankdir: 'TB' });
nodes.forEach((node) => {
dagreGraph.setNode(node.id, { width: 150, height: 50 });
});
edges.forEach((edge) => {
dagreGraph.setEdge(edge.source, edge.target);
});
dagre.layout(dagreGraph);
const layoutedNodes = nodes.map((node) => {
const nodeWithPosition = dagreGraph.node(node.id);
return {
...node,
position: {
x: nodeWithPosition.x - 75,
y: nodeWithPosition.y - 25
}
};
});
return { nodes: layoutedNodes, edges };
}javascript
import dagre from 'dagre';
function getLayoutedElements(nodes, edges) {
const dagreGraph = new dagre.graphlib.Graph();
dagreGraph.setDefaultEdgeLabel(() => ({}));
dagreGraph.setGraph({ rankdir: 'TB' });
nodes.forEach((node) => {
dagreGraph.setNode(node.id, { width: 150, height: 50 });
});
edges.forEach((edge) => {
dagreGraph.setEdge(edge.source, edge.target);
});
dagre.layout(dagreGraph);
const layoutedNodes = nodes.map((node) => {
const nodeWithPosition = dagreGraph.node(node.id);
return {
...node,
position: {
x: nodeWithPosition.x - 75,
y: nodeWithPosition.y - 25
}
};
});
return { nodes: layoutedNodes, edges };
}Troubleshooting
故障排除
OpenAI API Errors
OpenAI API错误
- 401 Unauthorized: Check that is correctly set in
VITE_OPENAI_API_KEY.env - 429 Rate Limit: Implement retry logic with exponential backoff
- Image too large: Resize images before upload (max 20MB)
- 401 Unauthorized:检查文件中的
.env是否设置正确VITE_OPENAI_API_KEY - 429 Rate Limit:实现带指数退避的重试逻辑
- Image too large:上传前调整图像大小(最大20MB)
Mermaid Parsing Issues
Mermaid解析问题
- Ensure generated Mermaid code uses supported syntax (graph TB, graph LR)
- Validate node IDs are alphanumeric without special characters
- Check for balanced brackets in node labels
- 确保生成的Mermaid代码使用支持的语法(graph TB、graph LR)
- 验证节点ID为字母数字,无特殊字符
- 检查节点标签中的括号是否平衡
React Flow Rendering
React Flow渲染问题
- Ensure parent container has explicit height/width
- Import for proper styling
reactflow/dist/style.css - Use prop to auto-center the graph on mount
fitView
- 确保父容器有明确的高度/宽度
- 导入以获得正确样式
reactflow/dist/style.css - 使用属性在挂载时自动居中图形
fitView
Build Issues
构建问题
bash
undefinedbash
undefinedClear cache and reinstall
清除缓存并重新安装
rm -rf node_modules package-lock.json
npm install
rm -rf node_modules package-lock.json
npm install
Check for peer dependency issues
检查对等依赖问题
npm list
undefinednpm list
undefinedAPI Reference
API参考
Main Functions
主要函数
- : Converts image to Mermaid code
analyzeSystemDesign(imageDataUrl) - : Converts Mermaid to React Flow format
parseMermaidToReactFlow(mermaidCode) - : Gets AI-generated component details
getComponentDetails(componentName) - : Auto-layouts graph nodes
getLayoutedElements(nodes, edges)
- :将图像转换为Mermaid代码
analyzeSystemDesign(imageDataUrl) - :将Mermaid转换为React Flow格式
parseMermaidToReactFlow(mermaidCode) - :获取AI生成的组件详情
getComponentDetails(componentName) - :自动布局图形节点
getLayoutedElements(nodes, edges)
Environment Variables
环境变量
- : OpenAI API key for AI analysis (optional for mock mode)
VITE_OPENAI_API_KEY
- :用于AI分析的OpenAI API密钥(模拟模式下可选)
VITE_OPENAI_API_KEY