写在前面:当“画图”遇上“对话”
有多少次,你在白板前苦苦拉线、摆方框,只为赶一张架构图?Next AI Draw.io 把这件事变成聊天:和 AI 说一句“给我画个带动画连线的 Transformer 架构”,几秒后 draw.io 里就出现一张能动的图。本文不是普通的“项目介绍”,而是一篇把技术架构、关键实现、使用心得、未来想象揉在一起的“拆机文”。没有堆砌术语的刻板腔,也没有流水账式的步骤,而是站在开发者视角,带你看清这套“Chat + Draw.io”组合拳的设计取舍与边界。
目标读者:前端工程师、全栈爱好者、架构师,以及任何需要高效产出图表的人。你会看到源码级的要点,也能读到一些“小吐槽”和“彩蛋”。
项目背景:为什么要把 AI 塞进 draw.io?
传统画图的痛点很简单:拖拽慢、复用难、协作麻烦。LLM 能用文字描述结构,但输出的 XML 一旦结构不对就“光速炸裂”。Next AI Draw.io 选了一个折中方案:
保留 draw.io 的成熟 UI,避免重复造轮子;
让 LLM 只做“生成/编辑 XML”这件擅长的事;
用严谨的校验、缓存和小工具兜底,防止“模型发挥过猛”。
结果就是:用户在聊天窗口里描述需求,AI 生成符合规范的 draw.io XML,前端即时渲染;如果模型“手抖”,还有 edit_diagram 精准替换、合法化修复和历史回退兜底。
整体架构一张图(文字版)
UI 层(Next.js 15 App Router):左侧 draw.io 画布 + 右侧聊天面板,支持响应式、可折叠、主题切换。
状态层(React Context):
DiagramContext统一管理 XML、SVG、历史、导入导出和 draw.io Ref。AI 交互层(Vercel AI SDK):
/api/chat流式对话,工具调用display_diagram与edit_diagram驱动前端渲染和增量修改。多云模型层:一套
lib/ai-providers.ts动态选择 OpenAI/Claude/Gemini/Bedrock/Azure/Ollama/OpenRouter/DeepSeek/SiliconFlow。防炸弹策略:XML 结构校验、Bedrock 工具输入修正、缓存命中、异常重试、文件体积限制。
可观察性:Langfuse 埋点、sessionId 追踪、feedback API,方便线上巡检。
核心体验路径:从一句话到一张动态图
输入:在聊天框输入“用 AWS 图标画个三层 Web 架构”,可附带示例图片。
预处理:前端先导出当前 XML,格式化后作为上下文发送,附带 sessionId 与(可选)access code。
模型生成:后端把系统提示 + 现有 XML + 用户消息投喂给模型;若首条消息为空画布,则尝试命中本地缓存,直接回写示例图。
- 工具调用:
display_diagram:一次性生成完整 XML。edit_diagram:按 search/replace 精准替换,降低“全量重绘”风险。
前端渲染:
ChatMessageDisplay拦截工具输出,调用replaceNodes把<root>片段嵌回完整<mxfile>,校验通过后加载到 draw.io。历史与存档:每次用户提问前都会快照 XML,可回滚、重试或导出为 drawio/png/svg。
关键模块深潜
1) DiagramContext:一处管理 XML、历史与导出
contexts/diagram-context.tsx把所有与图相关的状态集中到 Context:chartXML、latestSvg、历史栈、导出/导入、清空、文件保存。最实用的设计是“导出前标记历史”和“跳过校验的内部模板加载”,保证回滚和清空都很顺滑。
<DiagramContext.Provider value={{ chartXML, latestSvg, diagramHistory, loadDiagram, handleExport, handleExportWithoutHistory, resolverRef, drawioRef, handleDiagramExport, clearDiagram, saveDiagramToFile, isDrawioReady, onDrawioLoad, }} > {children} </DiagramContext.Provider>为什么重要?
全局可复用:chat 面板、历史弹窗、导出组件都能读写同一份状态。
导出即校验:
loadDiagram默认校验 XML 结构,避免脏数据污染画布。历史安全网:每次用户提问前先快照 XML,失败可回滚。
2) Chat 面板:工具驱动的流式对话
components/chat-panel.tsx使用 Vercel AI SDK 的useChat,绑定两个“工具”:
display_diagram: { description: `Display a diagram on draw.io. Pass the XML content inside <root> tags. ... inputSchema: z.object({ xml: z .string() .describe("XML string to be displayed on draw.io"), }), }, edit_diagram: { description: `Edit specific parts of the current diagram by replacing exact line matches...前端在onToolCall中区分两个工具:
display_diagram:直接加载 XML,校验失败会把错误写回模型,触发自动重试。edit_diagram:先取本地缓存的 XML,再按 search/replace 局部替换,失败同样返回可读的错误。
async onToolCall({ toolCall }) { if (toolCall.toolName === "display_diagram") { const { xml } = toolCall.input as { xml: string } const validationError = onDisplayChart(xml) ... } else if (toolCall.toolName === "edit_diagram") { const { edits } = toolCall.input as { edits: Array<{ search: string; replace: string }> } ... const editedXml = replaceXMLParts(currentXml, edits) const validationError = onDisplayChart(editedXml) ... } },技术亮点
sendAutomaticallyWhen:工具完成后自动二次提交,模型可根据错误重试。
本地快照优先:避免频繁导出 iframe,兼容 Vercel 部署的跨域/时序问题。
存储持久化:messages、XML snapshots、sessionId 都存进 localStorage,刷新不丢进度。
3) XML 安全网:格式化、替换、合法化、校验
AI 生成的 XML 最怕结构错乱。lib/utils.ts组合了四把“扳手”:
formatXML:统一缩进,方便后续行级搜索替换。replaceXMLParts:支持多策略匹配(精确、裁剪、字符频率、按 id/value、归一化空白),极大提升 edit_diagram 的成功率。convertToLegalXml:过滤非法<mxPoint>、补齐<root>,避免 draw.io 报错。validateMxCellStructure:查重 ID、父子引用、边的 source/target、孤儿节点等,一旦发现问题返回可读提示。
export function replaceXMLParts( xmlContent: string, searchReplacePairs: Array<{ search: string; replace: string }>, ): string { // Format the XML first to ensure consistent line breaks let result = formatXML(xmlContent) ...export function validateMxCellStructure(xml: string): string | null { const parser = new DOMParser() const doc = parser.parseFromString(xml, "text/xml") ... const duplicateIds: string[] = [] const nestedCells: string[] = [] const orphanCells: string[] = []为什么能打动架构师?
编辑器里“炸一次”代价巨大。这里的校验与修复让模型的“不确定性”被圈在笼子里:先修正,再替换,再校验,再渲染,失败则提示模型重来。用户感知就是“要么成功,要么得到精确错误信息”。
4) 后端 /api/chat:流式、缓存、Bedrock 兼容
app/api/chat/route.ts是整个“AI 调度室”:
缓存:首条空画布请求会根据提示词命中内置示例,直接返回 UI Stream,零延迟出图。
访问控制:可选的
ACCESS_CODE_LIST,用 headerx-access-code校验。Bedrock 兼容:工具输入强制 JSON 对象,空 content 过滤,providerOptions 注入 cachePoint。
系统消息分层缓存:Instruction 与当前 XML 分别作为 system 消息,便于 Bedrock 二级缓存。
const { model, providerOptions, headers, modelId } = getAIModel() ... const systemMessages = [ { role: "system", content: systemMessage, providerOptions: { bedrock: { cachePoint: { type: "default" } } } }, { role: "system", content: `Current diagram XML:\n"""xml\n${xml || ""}\n"""...`, providerOptions: { bedrock: { cachePoint: { type: "default" } } } }, ] const allMessages = [...systemMessages, ...enhancedMessages] const result = streamText({ model, stopWhen: stepCountIs(5), messages: allMessages, ... })工程感十足的细节
experimental_repairToolCall:自动转义 JSON 里未转义的引号,降低模型“劣化”带来的解析失败。Langfuse:输入输出、token 统计、用户 IP/sessionId 统统记录,方便线上排障。
文件限制:2MB、5 张,前后端一致校验,防止大文件拖垮请求。
5) 多云模型抽象:一键切 Provider
lib/ai-providers.ts通过环境变量自动检测或显式指定 Provider,统一返回model、providerOptions、headers。支持自定义 Base URL(OpenAI、Anthropic、Google、Azure、DeepSeek、SiliconFlow),也支持本地 Ollama。
export function getAIModel(): ModelConfig { const modelId = process.env.AI_MODEL ... switch (provider) { case "bedrock": { ... } case "openai": { ... } case "anthropic": { ... } ... } return { model, providerOptions, headers, modelId } }业务价值
云上 / 本地双模:开发期可用 Ollama 降本,生产切云厂商提质量。
Beta header 兼容:为 Anthropic/Bedrock 准备了细颗粒度工具流式 beta 标识,少踩官方坑。
使用指南:从零到跑起来
1) Docker 三行起步(推荐)
docker run -d -p 3000:3000 \ -e AI_PROVIDER=openai \ -e AI_MODEL=gpt-4o \ -e OPENAI_API_KEY=your_api_key \ ghcr.io/dayuanjiang/next-ai-draw-io:latest访问http://localhost:3000即可。想切换模型,改环境变量就行;想限制访问,加ACCESS_CODE_LIST=your_password。
2) 本地开发
git clone https://github.com/DayuanJiang/next-ai-draw-io npm install cp env.example .env.local # 按需填 AI_PROVIDER/AI_MODEL/KEY npm run dev3) 界面小技巧
Ctrl+B折叠/展开聊天区,画布更大。主题切换(Minimal/Sketch)会重载编辑器,记得先保存。
上传图片时别超过 2MB、5 张;粘贴图片同样受限。
聊天失败?看右上角 Settings,补充 Access Code 或更换模型。
应用场景:不仅是“自动画图”,更像“实时白板搭档”
架构评审:把“写在 PR 描述里”的架构草图变成可视化,讨论时实时改动。
需求澄清:产品经理口头描述,AI 生成用户旅程/泳道图,减少误解。
SRE 故障复盘:导入监控拓扑截图,让 AI 补全节点属性、标注事件时间线。
培训/分享:用示例提示词快速生成云厂商架构模板,讲解时随问随改。
创意草图:Sketch 主题+手绘风格,适合头脑风暴的“草稿墙”。
深挖三个“关键设计选择”
“全量生成”+“局部编辑”双轨
新图用display_diagram,改图用edit_diagram精准替换,避免大模型每次重绘导致的 ID 重复、父子错位。匹配策略多样化(精确/字符频率/按 id/value)让编辑成功率大幅上升。系统消息分层缓存
把“通用指令”和“当前 XML”拆成两条 system 消息,给 Bedrock/OpenAI 更多缓存机会——用户只改提示词时,指令+XML 都可复用;改 XML 时,指令仍可命中缓存,显著降低延迟与费用。前后端协同的“安全带”
文件大小、数量前后端一致校验;XML 结构先本地校验再渲染;出错后消息自动重投;Langfuse 记录链路。用户看到的是“稳”,开发者得到的是“可观测”。
常见问题与避坑
“生成失败”多数是 XML 格式问题:模型输出里有未转义字符或 mxCell 嵌套,校验会给出明确提示;让模型重试或改用 edit_diagram。
“没反应”可能是 Access Code:部署时建议设置
ACCESS_CODE_LIST,前端会在被拒时弹出设置。长对话“抽风”:聊天前会快照 XML,回滚后再提问,可消除上下文漂移。
文件过大:前端在选文件时就会 toast 报错,不会等到请求阶段才失败。
未来可期:我想看到的三个升级
UI 端模型配置:TODO 已在项目里标记,允许非技术用户在界面切模型、改温度、换 Endpoint。
团队协作与共享历史:把本地历史搬到服务端,支持“分享链接 + 只读/可编辑”。
更多“工具”而非“提示”:比如一键对齐网格、批量换图标主题、自动生成图例,减少 LLM“语言到布局”的负担。
结语:让 AI 做擅长的事,让人类少搬砖
Next AI Draw.io 没尝试“重写一个绘图器”,而是把 LLM 放在“会写 XML”这条最优路线上,再用校验、缓存、历史、工具来包裹不确定性。最终效果是:你像聊天一样描述,像魔术一样成图,还能像程序员一样精确微调。
这篇文章尝试在技术深度与可读性之间做平衡,希望你读完不仅知道“怎么跑”,更理解“为什么这样设计”。如果你也厌倦了拉框连线,不妨把它拉起来跑一跑——也许,下次评审会上你会成为那个“最会画图的人”。
更多AIGC文章
RAG技术全解:从原理到实战的简明指南
更多VibeCoding文章