Files
Ubuntu 32b43f85bd a
2026-07-03 16:58:28 +08:00

196 lines
6.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
Architecture Diagram Generator
生成精美的深色主题系统架构图
⚠️ 本脚本消耗 AI token,请确保用户明确要求使用
"""
import os
import sys
import json
import re
from datetime import datetime
from pathlib import Path
from typing import Optional
try:
from openai import OpenAI
except ImportError:
print("Error: openai package not installed")
sys.exit(1)
# 配置
SKILL_DIR = Path(__file__).parent
TEMPLATE_PATH = SKILL_DIR / "assets" / "template.html"
OUTPUT_DIR = Path("/tmp/architecture-diagrams")
# 配色方案
COLORS = {
"frontend": ("rgba(8, 51, 68, 0.4)", "#22d3ee"),
"backend": ("rgba(6, 78, 59, 0.4)", "#34d399"),
"database": ("rgba(76, 29, 149, 0.4)", "#a78bfa"),
"cloud": ("rgba(120, 53, 15, 0.3)", "#fbbf24"),
"security": ("rgba(136, 19, 55, 0.4)", "#fb7185"),
"message": ("rgba(251, 146, 60, 0.3)", "#fb923c"),
"generic": ("rgba(30, 41, 59, 0.5)", "#94a3b8"),
}
def get_api_client():
"""获取 API 客户端(优先 DeepSeek,备选 Moonshot)"""
# 优先使用 DeepSeek
api_key = os.getenv("DEEPSEEK_API_KEY")
if api_key:
return OpenAI(api_key=api_key, base_url="https://api.deepseek.com")
# 备选 Moonshot
api_key = os.getenv("MOONSHOT_API_KEY")
if api_key:
return OpenAI(api_key=api_key, base_url="https://api.moonshot.cn/v1")
raise ValueError("未找到 DEEPSEEK_API_KEY 或 MOONSHOT_API_KEY")
def build_prompt(project_name: str, description: str, components: list) -> str:
"""构建生成 SVG 的 prompt"""
components_str = "\n".join([
f"- {c['name']}: {c['type']} - {c.get('description', '')}"
for c in components
])
return f"""你是一个专业的架构图设计师。请根据以下系统描述,生成 SVG 代码。
项目: {project_name}
描述: {description}
组件列表:
{components_str}
要求:
1. 生成完整的 SVG 代码,可直接嵌入 HTML
2. 使用深色主题,背景透明(由外部容器控制)
3. 组件类型对应的配色:
- frontend: 填充 rgba(8, 51, 68, 0.4), 边框 #22d3ee (青色)
- backend: 填充 rgba(6, 78, 59, 0.4), 边框 #34d399 (绿色)
- database: 填充 rgba(76, 29, 149, 0.4), 边框 #a78bfa (紫色)
- cloud: 填充 rgba(120, 53, 15, 0.3), 边框 #fbbf24 (琥珀色)
- security: 填充 rgba(136, 19, 55, 0.4), 边框 #fb7185 (玫瑰色)
- message: 填充 rgba(251, 146, 60, 0.3), 边框 #fb923c (橙色)
- generic: 填充 rgba(30, 41, 59, 0.5), 边框 #94a3b8 (灰色)
4. 组件使用圆角矩形 (rx="6"),半透明填充,1.5px 边框
5. 组件之间用灰色箭头连接 (#64748b),箭头使用 marker
6. 组件标签使用白色文字,字号 11px,居中
7. 组件大小约 120x60px,间距合理避免重叠
8. 图例放在右下角,说明各颜色代表的组件类型
只返回 SVG 代码,不要返回其他内容。SVG 的 viewBox 应该是 "0 0 900 500" 左右。
"""
def generate_svg(prompt: str) -> str:
"""调用 AI API 生成 SVG"""
client = get_api_client()
response = client.chat.completions.create(
model="deepseek-chat", # 或 moonshot 模型
messages=[
{"role": "system", "content": "你是一个专业的架构图 SVG 生成器。只输出 SVG 代码,不输出任何解释。"},
{"role": "user", "content": prompt}
],
temperature=0.3,
max_tokens=4000
)
return response.choices[0].message.content
def extract_svg_code(content: str) -> str:
"""从 AI 响应中提取 SVG 代码"""
# 尝试提取 <svg> 标签内容
svg_match = re.search(r'<svg[^>]*>.*?</svg>', content, re.DOTALL)
if svg_match:
return svg_match.group(0)
# 如果没有标签,可能是纯 SVG 路径
if '<' in content and '>' in content:
return f'<svg viewBox="0 0 900 500" xmlns="http://www.w3.org/2000/svg">{content}</svg>'
return content
def generate_diagram(
project_name: str,
description: str,
components: list,
output_path: Optional[str] = None
) -> str:
"""
生成架构图
Args:
project_name: 项目名称
description: 项目描述
components: 组件列表,每项包含 name, type, description
output_path: 输出路径,默认保存到 /tmp/architecture-diagrams/
Returns:
生成的 HTML 文件路径
"""
# 确保输出目录存在
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
# 生成文件名
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
safe_name = re.sub(r'[^\w\-]', '_', project_name.lower())
if output_path is None:
output_path = OUTPUT_DIR / f"{safe_name}_{timestamp}.html"
else:
output_path = Path(output_path)
# 读取模板
template = TEMPLATE_PATH.read_text(encoding='utf-8')
# 构建 prompt 并生成 SVG
prompt = build_prompt(project_name, description, components)
svg_content = generate_svg(prompt)
svg_content = extract_svg_code(svg_content)
# 生成卡片内容
frontend_items = [f"<li>{c['name']}</li>" for c in components if c['type'] == 'frontend']
backend_items = [f"<li>{c['name']}</li>" for c in components if c['type'] == 'backend']
database_items = [f"<li>{c['name']}</li>" for c in components if c['type'] == 'database']
# 填充模板
html = template.replace("{{PROJECT_NAME}}", project_name)
html = html.replace("{{SVG_CONTENT}}", svg_content)
html = html.replace("{{FRONTEND_ITEMS}}", "\n".join(frontend_items) if frontend_items else "<li>无</li>")
html = html.replace("{{BACKEND_ITEMS}}", "\n".join(backend_items) if backend_items else "<li>无</li>")
html = html.replace("{{DATABASE_ITEMS}}", "\n".join(database_items) if database_items else "<li>无</li>")
html = html.replace("{{TIMESTAMP}}", datetime.now().strftime("%Y-%m-%d %H:%M"))
# 保存
output_path.write_text(html, encoding='utf-8')
return str(output_path)
if __name__ == "__main__":
# 测试用例
test_components = [
{"name": "React 前端", "type": "frontend", "description": "用户界面"},
{"name": "FastAPI", "type": "backend", "description": "API 服务"},
{"name": "SQLite", "type": "database", "description": "数据存储"},
{"name": "Redis", "type": "database", "description": "缓存"},
]
result = generate_diagram(
project_name="Test Project",
description="一个简单的测试项目",
components=test_components
)
print(f"Generated: {result}")