Logo
热心市民王先生

核心实现细节分析

实现细节 设计模式 代码分析

设计模式、关键算法、代码片段与实现技巧深度剖析

视觉模式库实现

模式 1:扇出(Fan-Out)- 一对多关系

使用场景: API Gateway→微服务、PRD→功能列表、根原因→多个影响

实现代码结构:

{
  "elements": [
    {
      "id": "central_ellipse",
      "type": "ellipse",
      "x": 400, "y": 300,
      "width": 120, "height": 80,
      "strokeColor": "#c2410c",
      "backgroundColor": "#fed7aa",
      "text": "API Gateway"
    },
    {
      "id": "arrow_fan_1",
      "type": "arrow",
      "x": 460, "y": 260,
      "points": [[0, 0], [-80, -60]],  // 左上
      "startBinding": {"elementId": "central_ellipse", "focus": 0, "gap": 2},
      "endBinding": {"elementId": "service_1", "focus": 0, "gap": 2}
    },
    {
      "id": "arrow_fan_2",
      "type": "arrow",
      "x": 460, "y": 340,
      "points": [[0, 0], [-80, 60]],   // 左下
      "startBinding": {"elementId": "central_ellipse", "focus": 0, "gap": 2},
      "endBinding": {"elementId": "service_2", "focus": 0, "gap": 2}
    },
    {
      "id": "arrow_fan_3",
      "type": "arrow",
      "x": 520, "y": 300,
      "points": [[0, 0], [80, 0]],     // 右侧
      "startBinding": {"elementId": "central_ellipse", "focus": 0, "gap": 2},
      "endBinding": {"elementId": "service_3", "focus": 0, "gap": 2}
    }
  ]
}

设计要点:

  • 中心元素使用椭圆(柔软、起源感)
  • 箭头从中心放射状发出
  • 角度均匀分布(如 120°、90° 间隔)
  • 目标元素使用相同形状(矩形或椭圆)

模式 2:收敛(Convergence)- 多对一聚合

使用场景: 多数据源→聚合、多输入→单输出、funnel 模式

实现代码结构:

{
  "elements": [
    {
      "id": "input_1",
      "type": "ellipse",
      "x": 100, "y": 200,
      "strokeColor": "#1e3a5f",
      "backgroundColor": "#3b82f6",
      "text": "Data Source A"
    },
    {
      "id": "input_2",
      "type": "ellipse",
      "x": 100, "y": 300,
      "strokeColor": "#1e3a5f",
      "backgroundColor": "#3b82f6",
      "text": "Data Source B"
    },
    {
      "id": "input_3",
      "type": "ellipse",
      "x": 100, "y": 400,
      "strokeColor": "#1e3a5f",
      "backgroundColor": "#3b82f6",
      "text": "Data Source C"
    },
    {
      "id": "aggregator",
      "type": "diamond",  // 菱形表示聚合/决策
      "x": 400, "y": 300,
      "width": 140, "height": 100,
      "strokeColor": "#b45309",
      "backgroundColor": "#fef3c7",
      "text": "Aggregator"
    },
    {
      "id": "arrow_merge_1",
      "type": "arrow",
      "points": [[0, 0], [150, 50]],  // 从 input_1 到 aggregator
      "startBinding": {"elementId": "input_1", "focus": 0, "gap": 2},
      "endBinding": {"elementId": "aggregator", "focus": 0, "gap": 2}
    }
    // ... 类似添加 arrow_merge_2, arrow_merge_3
  ]
}

设计要点:

  • 多个输入元素垂直/水平排列
  • 使用菱形表示聚合点(经典 funnel 符号)
  • 箭头从多个方向汇聚到单点

模式 3:时间线(Timeline)- 序列/事件流

使用场景: 协议事件流、生命周期、历史演变

实现代码结构:

{
  "elements": [
    {
      "id": "timeline_spine",
      "type": "line",
      "x": 200, "y": 100,
      "points": [[0, 0], [0, 500]],  // 垂直主线
      "strokeColor": "#1e3a5f",
      "strokeWidth": 3  // 加粗主线
    },
    {
      "id": "event_1_dot",
      "type": "ellipse",
      "x": 194, "y": 144,  // 居中于主线
      "width": 12, "height": 12,
      "strokeColor": "#3b82f6",
      "backgroundColor": "#3b82f6"
    },
    {
      "id": "event_1_label",
      "type": "text",
      "x": 220, "y": 140,  // 主线右侧
      "width": 200, "height": 25,
      "text": "RUN_STARTED",
      "fontSize": 16,
      "fontFamily": 3,
      "textAlign": "left",
      "strokeColor": "#1e40af"  // 标题颜色
    },
    {
      "id": "event_2_dot",
      "type": "ellipse",
      "x": 194, "y": 244,
      "width": 12, "height": 12,
      "strokeColor": "#3b82f6",
      "backgroundColor": "#3b82f6"
    },
    {
      "id": "event_2_label",
      "type": "text",
      "x": 220, "y": 240,
      "text": "STATE_DELTA",
      "strokeColor": "#3b82f6"  // 副标题颜色
    },
    {
      "id": "event_3_dot",
      "type": "ellipse",
      "x": 194, "y": 344,
      "width": 12, "height": 12,
      "strokeColor": "#3b82f6",
      "backgroundColor": "#3b82f6"
    },
    {
      "id": "event_3_label",
      "type": "text",
      "x": 220, "y": 340,
      "text": "A2UI_UPDATE → createA2UIMessageRenderer()",
      "strokeColor": "#64748b"  // 正文颜色
    }
  ]
}

设计要点:

  • 使用 line 类型作为主轴(非箭头)
  • 小椭圆点(12x12)标记事件位置
  • 自由浮动文本(无容器)作为标签
  • 用字号/颜色区分事件重要性
  • 可添加代码片段作为证据工件

模式 4:树结构(Tree)- 层级/文件系统

使用场景: 文件目录、组织结构、分类体系

实现代码结构:

{
  "elements": [
    {
      "id": "tree_trunk",
      "type": "line",
      "x": 100, "y": 50,
      "points": [[0, 0], [0, 300]],  // 垂直主干
      "strokeColor": "#1e3a5f",
      "strokeWidth": 2
    },
    {
      "id": "branch_1",
      "type": "line",
      "x": 100, "y": 100,
      "points": [[0, 0], [60, 0]],  // 向右分支
      "strokeColor": "#1e3a5f",
      "strokeWidth": 1
    },
    {
      "id": "node_1_label",
      "type": "text",
      "x": 170, "y": 95,
      "text": "src/",
      "fontSize": 16,
      "fontFamily": 3,
      "textAlign": "left",
      "strokeColor": "#1e40af"
    },
    {
      "id": "sub_branch_1a",
      "type": "line",
      "x": 160, "y": 100,
      "points": [[0, 0], [0, 80]],  // 向下的子分支
      "strokeColor": "#1e3a5f",
      "strokeWidth": 1
    },
    {
      "id": "sub_branch_1b",
      "type": "line",
      "x": 160, "y": 140,
      "points": [[0, 0], [40, 0]],  // 向右的子分支
      "strokeColor": "#1e3a5f",
      "strokeWidth": 1
    },
    {
      "id": "leaf_1_label",
      "type": "text",
      "x": 210, "y": 135,
      "text": "components/",
      "fontSize": 14,
      "strokeColor": "#3b82f6"
    }
  ]
}

设计要点:

  • 完全使用 line + 自由浮动文本,无矩形容器
  • 主干线宽 > 分支线宽(视觉层级)
  • 用字号/颜色区分层级深度
  • 分支角度一致(90° 或 45°)

分区块 JSON 构建策略详解

问题背景

LLM 输出限制: Claude Code 单次响应约 32K token,而综合技术图表的 JSON 轻松超过此限制。

错误示例(试图一次性生成):

用户:创建展示整个微服务架构的图表
AI: 开始生成 JSON...
    → 输出到 25K token 时被截断
    → JSON 不完整,无法解析
    → 用户得到损坏的文件

分区块策略实现

Phase 1: 逐区构建

第一遍响应:
{
  "type": "excalidraw",
  "version": 2,
  "elements": [
    // Section 1: Entry point / trigger
    {"id": "trigger_rect", "type": "rectangle", ...},
    {"id": "trigger_text", "type": "text", ...},
    {"id": "section1_arrow", "type": "arrow", ...}
  ],
  "appState": {...},
  "files": {}
}

第二遍响应:
[编辑现有文件,追加 Section 2]
{
  ...
  "elements": [
    // 保留 Section 1 元素
    {"id": "trigger_rect", ...},
    {"id": "trigger_text", ...},
    {"id": "section1_arrow", ...},
    // 新增 Section 2 元素
    {"id": "decision_diamond", "type": "diamond", ...},
    {"id": "section2_text", "type": "text", ...}
  ]
}

第三遍响应:
[追加 Section 3 - 主内容区]
{
  ...
  "elements": [
    // 保留 Section 1+2
    ...,
    // 新增 Section 3
    {"id": "hero_rect", ...},
    {"id": "evidence_code_snippet", ...}
  ]
}

Phase 2: 审查与修复

最终遍:
1. 检查跨区箭头绑定是否正确
   - arrow 的 startBinding 和 endBinding 是否都指向存在元素?
   
2. 检查整体间距
   - Section 1 和 Section 2 之间是否有足够空白?
   - 是否有区域过于拥挤?

3. 更新 boundElements
   - 当新元素绑定到旧元素时,更新旧元素的 boundElements 数组

关键技巧

技巧 1: 描述性字符串 ID

// ❌ 不好的 ID(无意义,难以跨区引用)
{"id": "elem_1", ...}
{"id": "elem_2", ...}

// ✅ 好的 ID(语义清晰,便于调试)
{"id": "trigger_rect", ...}
{"id": "arrow_to_service", ...}
{"id": "evidence_json_snippet", ...}

优势:

  • 跨区引用时可读性强
  • 调试时可快速定位
  • 审查时容易理解元素用途

技巧 2: Seed 值按区命名空间

// Section 1: 100xxx
{"id": "trigger_rect", "seed": 100001, ...}
{"id": "trigger_text", "seed": 100002, ...}

// Section 2: 200xxx
{"id": "decision_diamond", "seed": 200001, ...}
{"id": "decision_text", "seed": 200002, ...}

// Section 3: 300xxx
{"id": "hero_rect", "seed": 300001, ...}

优势:

  • 避免 seed 碰撞
  • 可通过 seed 值快速识别元素所属区
  • 便于按需删除/修改整区

技巧 3: 跨区绑定同步更新

// 第三遍追加 Section 3 时,新增箭头指向 Section 1
{
  "id": "arrow_cross_section",
  "type": "arrow",
  "startBinding": {"elementId": "trigger_rect", "focus": 0, "gap": 2},  // Section 1 元素
  "endBinding": {"elementId": "hero_rect", "focus": 0, "gap": 2}       // Section 3 元素
}

// 同时编辑 Section 1 的 trigger_rect,更新 boundElements
{
  "id": "trigger_rect",
  ...
  "boundElements": [
    {"id": "trigger_text", "type": "text"},
    {"id": "arrow_cross_section", "type": "arrow"}  // ← 新增跨区绑定
  ]
}

关键点: 当新元素绑定到旧元素时,必须同步更新旧元素的 boundElements 数组,否则 Excalidraw 无法正确识别关系。

渲染验证循环实现

循环流程图

┌─────────────────┐
│  生成/编辑 JSON  │
└────────┬────────┘


┌─────────────────────────────────┐
│ render_excalidraw.py            │
│   cd references &&              │
│   uv run python                 │
│   render_excalidraw.py          │
│   diagram.excalidraw            │
└────────┬────────────────────────┘


┌─────────────────┐
│  输出 diagram.png │
└────────┬────────┘


┌─────────────────────────────────┐
│ AI 使用 Read 工具读取 PNG        │
│ 视觉检查以下内容:               │
│ - 文本是否溢出容器?            │
│ - 元素是否重叠?                │
│ - 箭头路径是否交叉元素?        │
│ - 标签是否明确锚定?            │
│ - 间距是否均匀?                │
│ - 构图是否平衡?                │
└────────┬────────────────────────┘


    ┌────┴────┐
    │ 有问题? │
    └────┬────┘

    ┌────┴────┐
    │  是     │  否
    ↓         ↓
┌────────┐  ┌──────────┐
│ 修复 JSON│  │ 完成 ✓  │
│ 重新渲染│  └──────────┘
└───┬────┘

    └──────→ (循环)

典型修复场景

场景 1: 文本溢出容器

问题诊断(从 PNG 观察):
"API Gateway" 文本右侧被矩形裁剪

修复方案:
{
  "id": "trigger_rect",
  "type": "rectangle",
  "width": 180,  // ← 原 150,增加 30px
  ...
}

场景 2: 箭头穿过元素

问题诊断:
从 trigger 到 service 的箭头穿过 decision 菱形

修复方案 1(添加路径点):
{
  "id": "arrow_avoid",
  "type": "arrow",
  "points": [
    [0, 0],      // 起点
    [50, 0],     // 先向右
    [50, -80],   // 向上绕过
    [150, -80],  // 继续向右
    [150, 0]     // 向下到终点
  ]
}

修复方案 2(调整元素位置):
{
  "id": "decision_diamond",
  "y": 400  // ← 原 300,下移 100px 避开箭头路径
}

场景 3: 间距不均匀

问题诊断:
Section 1 和 Section 2 之间间距 50px,Section 2 和 Section 3 之间 200px

修复方案:
统一调整为 120px:
{
  "id": "section2_group",
  "y": 350  // ← 原 280,下移 70px
}

迭代次数统计

根据 SKILL.md 中的经验法则:

图表复杂度典型迭代次数主要修复类型
简单概念图1-2 次微调间距、文本对齐
综合技术图3-5 次文本溢出、箭头路径、跨区绑定
超大多区图5-8 次整体构图平衡、多区对齐

关键原则: 不要为了”完成任务”而提前结束循环。如果构图可以改进,就继续迭代。

证据工件实现

代码片段样式

JSON 结构:

{
  "id": "evidence_code_bg",
  "type": "rectangle",
  "x": 600, "y": 200,
  "width": 400, "height": 180,
  "strokeColor": "#1e293b",  // 深色背景
  "backgroundColor": "#1e293b",
  "fillStyle": "solid",
  "strokeWidth": 1,
  "roughness": 0,
  "roundness": {"type": 3}  // 圆角
}

代码文本(语法高亮通过颜色实现):

{
  "id": "evidence_code_line1",
  "type": "text",
  "x": 620, "y": 220,
  "text": "import { createA2UIMessageRenderer } from '@copilotkit/react-ui';",
  "fontSize": 14,
  "fontFamily": 3,  // 等宽字体
  "textAlign": "left",
  "strokeColor": "#e2e8f0",  // 浅色文本
  "backgroundColor": "transparent"
}

设计要点:

  • 深色背景(#1e293b)与浅色文本(#e2e8f0)高对比
  • 等宽字体(fontFamily: 3)模拟代码编辑器
  • 左对齐(textAlign: “left”)符合代码阅读习惯
  • 圆角矩形(roundness)现代感

JSON 数据示例样式

JSON 结构:

{
  "id": "evidence_json_bg",
  "type": "rectangle",
  "x": 600, "y": 420,
  "width": 350, "height": 140,
  "strokeColor": "#1e293b",
  "backgroundColor": "#1e293b",
  "fillStyle": "solid"
}

JSON 文本(绿色高亮):

{
  "id": "evidence_json_content",
  "type": "text",
  "x": 620, "y": 440,
  "text": "{\n  \"type\": \"RUN_STARTED\",\n  \"agentId\": \"orchestrator\",\n  \"timestamp\": 1234567890\n}",
  "fontSize": 14,
  "fontFamily": 3,
  "textAlign": "left",
  "strokeColor": "#22c55e",  // 绿色文本
  "backgroundColor": "transparent"
}

设计要点:

  • 绿色文本(#22c55e)是 JSON/数据的语义颜色
  • 使用 \n 换行符格式化 JSON
  • 保持真实数据结构(非”Input”、“Output”占位符)

事件/步骤序列样式

使用时间线模式 + 真实事件名:

{
  "elements": [
    {
      "id": "event_1_label",
      "type": "text",
      "text": "RUN_STARTED",  // ← 真实事件名(非"Event 1")
      "strokeColor": "#1e40af"  // 标题颜色
    },
    {
      "id": "event_2_label",
      "type": "text",
      "text": "STATE_DELTA",  // ← 真实事件名
      "strokeColor": "#3b82f6"  // 副标题颜色
    },
    {
      "id": "event_3_label",
      "type": "text",
      "text": "A2UI_UPDATE → createA2UIMessageRenderer()",  // ← 具体方法名
      "strokeColor": "#64748b"  // 正文颜色
    }
  ]
}

研究强制要求: SKILL.md 明确要求在生成技术图表前,必须查阅真实文档,使用实际的事件名、API 方法名、数据格式。

颜色作为实现细节

语义颜色映射算法

伪代码:

function getColorForConcept(concept):
  if concept in ["start", "trigger", "input", "entry"]:
    return palette["Start/Trigger"]  // fill: #fed7aa, stroke: #c2410c
  
  if concept in ["end", "success", "output", "result", "complete"]:
    return palette["End/Success"]  // fill: #a7f3d0, stroke: #047857
  
  if concept in ["decision", "condition", "if", "switch"]:
    return palette["Decision"]  // fill: #fef3c7, stroke: #b45309
  
  if concept in ["ai", "llm", "agent", "model"]:
    return palette["AI/LLM"]  // fill: #ddd6fe, stroke: #6d28d9
  
  if concept in ["error", "exception", "fail", "retry"]:
    return palette["Error"]  // fill: #fecaca, stroke: #b91c1c
  
  // 默认:Primary/Neutral
  return palette["Primary/Neutral"]  // fill: #3b82f6, stroke: #1e3a5f

文本层级颜色实现

三级文本系统:

层级字号颜色用途
Title20-28px#1e40af章节标题、主标签
Subtitle16-20px#3b82f6副标题、次要标签
Body/Detail14-16px#64748b描述文本、注解、元数据

实现示例:

{
  "id": "section_title",
  "type": "text",
  "text": "Backend Processing",
  "fontSize": 24,
  "strokeColor": "#1e40af"  // Title color
}
{
  "id": "section_subtitle",
  "type": "text",
  "text": "Event Streaming Pipeline",
  "fontSize": 18,
  "strokeColor": "#3b82f6"  // Subtitle color
}
{
  "id": "section_description",
  "type": "text",
  "text": "Events are streamed from the AI agent to the frontend via AG-UI protocol.",
  "fontSize": 14,
  "strokeColor": "#64748b"  // Body color
}

优势:

  • 无需容器即可创建视觉层级
  • 颜色编码帮助快速扫描
  • 减少 70% 以上的矩形使用

<30% 容器化原则实现

判断是否使用容器的决策树

文本是否需要容器?

├─ 是章节焦点/视觉锚点? → 使用矩形

├─ 需要与相邻元素视觉分组? → 使用框架或共享背景

├─ 箭头需要连接到此文本? → 使用小椭圆标记点(12x12)

├─ 文本本身是决策/条件? → 使用菱形

└─ 以上都不是 → 自由浮动文本(无容器)

实践统计

根据 SKILL.md 的 27 点质量检查清单第 20 条:

Container ratio: <30% of text elements should be inside containers

典型综合图表元素统计:

总文本元素:50 个
- 自由浮动文本:38 个(76%)
- 容器内文本:12 个(24%)✓ 符合<30% 要求

容器类型分布:
- 矩形:8 个(焦点元素、证据工件背景)
- 菱形:2 个(决策点)
- 椭圆:2 个(起始/结束点)

替代容器的技术

技术 1: 结构线

// 使用时间线而非带框的步骤列表
{
  "id": "timeline_spine",
  "type": "line",
  "points": [[0, 0], [0, 500]]
}
// 自由浮动文本作为标签(无容器)

技术 2: 小标记点

// 使用 12x12 椭圆点而非大矩形框
{
  "id": "bullet_dot",
  "type": "ellipse",
  "width": 12, "height": 12,
  "strokeColor": "#3b82f6",
  "backgroundColor": "#3b82f6"
}
// 自由浮动文本在点右侧

技术 3: 留白分隔

不使用分隔线或背景框,而是通过留白创建视觉分组:

Section 1 (y: 100-250)
    ← 120px 留白 →
Section 2 (y: 370-520)
    ← 120px 留白 →
Section 3 (y: 640-790)

本章小结

核心实现技巧:

  1. 分区块 JSON 构建 - 描述性 ID、seed 命名空间、跨区绑定同步
  2. 渲染验证循环 - 典型 3-5 次迭代,修复文本溢出/箭头路径/间距
  3. 证据工件 - 深色背景代码片段、绿色 JSON 数据、真实事件名
  4. 语义化颜色 - 概念→颜色映射算法,文本三级层级
  5. <30% 容器化 - 自由浮动文本优先,结构线/标记点替代

设计模式总结:

  • 扇出/收敛模式用于关系可视化
  • 时间线/树结构用于序列/层级
  • 证据工件增强教学价值
  • 颜色编码信息而非装饰

下一章将分析实际应用场景和用例。