Hermes Trilium Draw 架构图生成 · Skill+MCP 完全剖析
一、数据流总览:四层管道
二、格式链深度解剖 (Format Chain)
2.1 用户意图 → Mermaid → Visual Design
这是最短路径:用户描述架构意图 → Hermes 调用 mcp_drawio_open_drawio_mermaid(mermaid语法) → 浏览器在 draw.io 编辑器中自动打开一个 Mermaid-to-drawio 转换后的草图。
Mermaid 支持的全部 26 种图类型:
- 流程图:
graph TD / flowchart LR - 时序图:
sequenceDiagram - 类图:
classDiagram - 状态图:
stateDiagram-v2 - ER 图:
erDiagram - C4 模型:
C4Context - 架构蓝图:
architecture-beta - ……等
关键认识:Mermaid 只是起点草图——@drawio/mcp 将 Mermaid 转换为 draw.io 可编辑元素后,用户在编辑器中进行视觉精调(拖组件、改颜色、布线、调间距),最终导出。
2.2 导出格式解码:SVG + content 属性的双层结构
draw.io 导出的 SVG(标准 XMLSVG 格式)是一种数据 + 渲染双层格式:
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg"
version="1.1" width="1100px" height="600px"
viewBox="0 0 1100 600"
content="<mxfile host="embed.diagrams.net" ...>
<diagram name="Page-1" id="xxx">
<mxGraphModel>
<root>
<mxCell id="0"/>
...
</root>
</mxGraphModel>
</diagram>
</mxfile>">
<defs/>
<g data-cell-id="0">
<g data-cell-id="1">
<rect x="30" y="20" width="500" height="600" .../>
<text ...>服务器面板</text>
</g>
<g data-cell-id="2">
<rect x="50" y="70" width="460" height="50" .../>
<text ...>Nginx</text>
</g>
</g>
</svg>
| 层 | 作用 | 消费者 |
|---|---|---|
| SVG body(可见层) | W3C SVG 1.1 标准矢量图形——矩形、文字、线条、箭头。包含 data-cell-id 属性映射到 mxCell 的 id。 | CKEditor5 预览、共享页面渲染 |
| content 属性(数据层) | HTML 编码的 mxGraph XML(完整 draw.io 文件结构)。包含 <mxfile> → <diagram> → <mxGraphModel> → <root> → <mxCell> 的完整层级。所有值、样式、几何信息、连线的原始数据。 | draw.io 编辑器双击打开、编辑后保存 |
核心设计思想:SVG body 是人类可见的渲染结果,content 属性是机器可读的编辑源数据。两者通过 data-cell-id 属性一一对应。这实现了「预览即渲染图、双击即编辑器」的双重能力。
2.3 格式兼容性
Trilium 使用的格式 = draw.io 标准导出格式(不是 Trilium 私有格式):
- draw.io 桌面 CLI:
drawio -x -f svg --embed-diagram file.drawio - draw.io Web UI: 文件 → 导出为 → SVG(保持「嵌入图表数据」打开)
- draw.io embed API:
format: 'xmlsvg'
三种途径的输出可以直接存入 Trilium,无需转换。
2.4 多页图表
一个 SVG 文件中可以包含多个 <diagram> 元素(即多页):
content="<mxfile>
<diagram name="当前架构·组件拓扑" id="current">...</diagram>
<diagram name="目标架构·解耦后" id="target">...</diagram>
</mxfile>"
SVG body 只渲染第一个 <diagram>(第 1 页)。Trilium CKEditor5 预览也只显示第 1 页。用户双击进入 draw.io 编辑器后可以看到全部页面并在页面间切换。
三、@drawio/mcp 工具三兄弟
| 工具 | 入口 | 输入 | 场景 |
|---|---|---|---|
open_drawio_mermaid | Mermaid 语法 | 流程图/时序图/ER图等文本描述 | 最常用——用户描述意图 → Hermes 写 Mermaid → 浏览器编辑 |
open_drawio_xml | mxGraph XML | 完整的 draw.io XML 数据 | 编辑现有 Trilium 图(extract-xml.py 提取) |
open_drawio_csv | CSV 表格 | 结构化表格数据 | 组织结构图/ER 图从表格生成 |
所有三个工具都有共同的辅助参数:
lightbox:只读模式(禁止编辑)darkMode:深色模式
执行机制:@drawio/mcp 在宿主机(Hermes Docker 容器)上打开一个 Chrome/Chromium 浏览器标签页,导航到 embed.diagrams.net 并传入数据。用户在浏览器中编辑后,通过「导出为 SVG」获得文件。
四、Trilium 原生 draw.io 笔记的三要素
要让一张 svg 文件成为 Trilium 中可双击编辑的 draw.io 原生笔记,必须满足三个条件:
TBD: 需要生成三要素的单图
为什么是这三个条件?
- type=image:Trilium 将 note 视为图片资源,可以在 CKEditor5 中通过
<img src="api/images/{id}/{title}">引用。不允许用旧版type='file'——那会导致 1×1px 占位符。 - mime=image/svg+xml:告诉 Trilium 这是一个 SVG 格式的矢量图,而非普通图片。
- 标题以 .drawio.svg 结尾:触发 Trilium 的 draw.io 插件识别。插件扫描笔记树,找到所有标题以 .drawio.svg 结尾且 type=image 的笔记,在双击时打开 draw.io 编辑器(而非 Trilium 内置图片查看器)。
三要素的协作验证
Trilium 检出标题 → ".drawio.svg" 结尾?
├─ 否 → 图片查看器
└─ 是 → 检查 type=image + mime=image/svg+xml?
├─ 否 → 提示格式不兼容
└─ 是 → 从 content 属性提取 mxGraph XML
→ 调用 draw.io widget API 打开编辑器
→ 编辑器加载完整图表(所有页面、样式、连线)
→ 用户编辑后保存 → Trilium 接收新 SVG 写入笔记
五、模板机制与 Widget 插件
5.1 两层结构
┌─ draw.io 插件笔记 (type=code, mime=application/javascript;env=frontend)
│ ├── #widget 标签 → 声明这是前端插件
│ └── #run=frontendStartup → 在 Trilium 启动时加载
│
├── 模板笔记 (子笔记) ⬅ #widget 的子笔记
│ ├── type=image, mime=image/svg+xml
│ ├── 标题 = 任意.svg
│ ├── #template → 声明这是一个模板
│ └── #originalFileName=drawio.svg → 声明文件类型
│
└── 用户创建的 draw.io 笔记(引用模板)
├── template relation → 模板笔记 ID
└── 继承模板的预览样式和编辑能力
5.2 模板 = 新笔记的蓝本
当 draw.io 笔记设置了 template relation 指向模板笔记时:
- 双击编辑时,编辑器从 content 属性加载数据
- 如果 content 属性为空(新建空图),Trilium 会从模板笔记复制初始 SVG 内容作为起点
- 模板可以包含预设的样式默认值(画布尺寸、颜色主题等)
5.3 Widget 插件的生命周期
① Trilium 启动 → 扫描 #widget 标签
② 加载 JS 插件到前端
③ 插件注册 draw.io 编辑器入口:
监听笔记双击事件 → 检查三要素 → 匹配时拦截默认行为
打开 draw.io 编辑器 iframe
④ 保存时:插件从 iframe 取得新 SVG → 通过 Trilium API 写入笔记
六、imageLink 嵌入模式详解
6.1 为什么需要 imageLink?
在 Trilium 中,draw.io 子笔记是一个独立的图片资源。要在母笔记的 HTML 内容中显示这张图,需要两个步骤:
- 元数据关联:在母笔记上创建
imageLinkrelation,指向 draw.io 子笔记。这建立了「母笔记包含此图片」的语义关系。 - HTML 引用:在母笔记的 HTML 内容中添加
<img>标签,通过 Trilium 的内部图片 API 加载。
6.2 完整嵌入步骤
① 创建 draw.io 子笔记
└── 得到 noteId = "gm4DzEp30gcq"
② 在母笔记上设置 imageLink relation
mcp_trilium_manage_attributes(
action="create",
noteId="母笔记ID",
type="relation",
name="imageLink",
value="gm4DzEp30gcq"
)
③ 在母笔记 HTML 中插入 img 标签
<img src="api/images/gm4DzEp30gcq/架构图·系统拓扑.drawio.svg"
style="max-width: 100%; height: auto;" />
④ 可选:设置 template relation
mcp_trilium_manage_attributes(
action="create",
noteId="gm4DzEp30gcq",
type="relation",
name="template",
value="U8Z4DeGS5V7T" // 模板笔记 ID
)
6.3 API 路径语义
api/images/{noteId}/{title} 是 Trilium 内部图片服务端点:
{noteId}:draw.io 子笔记的 ID{title}:实际输出时可以是任意字符串(Trilium 主要用 noteId 定位)- 返回:SVG 文件的原始内容(Content-Type: image/svg+xml)
6.4 共享页面的渲染
共享页面(#shareAlias)中的 api/images/{id}/{title} 路径会被 Trilium 的 ETAPI 代理正确渲染。因此设置了 #shareAlias 的母笔记,其中的 draw.io SVG 图片在共享页上也会正常显示。
七、三种工作流的深度对比
7.1 创建新图(从零)
用户: "画个系统架构图"
│
▼
① Hermes 调用 open_drawio_mermaid("graph TD; A-->B")
→ 浏览器打开 draw.io 编辑器
→ 用户拖拽布局、调颜色、布线
→ 导出 SVG → 发回聊天
│
▼
② Hermes 用 MCP API 创建笔记:
mcp_trilium_create_note(
parentNoteId="...",
title="系统架构.drawio.svg", ← 三要素①
type="image", ← 三要素②
mime="image/svg+xml", ← 三要素③
content=svg_content
)
│
▼
③ Hermes 设置关联:
- template relation → 模板笔记
- imageLink relation → 母笔记
- 母笔记 HTML 插入 <img>
│
▼
④ 用户在 Trilium 中双击 → draw.io 编辑器打开 → 可再次编辑
7.2 编辑已有图
用户: "改一下架构图"
│
▼
① Hermes 读取笔记 SVG 内容
│
▼
② 从 SVG 的 content 属性提取 mxGraph XML
extract-xml.py: re.search(r'content="([^"]*)"', svg)
→ unescape HTML entities → mxGraph XML
│
▼
③ Hermes 调用 open_drawio_xml(xml=mxGraph_xml)
→ 浏览器打开编辑器,加载现有图表
→ 用户在 draw.io 中修改
→ 导出 SVG → 发回聊天
│
▼
④ Hermes 用 MCP API 更新笔记
mcp_trilium_update_note(
noteId="...",
content=new_svg_content ← 新 SVG(保持旧标题/关联不变)
)
7.3 导入 .drawio 文件
用户: 上传了一个 diagram.drawio 文件
│
▼
① Hermes 处理:
make-trilium-svg.py diagram.drawio > note.svg
原理: 读取 .drawio 的 mxGraph XML
→ 创建最小 SVG shell
→ 将 XML 写入 content 属性
│
▼
② 同创建流程的②③④步:
创建 Trilium 笔记 + 设关联
八、编码链陷阱(最易出错的环节)
8.1 编码层次
从 mxGraph XML 到最终存储在 Trilium 中的 SVG content 属性,经过了 三层编码嵌套:
原始 mxGraph XML:
<mxCell id="1" value="Trilium\nDocker" style="..." />
│
▼ XML-SAX escape + HTML entity encoding
content 属性(单行 HTML 编码):
<mxCell id="1" value="Trilium\nDocker" style="..." />
│
▼ SVG XML 中的属性值
<svg content="&lt;mxCell id=&quot;1&quot; ...">
│
▼ 存储到 Trilium SQLite
每一层 decode 都有可能出错。如果任何一层多 decode 或少 decode,就会出现 "非绘图文件" 或 "Unescaped '<' not allowed" 错误。
8.2 \n vs <br> 编码陷阱
这是经过实战验证的最常见问题:
| 方式 | style 中 | value 中 | 编码复杂度 | 结果 |
|---|---|---|---|---|
| ❌ html=1 + <br> | html=1 | Trilium<br>Docker | 三重编码 | 极易在某层解码出错 |
| ✅ 无 html=1 + \n | 不含 html=1 | Trilium\\nDocker (字面反斜杠+n) | 零编码问题 | 可靠稳定 |
原理:\n 是两个安全 ASCII 字符(反斜杠 + 小写 n),在 XML 属性值中不需要转义。而 <br> 是 HTML 标签,在 XML 属性中必须被转义为 <br>,经过多层嵌套后编码链极易断裂。
关键事实:即使用 \n 模式创建笔记,用户在 draw.io 编辑器中双击保存一次后,Trilium 会自动将格式转换为 html=1 + <br>。这是因为 draw.io 编辑器自己的序列化引擎偏好 html=1。但这没关系——因为编辑过程在 draw.io 内部完成,编码链由 draw.io 自己负责。只要初始创建时用 \n 避开编码陷阱即可。
8.3 其他格式要求
| 要求 | 正确值 | 错误代价 |
|---|---|---|
host 属性 | embed.diagrams.net | 保存失败 |
| User-Agent | 真实浏览器 UA(如 Mozilla/5.0 ...) | 保存可能失败 |
| content 属性 | 单行(无 
、无 <?xml?>) | "非绘图文件" 错误 |
data-cell-id | 每个 SVG <g> 必须有对应 mxCell id | 编辑器显示旧内容 |
| 页面数 | SVG body + content 中 exactly 1 页(或多页但只有第1页渲染) | 预览≠编辑器 |
| SVG body 有实际图形 | 含 <rect><text><line><polygon> 等 | 空白预览 |
九、手写生成模式(Python 脚本)
9.1 适用场景
当需要批量生成、程序化生成或一致性高的架构图时,可用 Python 脚本在 /tmp/ 中手写 mxGraph XML 并包装为 Trilium SVG。这种方式在 @drawio/mcp 出现之前是主力方式,现在由 @drawio/mcp + 人工设计替代。但理解此方式有助于深刻理解格式本质。
9.2 关键数据结构
mxCell 层级树:
id="0" ← 根节点(root),所有节点的 parent
id="1" ← 画布节点(mxCell parent=0, 无几何)
id="2" ← 实际图形(parent=1, 有几何+样式+值)
id="3"
...
几何表示:
mxGeometry x="30" y="20" width="500" height="600"
as="geometry"(顶点/容器)
as="geometry" relative="1" + sourcePoint + targetPoint(连线)
连线(edge):
edge="1"
mxGeometry relative="1"
mxPoint as="sourcePoint"(起点)
mxPoint as="sourcePoint"(中途点,可多个)
mxPoint as="targetPoint"(终点)
9.3 容器嵌套(视觉 vs 逻辑)
⚠️ 重要区别:mxGraph 的父容器关系有两种:
- 逻辑父容器(
mxCell parent="id"):决定图的层级结构和组织方式。所有元素最终父容器为 id="1"(画布)。 - 视觉父容器:子元素在父元素框内展示,通过
<mxGeometry x="相对偏移" y="...">实现。但 mxCell 的 parent 仍指向 id="1"。
容器面板和内部组件在 mxGraph 中不设真正的 parent-child 关系——所有 mxCell 的 parent 都设为 "1",只靠坐标位置(x,y)落在面板区域内部来实现视觉包含。这样做避免了 ID 索引和层级解析的复杂性。
9.4 多页生成架构
每个 <diagram> 元素 = 一页
每个 <diagram> 包含完整的 <mxGraphModel> → <root>
所有 <diagram> 包裹在 <mxfile> 中
结构:
<mxfile host="embed.diagrams.net" ...>
<diagram name="当前架构" id="page1">
<mxGraphModel>
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="2" parent="1" .../>
</root>
</mxGraphModel>
</diagram>
<diagram name="目标架构" id="page2">
...(mxCell id 继续自增,不重置)
</diagram>
</mxfile>
SVG body 只渲染第一个 <diagram>。用户双击进入编辑器后可看到所有页面。
十、与字符架构图的并行关系
Trilium 中有两种架构图绘制风格,各有优劣且互不依赖:
| 维度 | Draw.io SVG 架构图 | 字符架构图(Unicode 框线) |
|---|---|---|
| 渲染方式 | W3C SVG 矢量图形 | <pre><code> 中的 Unicode 框线字符 |
| 表达能力 | 无限——颜色、渐变、虚线、圆角、箭头样式、图标 | 有限——单色、框线 + 文字、无图形 |
| 可编辑性 | 双击进入 draw.io 编辑器 | 直接修改 HTML 源码 |
| 跨设备一致性 | 完全一致(SVG 是精确渲染) | 受字体影响(CJK 对齐需要精准计算宽度) |
| 生成方式 | @drawio/mcp 浏览器编辑器 + 导出 SVG | Python vw()/pad() 计算 + 断言验证 |
| 生成复杂度 | 低(用户拖拽) | 高(需程序化计算每行视觉宽度) |
| 共享页兼容性 | 好(SVG 由浏览器原生渲染) | 受 shareCss 的 font-mono 配置影响 |
| 适合场景 | 系统架构、拓扑、部署图、流程图 | 简单的流程/层级结构、代码随附文档 |
| 技能参考 | trilium-draw-architecture-flowchart | trilium-architecture-diagram-conventions |
选择建议:
- 展示给他人(共享页面、报告)→ draw.io SVG,专业美观
- 随代码快速记录思路 → 字符架构图,无需离开编辑器
- 需要精确颜色/图标 → draw.io SVG
- 纯文本可搜索/可嵌入代码注释 → 字符架构图
十一、运维与排障体系
11.1 完整验证清单
创建后验证:
[✓] 笔记 type=image, mime=image/svg+xml
[✓] 标题以 .drawio.svg 结尾
[✓] template relation → 模板笔记
[✓] imageLink relation → 母笔记
[✓] 母笔记 HTML 中有 <img src="api/images/{id}/{title}">
[✓] CKEditor5 预览有实际图形(非空白)
[✓] 双击子笔记 → 打开 draw.io 编辑器
[✓] 编辑后保存 → 图片更新
[✓] 共享页面 → 图片正常渲染
11.2 故障诊断矩阵
| 现象 | 根因 | 解决 |
|---|---|---|
| CKEditor5 预览空白 | SVG body 无实际 <rect>/<text> 等渲染元素 | 用 draw.io 导出替代手写 |
| 有矩形框无文字 | 旧版手动生成缺 text 元素 | 重新导出(draw.io 自动生成完整 SVG body) |
| "非绘图文件" 错误 | content 属性格式不符:host 不是 embed.diagrams.net、含 xml? 头、多行编码 | 确保 content 单行、host 正确、无 xml 头 |
| 双击打开编辑器但内容空/旧 | data-cell-id 不匹配 / html=1 编码链断裂 | 检查 data-cell-id 与 mxCell id 对应;用 \n 替代 br |
| 共享页图片断裂 | shareCss 的 font-mono 覆盖 <pre> 字体 | 从 shareCss 删除 font-family: var(--font-mono) |
| 编辑保存后不更新 | 浏览器缓存旧 SVG | Ctrl+Shift+R 强刷新 |
| 母笔记不显示图片 | 缺少 imageLink relation 或 <img src> 路径错误 | 检查 relation + HTML 中的 noteId 正确 |
十二、系统架构图:三层容器层次模式
12.1 模式定义
针对多服务器、多 Docker 容器的架构展示场景,定义了「服务器 → Docker 容器 → 程序内组件」三层的视觉风格:
| 层 | 视觉样式 | 颜色方案 | 边框 |
|---|---|---|---|
| 服务器 | 大虚线面板 | #e8eaf6 浅紫背景, #7986cb 紫边框 | dashed=1, rx=8 |
| Docker 容器 | 中实线面板 | #e0f2f1 浅绿背景, #4db6ac 绿边框 | rx=8 |
| 程序内组件 | 小实线框 | #f5f5f5 灰背景, #9e9e9e 灰边框 | rx=8 |
12.2 布局策略
画布: 1100×650px
左栏 (ghost.atibm.com):
├── 服务器面板: x=30, y=20, w=500, h=600
│ ├── Nginx 代理: x=50, y=70, w=460, h=50
│ ├── Trilium Docker: x=50, y=150, w=460, h=260
│ │ ├── 组件 A: 190×40px @ x=65
│ │ └── 组件 B: 190×40px @ x=270
│ └── ...
右栏 (arm.atibm.com):
├── 服务器面板: x=570, y=20, w=500, h=600
│ ├── Hermes Docker: x=590, y=70, w=460, h=310
│ └── ...
组件间连线:
── 正交折线 (edgeStyle=orthogonalEdgeStyle)
── 颜色: 蓝=MCP, 橙=gbrain, 紫=Telegram, 绿=反向代理
── 标签在连线中点
十三、工具生态总结
13.1 Hermes 侧的辅助脚本
| 脚本 | 路径 | 功能 |
|---|---|---|
make-trilium-svg.py | skills/.../scripts/ | .drawio 文件 → Trilium SVG |
extract-xml.py | skills/.../scripts/ | Trilium SVG → mxGraph XML(编辑用) |
13.2 涉及的服务
| 服务 | 角色 | 通信方式 |
|---|---|---|
| Hermes Agent | 编排层——加载 skill、调用 MCP、处理 SVG、写入 Trilium | 本地进程 |
| @drawio/mcp | MCP 服务器——打开浏览器编辑器 | stdin/stdout JSON-RPC |
| draw.io (浏览器) | 图形编辑引擎——用户设计 SVG 导出 | HTTP (embed.diagrams.net) |
| Trilium (Docker) | 知识存储——SVG + mxGraph XML → SQLite | HTTP ETAPI / MCP API |
| trilium-mcp-server | MCP 网关——统一 API 访问层 | stdin/stdout JSON-RPC |
13.3 完整绘制流程总结
描述意图
└─→ Hermes 写 Mermaid 脚本
└─→ @drawio/mcp 打开浏览器编辑器
└─→ 用户在 draw.io 中设计精修
└─→ 导出 SVG → 发回聊天
└─→ Hermes 用 MCP API 创建 Trilium 笔记
├─ type=image, mime=image/svg+xml, 标题.drawio.svg
├─ template relation → 模板笔记
├─ imageLink relation → 母笔记
└─ 母笔记 HTML 添加 <img>
└─→ 双击可编辑 → 保存即更新
十四、附录:相关笔记与技能
- 技能:
trilium-draw-architecture-flowchart— 通过 @drawio/mcp 创建/编辑 draw.io 笔记 - 技能:
trilium-architecture-diagram-conventions— 字符架构图规范(并行选项) - 参考:
container-hierarchy-pattern.md— 三层容器层次架构图模式(服务器→Docker→组件) - 参考:
drawio-encoding-debug-transcript.md— 编码链调试记录(\n vs <br> 陷阱定位过程) - 参考:
drawio-architecture-diagrams-example.md— 手写生成示例(多页、连线模式、MCP API 写入)
本文编写于 2026-06-02。