POD 编辑器:基于 AiAgent 与画布构建的定制设计方案
目录
本文记录的是 2026 年 4—5 月的公司内部项目实践,相关数据已做脱敏处理。文中进展与后续规划以当时的项目阶段为准。
POD 是 Print on Demand,即按需印制。在国际站的定制场景里,买家可以为包、服装等载体设计图案,再查看图案放到产品上的效果。我们这一阶段建设的 POD 编辑器,承接的是其中的设计过程:把买家的想法变成一份可以编辑、保存、恢复和导出的方案。
原本在商品详情上对于支持 logo 定制之类的轻定制品,已经接入过一个相对简单的定制编辑器,这一次是希望加入 ai 相关的能力,比如文生图、图生图等,并接入真实的商品白模,期望提供更好的产品服务有 POD 定制诉求的买卖家。
最终的产品预览图:左侧是 AiAgent (AiMode),右侧是 POD 编辑器。

业务流程概况:
整体方案
我们提供一个独立的 POD 编辑器以及开放能力,然后接入到 AiMode 或者 AccioWork 之类的有 AIAgent 能力的平台,使用现有的 Agent 能力来完成产品。之前先和 Accio 进行过沟通后来因为方向上的问题,最终选择和 AiMode 合作。
整体的系统交互图如下:
几个核心的系统参与方:
| 参与方 | 负责的状态与行为 |
|---|---|
| AiMode Host(宿主页面) | 对话身份、右侧面板、iframe 生命周期、Skill 请求与结果路由 |
| POD Skill 与图片卡片 | Skill 解释业务动作、调用模型并返回结果;卡片展示图片、提供进入编辑器的入口 |
| POD 业务应用 | 方案入口、白模和素材流程、AI 结果落点、保存与导出编排 |
| 中台 wow-editor | 画布渲染、对象和工具操作、编辑文档、预览与图片导出 |
| Marbas POD 服务 | 登录身份、业务数据校验、方案持久化,以及商品、TDDL、Filebroker 等服务适配 |
一次完整操作:Skill 生成图片,卡片经 Host 把图片交给 POD,POD 调用画布接口完成编辑;画布中的 AI 动作再经过 Host 回到 Skill,结果回填后,由 POD 将完整方案保存到 Marbas。
我主要负责其中的 POD 、Marbas 以及和 AiMode 的交互功能。
与 AiMode 协作:通用双向通信能力搭建
由于我们的系统是长在 AiMode 中的,那么左侧的 AiAgent 对话能力怎么和右侧的 POD 进行数据交互,就是一个必须要解决的问题。我们对左右两侧的协作有两个直接的业务诉求:
- 把对话里的图片带进画布。 买家点击左侧卡片的
ApplyThisDesign,打开右侧 POD,并把这张图加进去;如果 POD 已经打开,就继续向当前画布添加图片,保留之前的编辑。 - 从画布发起 AI 改图,再接回结果。 买家在右侧 POD 选中图片并发起 AI 动作,请求经过
POD → AiMode → POD Skill → AiMode → 原 POD,最后由 POD 完成本次画布操作。
这两项操作依赖的基础能力有所不同:
| 业务场景 | 需要补齐的能力 |
|---|---|
卡片点击 ApplyThisDesign | 打开侧栏并加载指定 iframe;识别并复用已打开的应用;建立通信通道;向该实例交付本次图片数据,并取得处理回执 |
| POD 发起 AI 改图 | 在上述通道上发起调用;明确指定 Skill,并交付完整输入;取得结构化执行结果;关联原会话、原 iframe 和原请求,让 POD 继续处理 |
基础面板和单次消息能力已经由 @梁鑫 提供。我在这之上主要补了两部分:增强 openApp 的应用复用与数据交付,以及贯穿 iframe、AiMode 和 Skill 的通用调用协议,下面从这两个场景看具体怎么做。
场景一:从 AiMode 到 POD
用户在左侧卡片点击 ApplyThisDesign 时,会调用 Host 的 openApp,对应消息名为 ui/open-app。请求同时携带应用地址 source.url、面板位置,以及本次业务数据 payload。Host 先取得可用的右侧 iframe,再把 payload 交给它。
这里要把 “打开哪个应用”和“这次让它做什么”分开。假设买家已经调整好第一张图,又点击了第二张卡片,如果通过修改 iframe 的 src 传递新图片,浏览器会重新加载页面,尚未保存的编辑也可能丢失。因此,Host 需要识别当前 POD 是否可以继续使用。
当前采用复用策略的 direct App,以同一对话和同一应用身份作为复用条件。应用身份忽略 URL 的 query,保留 origin、pathname 和 hash 路由;所以入口参数变化,不会自动让已打开的编辑器重新加载。这项策略需要显式启用,切换对话或应用身份时仍会关闭旧实例并重新打开。
首次加载需要握手;复用已有实例时,沿已建立的通道直接进入“本次交付”。图中的 launchId 是 Host 为一次 launch 生成的 requestId。
首次加载时,POD 注册消息监听并发送 ui/ready,Host 以 ui/initialize 下发通信上下文。POD 校验父窗口来源、精确 origin,以及 context.conversationId 是否与页面的 solutionId 一致,再用 ui/resize 回应;当前 Host 以首次 resize 确认初始化已被接收。这样,双方才有了可继续使用的通信通道。
通道建立以后,无论 iframe 是新建还是复用,Host 都通过 ui/app-launch 发送本次 payload。Host 负责交付,payload 的业务含义由 POD 自己解释。 对图片卡片而言,entry 表达白模或商品入口,seed 表达本次要添加的图片;已打开的方案保留自己的上下文,新的 seed 继续进入添加图片流程。
POD 还会等待本地编辑器初始化和图片添加完成,再回复 ui/app-launch-result;Host 随后按卡片的 callId 回复 ui/rpc-result。通信就绪、画布可用和图片添加完成由此分别确认。Host 对未回执的旧应用有超时放行机制,避免阻塞后续打开操作。
这就实现了卡片的连续操作:第一次打开 POD 并传图,后续点击继续往当前 POD 传图。
场景二:从 POD 到 AiMode 再拿到返回
第二个场景是在画布上选中图片 A,发起 Stylize(风格化)。前面建立的通道能把消息送到 AiMode,但后面还有三个问题:AiMode 应交给哪个 Skill,Skill 怎样拿到完整输入,执行结果又如何回到本次操作。
为此,我们抽出了通用的 App/Skill 调用协议。它由两层组成:外层说明调用哪个 Skill、如何关联请求和结果;内层说明业务要做什么。 外层由 AiMode 统一处理,内层由 POD 与 POD Skill 约定。
下面是一次 Stylize 请求的关键字段,编号和 URL 为示意:
{
"type": "ui/invoke-skill",
"requestId": "P1",
"skillId": "pod_design_skill",
"input": {
"action": "replace",
"targetObjectId": "A",
"expectedResult": "image",
"content": {
"actionId": "image_stylize_bold_cartoon_mascot",
"images": [{ "role": "source", "url": "https://example.com/source.png" }]
}
}
}type、requestId、skillId 属于通用调用协议。input 是留给业务的容器:AiMode 原样传递,POD Skill 才需要理解里面的动作、对象、图片和预期结果。通用协议把 input 定义为 unknown,返回的 result、error 也同样保持不透明,避免平台随着每个业务动作增加解析分支。
会话身份属于通用通信上下文。当前 POD 发出的这条消息并不自行携带 conversationId,Host 从已登记的 iframe 上下文取得会话和实例身份;POD 则在发送前登记 P1 及本次操作、准备可访问的持久图片地址,等待 AiMode 返回的结果。
AiMode 执行层包含 Gateway、Agent 和工具结果传输,P1 是 POD 的请求编号,H1 是 Host 为这次调用生成的内部编号。
AiMode 怎样找到 Skill,并让它拿到完整输入
Host 收到 ui/invoke-skill 后,先校验来源与请求,登记原 iframe 及 P1,再生成内部关联编号 H1。后续 Agent 执行使用 H1,Host 保留它与原页面请求的对应关系:
| 关联信息 | 作用 |
|---|---|
| 会话、endpoint ID、generation | 确定发起调用的具体 iframe 实例,区分重建前后的页面 |
P1:POD 的 requestId | 让 POD 找到自己登记的操作,并在最终回包时恢复这个编号 |
H1:Host 的 correlationId | 贯穿 Agent 执行与终态回传,再由 Host 映射回原实例和 P1 |
接下来,Host 经原有通信方法 sendQuery 进入 AiMode 的对话执行链,同时发送两类信息。
一类是供平台识别的结构化元数据:appSkillInvocation 携带 H1 和指定的 skillId,派发参数也明确设置 skills。Gateway 据此识别这是应用发起的 Skill 调用(AiMode 原有功能),检查该 Skill 是否在当前 Agent 的可用范围内;检查通过后,把指定 Skill 放入实际派发的 skills 列表,再由 Agent 按 Skill 的执行约定调用工具。Skill 不可用时则返回与 H1 关联的错误。
另一类是供 Agent 和 Skill 读取的完整输入。Host 把 {requestId: H1, skillId, input} 序列化为 JSON,放进唯一的 <app_skill_invocation>...</app_skill_invocation> 块,并明确要求保留请求编号、按该编号返回终态。这里改变的是请求关联编号,POD 的业务 input 原样保留,不能先概括成一段“请帮用户改图”再交给 Skill,否则目标对象和动作参数都可能丢失。
POD Skill 的执行约定与这个入口相对应:读取该 JSON 块,以 input.action、input.expectedResult 和 input.content.actionId 识别具体动作,再校验并调用相应工具。以这次请求为例,Skill 根据 image_stylize_bold_cartoon_mascot 确定风格,使用 source 图片完成生成;需要用户文字的动作从 input.content.text 读取。新增 POD 动作时,输入含义由 POD 与 POD Skill 一起调整,Host 继续按同一规则派发。
这两类信息各有用途:结构化元数据确定 Skill 入口,完整信封保留执行所需的数据。
Skill 用结构化终态回传,POD 按原请求继续处理
Skill 生图以后,需要同时完成业务结果和调用协议两件事。图片地址、动作和目标对象遵循 POD 的约定;请求编号、成功或失败状态,则遵循 AiMode 的通用终态协议。
我们给 POD Skill 提供了 return_pod_bridge_result 回传工具。Skill 把收到的完整原请求作为 request,连同本次生成内容交给工具。工具从原请求机械复制 H1,并从原 input 恢复动作、目标对象和预期结果类型,再把图片规范为带持久 HTTPS 地址的单张结果图。关联字段因此不需要模型重新拼写。
工具输出的终态如下。外层的 requestId、status 用于 AiMode 路由,result 内部仍是 POD 的业务数据:
{
"requestId": "H1",
"status": "success",
"result": {
"action": "replace",
"targetObjectId": "A",
"expectedResult": "image",
"content": {
"images": [{ "role": "result", "url": "https://example.com/result.png" }]
}
}
}失败时使用同样的外层结构,以 status: "error" 和 error 返回原因。回传工具把终态放在 JSON 输出的 terminal 字段中;作为脚本执行时,这份输出保留在 stdout。AiMode 的工具传输层提取其中的 terminal,转成 appTaskTerminal 事件,与自然语言展示内容分开传递。对话里出现一张结果卡片,并不能代替这份程序回调。
Host 收到事件后,用原会话和 H1 找到待处理请求,恢复 P1,保留 result 或 error,组装为 ui/invoke-skill-result 发回原 endpoint。Host 不需要知道这张图用于风格化还是其他业务,只需要保证结果属于当前等待的调用。
这段回程还处理了异步执行中容易出错的情况:Host 在回送前先删除等待映射,后来的重复终态就不会再次交付;iframe 注销时清理该实例的等待,新打开的页面不会接到旧页面的结果。Agent 结束却没有终态时,在能够明确关联该次执行的条件下,以 TERMINAL_MISSING 等错误结束等待。
最后,POD 通过 P1 找到本地等待的操作,再校验 result 中的动作、目标对象、预期结果与业务上下文,加载并解码图片,应用到发起时记录的目标。买家等待期间即使选中了图片 B,也不会改变图片 A 这次操作的结果位置。至此,从右侧发起的 AI 动作才完成了往返。
小结
这套能力虽然从 POD 的需求中长出来,但应用身份、iframe 通道、指定 Skill、完整输入交付、请求关联和结构化终态,都属于其他 iframe 业务也会遇到的问题。同时我们属于 AiMode 的接入方,我们和 AiMode 都不希望 POD 的业务代码侵入到 AiMode 中,所以在实现的时候把通信能力收敛到通用协议和 Host 实现中。POD 自己维护 entry、seed、input 和 result 的含义,以及结果如何进入画布。
后续 AiMode 接入另一项 iframe 业务时,可以沿用 openApp 和 App/Skill 往返能力,为新应用指定对应 Skill,再由双方约定业务输入输出。平台无须逐个认识新业务的按钮和数据结构。对 POD 而言,这也意味着新增一个 AI 动作时,主要改动落在 POD 与 POD Skill 的业务约定上,通信和回程机制可以继续复用,不需要涉及 AiMode 的改动。
与 wow-editor 的协作: 把画布能力包装成业务操作
在接入中台之前,我用 Fabric.js 做过一版画布原型,覆盖多面白模、对象编辑、打印区域、撤销重做和图片导出。但编辑过程已经暴露出多面状态恢复、连续工具退出、系统图层隔离和视口坐标换算等问题。以及我们临时 vibeCoding 的画布在交互和体验上短期难以追平成熟的画布编辑器,而且持续维护完整画布成本较高,最终我们转向使用设计中台提供的成熟画布产品 wow-editor。之前这个原型的开发也没有完全浪费,帮助我们梳理出和中台 wow-editor 的最小交互 SDK,减少了一部分接入成本。
接入方案
wow-editor 以 SDK 的形式运行在 POD iframe 内,提供画布、工具交互和编辑文档。POD 负责方案、素材、AI 动作与保存流程,中间通过 WowEditorSdk 适配层协作。
我的设计思路是,对于我们 POD 业务层,尽可能不要感知画布的细节状态,我们只是调用画布,然后拿到画布发起的操作动作,然后各自维护各自的状态,确保职责上的隔离,也降低后续的交互协作成本。
图中实线表示业务调用,虚线表示事件与回调;方案的查询和保存由 POD 业务层发起,文档经适配层在 POD 与 wow-editor 之间传递。
数据保存方案
一个设计解决方案有两类数据:POD 用 solutionId 和白模、商品来源标识业务身份,wow-editor 用完整 editorData 表达画布状态。保存时通过公开 document.get 取得文档,按 JSON 写入 Marbas;恢复时再将文档交回 SDK。
这里的关键选择是把 editorData 当作不透明文档。POD 不拆解内部图层,也不另外维护一份对象、选区和位置数据。否则中台调整文档结构时,业务侧就要同步迁移两套状态。画布如何恢复由 wow-editor 负责,恢复哪份方案由 POD 负责。
重新打开时,POD 优先读取已有方案的文档和来源;只有新方案才使用 Marbas 统一提供的 MockupDefinition 初始化白模。这样,新卡片的入口参数或白模配置变化,不会覆盖已经保存的设计。
保存统一排队,配合自动保存与服务端归属校验。普通全面导出先保存,再由 SDK 生成各面图片供下载;导出图用于交付,继续编辑仍以完整文档为准。
需要注意的问题
协议和适配层解决了数据如何流动,但是在编辑器上的连续操作还要求它们在等待、失败和来源变化时保持一致。
Host 把结果送回正确页面之后,POD 还要找到正确对象。以图片 A 为例:买家点击 Stylize 后又选中了 B,如果在结果返回时读取“当前选中对象”,新图就会错误地覆盖 B。

所以在发起 ai 动作时,POD 会保存发起的对象 id 。在 Idea 模式下保留原图 A,建立独立的 loading 结果位置;在 Mockup 模式下记录本次操作的目标。收到终态后,先核对动作、对象、预期结果与当前业务上下文,再加载、解码图片,最后应用到已记录的位置。
等待状态附着在本次结果上,失败时清理本次操作,不回滚整个画布,避免抹掉等待期间的其他编辑。当前同一 POD iframe 只接受一项这类图片 AI 操作;其他操作是否允许继续,要看是否会破坏待回填目标。比如在 idea 模式发一个 ai 改图时,会在右侧生成一个 loading 占位,只要后续的动作不是删除或者修改这个占位,那么后续的动作都是可以并发执行的,不需要全程锁死画布。
总结
原本在 AiMode 的同学由于工期原因可能无法支持我们以后,我感觉整个项目上线风险很大,压力倍增,因为和 AiMode 的通信如果 AiMode 的同学不参与我们进去开发会存在很多不可知的问题。不过在调通了最基本的双向通信以后瞬间轻松下来。
虽然后续为了在正式上线前在 cocreate 要做演示,为此为了 cocreate 版本做了很多次特定调整,甚至包含生图方案的大幅调整,不过这些整体上还是相对可控的。今天 cocreate 已经结束,据我们去了现场的 UED 同学的描述,cocreate 展会上我们的 POD 功能大受欢迎,听到消息还是很开心的。
后续规划上我们会在当前的白模基础上,接入原有的轻定制品进一步扩大商品覆盖面。这样 POD 接入 AiMode 正式上线时,有定制诉求的买家就能够从设计、预览一路走到商品匹配,更顺畅地完成自己的定制需求。