system-design-visualizer-tool

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

System 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 install
bash
git clone https://github.com/mallahyari/system-design-visualizer.git
cd system-design-visualizer
npm install

Environment Configuration

环境配置

Create a
.env
file in the root directory:
env
VITE_OPENAI_API_KEY=your_openai_api_key_here
Note: If no API key is provided, the app runs in Mock Mode with sample data.
在根目录创建
.env
文件:
env
VITE_OPENAI_API_KEY=your_openai_api_key_here
注意:如果未提供API密钥,应用将使用示例数据在模拟模式下运行。

Start Development Server

启动开发服务器

bash
npm run dev
The app will be available at
http://localhost:5173
.
bash
npm run dev
应用将在
http://localhost:5173
地址可用。

Core Architecture

核心架构

The project uses a React + Vite setup with three main visualization stages:
  1. Image Upload: Accepts system design diagrams
  2. Mermaid Generation: AI converts image to Mermaid.js code
  3. Interactive Graph: Converts Mermaid to React Flow visualization
该项目采用React + Vite架构,包含三个主要可视化阶段:
  1. 图像上传:接收系统设计图
  2. Mermaid生成:AI将图像转换为Mermaid.js代码
  3. 交互式图形:将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
    VITE_OPENAI_API_KEY
    is correctly set in
    .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
    reactflow/dist/style.css
    for proper styling
  • Use
    fitView
    prop to auto-center the graph on mount
  • 确保父容器有明确的高度/宽度
  • 导入
    reactflow/dist/style.css
    以获得正确样式
  • 使用
    fitView
    属性在挂载时自动居中图形

Build Issues

构建问题

bash
undefined
bash
undefined

Clear 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
undefined
npm list
undefined

API Reference

API参考

Main Functions

主要函数

  • analyzeSystemDesign(imageDataUrl)
    : Converts image to Mermaid code
  • parseMermaidToReactFlow(mermaidCode)
    : Converts Mermaid to React Flow format
  • getComponentDetails(componentName)
    : Gets AI-generated component details
  • getLayoutedElements(nodes, edges)
    : Auto-layouts graph nodes
  • analyzeSystemDesign(imageDataUrl)
    :将图像转换为Mermaid代码
  • parseMermaidToReactFlow(mermaidCode)
    :将Mermaid转换为React Flow格式
  • getComponentDetails(componentName)
    :获取AI生成的组件详情
  • getLayoutedElements(nodes, edges)
    :自动布局图形节点

Environment Variables

环境变量

  • VITE_OPENAI_API_KEY
    : OpenAI API key for AI analysis (optional for mock mode)
  • VITE_OPENAI_API_KEY
    :用于AI分析的OpenAI API密钥(模拟模式下可选)