前言

​ 前段时间一直在琢磨一个事儿:能不能用自然语言来编辑矢量图形?就是说,我不用去手动拖拽、调参数,直接跟AI说”把这个圆变成红色”、”把所有矩形向右移动50像素”,它就帮我改好了。听起来有点科幻,但实际做下来发现,只要把Prompt工程和画布操作的桥梁搭好,这事儿还真能成。这篇文章就来聊聊这个AI驱动的SVG编辑器是怎么做的。


正文

一、技术栈选型

​ 做一个Web端的SVG编辑器,技术选型是第一步。我最终选定的组合是:

二、AI编辑的三种模式

​ AI编辑不是一刀切的,不同的操作场景需要不同的”权限范围”。我设计了三种编辑模式:

Global模式(全局)

​ 这是权限最大的模式。AI可以看到画布上的所有对象,可以修改任何对象、添加新对象、删除对象。适合那种”大刀阔斧”的操作,比如”给画布添加一个标题”、”把所有矩形的颜色统一成蓝色”。在这个模式下,发给AI的上下文(Context)包含整个画布的SVG内容。

Selection模式(选区)

​ 当你在画布上选中了多个对象时,自动切换到这个模式。AI只能操作你选中的那些对象,可以修改它们的属性,也可以在它们附近添加新对象,但不能动画布上其他的东西。比如”把选中的这三个形状对齐”、”给选中的元素统一加个阴影”。上下文只包含选中对象的信息。

Element模式(单元素)

​ 选中单个对象时进入这个模式,权限最小。AI只能修改这一个对象的属性,不能添加也不能删除。比如”把这个圆的半径改成100”、”旋转45度”。上下文只有这一个对象的SVG。

​ 这三种模式的切换是自动的——根据画布上的选择状态来判断。这样做的好处是,AI的”活动范围”被精确控制了,不会出现你只想改一个元素结果AI把整个画布都重排了的情况。同时,发给AI的上下文也更精简,token消耗更少,响应也更快。

三、Prompt工程:让AI理解Fabric.js属性

​ AI编辑的效果好不好,很大程度上取决于Prompt写得好不好。核心挑战是:让AI知道Fabric.js有哪些属性可以改,每个属性是什么意思,值的范围是什么

​ 我的做法是在System Prompt里塞了一份”Fabric.js属性速查表”,告诉AI:

​ 同时明确告诉AI,它的输出必须是一个JSON格式的Patch数组,每个Patch包含action(modify/add/remove)和对应的参数。这样AI的输出就是结构化的,可以直接被程序解析和执行。

​ 另外还有一个”黑名单”机制——有些属性是绝对不能让AI碰的,比如objectIdtypecanvas这些内部属性。如果AI的响应里包含了这些属性,会在应用前被自动过滤掉,防止出现安全问题。

四、Patch系统:AI响应到画布操作的桥梁

​ Patch系统是整个AI编辑流程的核心枢纽。AI返回的是一组JSON描述的操作指令,Patch系统负责把这些指令”翻译”成实际的画布操作。

​ 三种Patch类型:

​ Patch的应用流程是这样的:

  1. 快照保存:在应用任何Patch之前,先把当前画布状态存一份快照。万一出了问题,可以一键回滚。
  2. 属性清洗:过滤掉黑名单里的危险属性。
  3. 批量执行:所有Patch被包装成一个CompositeCommand,作为一个原子操作执行。要么全部成功,要么全部回滚。
  4. 历史记录:执行成功后,这个CompositeCommand被推入undo/redo栈,支持撤销和重做。

​ 这套设计的好处是,AI编辑和手动编辑共享同一套undo/redo机制。你可以先让AI改一通,觉得不满意就Ctrl+Z撤回,然后手动微调,再Ctrl+Z撤回手动操作,回到AI改之前的状态。整个编辑历史是线性的、可追溯的。

五、Command模式与Undo/Redo

​ 编辑器里有一个很重要的体验问题:AI改完之后不满意怎么办?总不能让用户手动一个个属性改回去吧。所以undo/redo是必须的,而且AI编辑和手动编辑必须共享同一套撤销栈。

​ 我用的是经典的Command模式。每一个操作(不管是手动拖拽、修改属性,还是AI批量编辑)都被封装成一个Command对象,包含executeundo两个方法。AI编辑产生的多个Patch会被包装成一个CompositeCommand,作为一个原子操作推入历史栈。这样Ctrl+Z一下就能撤回整个AI编辑,而不是只撤回其中一个Patch。

​ 每个画布上的对象都有一个唯一的objectId,通过ensureObjectId机制在对象创建时自动分配。这个ID贯穿了整个编辑流程——AI通过它定位要修改的对象,Command通过它记录操作目标,undo的时候也靠它找回对象。

六、上下文优化:让AI少花钱多办事

​ 调AI API是要花钱的,token数直接决定了成本和响应速度。一个复杂的SVG画布可能有几十个对象,导出的SVG代码轻松超过50000个字符。如果每次都把完整的SVG丢给AI,既浪费token又拖慢响应。

​ 所以我做了几层优化:

​ 另外还有一个实用功能:SVG Code Viewer。它能实时显示画布对应的SVG源码,支持语法高亮,而且点击代码里的某个元素标签,画布上对应的对象会被选中高亮。反过来,在画布上选中一个对象,代码视图也会自动滚动到对应位置。这个双向联动在调试AI编辑结果的时候特别好用——你能直观地看到AI到底改了哪些属性。

最后

参考文章:

Fabric.js Documentation

Zustand - Bear necessities for state management

OpenAI API Reference


声明

本文仅作为个人学习记录,由AI辅助编写。