AI+Excalidraw,用自然语言画手绘风格技术图

✨前言

Hi,我是松柏!

不知道大家有没有用过 Excalidraw ,Excalidraw 是一款开源的手绘风格虚拟白板,在 Github 上有 113k 的 star,画出来的流程图、时序图、架构图等等还是挺有特色的,大概是这种风格:

最近我偶然了解到 Excalidraw 的原理是通过 JSON 绘图,那岂不意味着可以通过 AI 生成符合规范的 JSON 内容来实现自动绘图!所以,我就做了个工具,实现通过 AI 自动绘制手绘风格图,实时流式渲染,边生成边画图:

这还有个 gif 版的,虽然有点糊,不过能看出是流式渲染的:

移动端的效果:

体验地址会放到文章底部,废话不多说,点赞关注,我们直接发车!

AI 实现

这里给大家分享两种实现方式,一种是通过 AI 实现,我们只负责说清楚需求,然后作为一个监工让 AI 干活,这也是我现在做新功能最喜欢的方式;另一种就是一步一步的手动编码实现啦。

不过第一种方式只适合我们已经清楚了这个应该怎么做,有思路但是不想做一些重复的劳动,如果是为了学习的话,还是更建议自己实现一遍。

先来看怎么让 AI 来做这个需求吧。我们要做的就是把需求整理成一个详细的 prompt,直接复制给 AI 即可:

plain 复制代码
帮我实现一个 AI + Excalidraw 手绘风格绘图工具,具体要求如下:

## 一、功能需求
1. 集成 Excalidraw 画板,参考官方文档:https://docs.excalidraw.com/docs/@excalidraw/excalidraw/integration
2. 通过 AI 对话生成 Excalidraw JSON 格式的图形元素
3. 流式输出 + 实时渲染:每解析到一个完整的 JSON 元素就立即渲染到画布
4. 支持移动端和桌面端布局

## 二、后端要求
1. AI 调用在服务端进行,不暴露 API 密钥
2. 记录每次对话的 IP、User-Agent、对话内容、响应时间等信息
3. 支持灵活切换各家 AI 服务(OpenAI、智谱、阿里百炼等),封装统一的兼容层

## 三、前端要求
1. 对话历史保存在 localStorage,不依赖后端加载
2. 画布数据也保存在 localStorage,刷新不丢失
3. 流式解析 AI 返回的 JSON 元素,处理嵌套和字符串内的花括号
4. 为 AI 生成的元素补全 Excalidraw 必需的默认字段

## 四、代码质量
1. 组件封装:AI 服务层、流式解析器、画布组件、对话组件分离
2. 类型安全:使用 TypeScript,定义完整的类型
3. 可复用:AI 服务封装成可配置的模块

## 五、AI 绘图提示词要点
需要设计一个 System Prompt,包含:
- 输出格式:纯 JSON,禁止代码块,方便流式解析
- 文字处理:中英文宽度计算、形状内文字双向绑定
- 箭头规范:points 相对坐标写法
- 常用颜色:Excalidraw 内置调色板

最好是用一些 AI IDE 的 Plan 模式,像 Cursor 、Trae 这些,先规划,再执行。

当然了,AI 生成的代码可能需要根据实际情况再进行调整,但核心逻辑一般都是没问题的。这种方式的好处是快,非常快,原本三个小时的活三分钟 AI 就搞完了;缺点就是我们对具体的实现细节可能不那么了解。

手动实现

如果是以学习思路为目的,更推荐用这种方式实现。

整体架构

整个工具的架构分为三层:

plain 复制代码
[用户输入描述]
      ↓
[AI 流式生成 Excalidraw JSON]
      ↓
[流式解析 JSON 元素]
      ↓
[实时渲染到 Excalidraw 画布]

核心思路是:用户描述想画的图 → AI 生成符合 Excalidraw 规范的 JSON 元素 → 前端实时解析并渲染。

我这里前端使用的是 Next.js ,集成了官方的 @excalidraw/excalidraw 包。

后端使用 Vercel AI SDK 对接大模型,它支持 OpenAI 及兼容服务像智谱、阿里百炼等等。

核心实现

一、Excalidraw JSON 规范

Excalidraw 的绘图原理是通过 JSON 描述元素。每个元素有 idtypexywidthheight 这些基础属性,不同类型还有特定属性。

支持的元素类型:

类型 说明 特有属性
rectangle 矩形 -
ellipse 椭圆 -
diamond 菱形 -
text 文本 text, fontSize, fontFamily
arrow 箭头 points, endArrowhead
line 线条 points

一个简单的矩形元素示例:

json 复制代码
{
  "type": "excalidraw",
  "version": 2,
  "source": "https://excalidraw.com",
  "elements": [
    {
      "id": "rect-1",
      "type": "rectangle",
      "x": 100,
      "y": 100,
      "width": 150,
      "height": 80,
      "backgroundColor": "#a5d8ff",
      "strokeColor": "#1971c2"
    }
  ]
}

backgroundColor 是矩形的颜色,strokeColor 是边框颜色:

二、AI 提示词设计

为了让 AI 生成正确的 JSON 同时保证出图的效果,我们需要给 AI 提供一些必要的信息,比如:

  • 输出格式:纯 JSON,不要代码块(方便流式解析)
  • 文字处理:中英文宽度计算规则,形状内文字的双向绑定
  • 箭头规范:points 是相对坐标,各方向箭头的写法
  • 常用颜色:Excalidraw 内置调色板

我的 prompt 结构大概是这样的,仅供参考:

plain 复制代码
你是一个专业的 Excalidraw 绘图助手。用户会描述他们想要绘制的图形、流程图、架构图等,你需要生成对应的 Excalidraw 元素 JSON。
## 输出格式要求
1. **先说明,后输出**:先简要说明要画什么,然后连续输出所有 JSON 元素
2. **禁止**在 JSON 元素之间穿插说明文字
3. 每个元素直接输出纯 JSON 对象,以 { 开头,以 } 结尾
4. **禁止**使用代码块(禁止 \`\`\` 符号)
5. **必须**使用标准 JSON 格式:键值对用冒号分隔(如 "x":100),不要写成等号
## 文字处理
## 箭头和线条
## 元素基础结构
## 基础形状
## 常用颜色

因为实在是太长了,为了不影响文章的篇幅我就省略了一些内容,大家需要完整内容的话可以联系我。

三、流式 JSON 解析

AI 返回的内容是流式的,需要边接收边解析。核心解析逻辑:

typescript 复制代码
// 从流式文本中提取完整的 JSON 对象
function extractJsonObjects(text: string) {
  const results = []
  let i = 0
  
  while (i < text.length) {
    const startIndex = text.indexOf('{', i)
    if (startIndex === -1) break
    
    // 追踪花括号深度,处理嵌套
    let depth = 0
    let inString = false
    
    for (let j = startIndex; j < text.length; j++) {
      // ... 处理转义字符和字符串
      if (char === '{') depth++
      else if (char === '}') {
        depth--
        if (depth === 0) {
          // 找到完整的 JSON 对象
          results.push(text.slice(startIndex, j + 1))
          break
        }
      }
    }
  }
  return results
}

这里有三个关键点:

1)追踪花括号深度来处理嵌套 JSON

2)正确处理字符串内的花括号(避免误判)

3)记录已处理位置,避免重复解析

当然,可能还有一些我没想到的情况,遇到的话按需处理即可。

四、实时渲染

解析出元素后,通过 Excalidraw API 添加到画布:

typescript 复制代码
// 暴露给父组件的方法
useImperativeHandle(ref, () => ({
  addElements: (newElements) => {
    const api = excalidrawAPIRef.current
    const currentElements = api.getSceneElements()
    
    // 处理 id 冲突
    const elementsToAdd = newElements.map(el => {
      if (existingIds.has(el.id)) {
        return { ...el, id: generateNewId() }
      }
      return el
    })
    
    // 更新画布
    api.updateScene({
      elements: [...currentElements, ...elementsToAdd],
    })
  },
}))

到这一步核心流程就完成了,不过为了稳定性考虑,还可以进行下面的第五步处理。

五、元素默认值补全

AI 可能只生成必要字段,需要补全 Excalidraw 需要的其他字段,也算是一种降级策略:

typescript 复制代码
function getDefaultElementProps() {
  return {
    angle: 0,
    strokeColor: '#1e1e1e',
    backgroundColor: 'transparent',
    fillStyle: 'solid',
    strokeWidth: 2,
    roughness: 1, // 手绘粗糙度
    opacity: 100,
    seed: Math.random() * 100000, // 随机种子,产生手绘效果
    version: 1,
    versionNonce: Math.random() * 1000000000,
    isDeleted: false,
    groupIds: [],
    boundElements: null,
  }
}

移动端适配

为了在手机上也能使用这个工具,我还做了移动端的适配,核心需求是 能用AI生成并看到效果,编辑之类的操作在移动端就不奢求了。移动端采用上下布局,即上面的画布 + 底部输入框:

数据持久化

这个工具所有的画布数据都保存在 localStorage 里,不会上传到服务器,所以大家可以放心使用。

🎯结语

以上就是 AI + Excalidraw 实现手绘风格绘图工具的完整实现啦,核心就是让 AI 生成符合规范的 JSON,然后流式解析并实时渲染。

至于效果的话,我只能说"流程是通的,效果是拉胯的",哈哈哈,所以目前这个阶段只适合作为玩具玩一玩,远远不够生产的水平,大家感兴趣的话可以到 www.lzkz.top/tool/excali... 直接体验。

不过因为这个工具写在了我的个人工具里,所以暂时没有开源,大家需要的话可以在评论区留言或者直接联系我!

如果有用的话,欢迎关注点赞一起交流进步!下期再见,拜拜👋🏻。

相关推荐
美酒没故事°19 小时前
Open WebUI安装指南。搭建自己的自托管 AI 平台
人工智能·windows·ai
涡能增压发动积19 小时前
同样的代码循环 10次正常 循环 100次就抛异常?自定义 Comparator 的 bug 让我丢尽颜面
后端
云烟成雨TD19 小时前
Spring AI Alibaba 1.x 系列【6】ReactAgent 同步执行 & 流式执行
java·人工智能·spring
Wenweno0o19 小时前
0基础Go语言Eino框架智能体实战-chatModel
开发语言·后端·golang
于慨19 小时前
Lambda 表达式、方法引用(Method Reference)语法
java·前端·servlet
石小石Orz19 小时前
油猴脚本实现生产环境加载本地qiankun子应用
前端·架构
swg32132119 小时前
Spring Boot 3.X Oauth2 认证服务与资源服务
java·spring boot·后端
从前慢丶19 小时前
前端交互规范(Web 端)
前端
tyung19 小时前
一个 main.go 搞定协作白板:你画一笔,全世界都看见
后端·go
AI攻城狮19 小时前
用 Obsidian CLI + LLM 构建本地 RAG:让你的笔记真正「活」起来
人工智能·云原生·aigc