给前端 AI 草稿箱做离线恢复时,我会先把 IndexedDB 账本拆出来
最近给一个前端 AI 写作助手补草稿恢复能力时,我又踩了一次浏览器本地存储的坑。用户在对话里让 Agent 改一段需求文档,页面里同时有提示词、模型返回、人工修改和待提交附件。只要刷新、断网、切账号或版本升级发生在中间,丢的就不止是一段文本,还包括这次修改为什么产生、哪些字段已经同步、哪些字段还在等待确认。后来我把草稿箱从一个简单缓存改成了本地账本:IndexedDB 保存结构化草稿,outbox 保存待同步动作,迁移记录保存 schema 变化,审计表保存每次恢复的依据。

原创示意图:把前端 AI 草稿箱拆成本地草稿、IndexedDB 迁移、outbox 队列、同步确认、冲突恢复和审计账本。 来源:Codex image generation
问题背景
MDN 对 IndexedDB 的说明很适合这类场景:它是浏览器里的低层级 API,可以存放大量结构化数据,也支持文件和 Blob。相比只把一个 JSON 塞进 localStorage,IndexedDB 更适合保存多张对象仓库,例如 draft、attachment、outbox、migration 和 audit。问题在于它的接口偏底层,事务、版本升级、索引和异常路径都需要设计清楚。用 Dexie 或 idb 这类封装可以让 TypeScript 代码舒服很多,但封装只能降低调用成本,数据生命周期仍然要自己负责。
关键难点
第一个难点是草稿状态很容易被写成最后一次覆盖。AI 草稿有机器生成片段,也有人工覆盖片段,还有自动保存的中间态。如果只存一份 latest draft,刷新后看起来能恢复,真正提交时却无法解释哪些字段来自模型,哪些字段来自用户。
第二个难点是版本升级。Dexie 的版本声明可以描述对象仓库和索引变化,idb 也提供基于 Promise 的 IndexedDB 封装,但 schema 升级失败时,用户可能停在旧页面、新代码和旧数据之间。这里不能假设升级总会一次成功。
第三个难点是同步确认。网络恢复后,outbox 里的动作要按顺序推到服务端。服务端返回成功只说明这次请求被接收,前端还要把本地 draft、outbox item 和 audit event 对齐,避免同一条修改被重复提交。
解决思路
我把草稿恢复拆成三层。第一层是 snapshot,保存编辑器可直接恢复的内容,包括标题、正文、引用附件、模型消息 id 和人工修改标记。第二层是 outbox,保存尚未确认的动作,例如 createDraft、patchField、attachFile、submitReview。第三层是 audit,记录为什么恢复、从哪个版本恢复、有没有冲突、用户最后选择了保留本地还是采用远端。
IndexedDB 里每次写入都尽量放进同一个事务。草稿内容和 outbox item 一起写入,页面崩溃后才能判断这次动作处于 planned、queued、synced 还是 conflicted。存储空间也要考虑,MDN 的存储配额文档提醒不同浏览器会有配额和清理策略,所以附件原文和模型上下文不能无限塞进本地库。我给大对象设置了引用和过期时间,只把恢复所需的最小证据留在账本里。
关键步骤
落地时我先定义 schema 版本,从 drafts、outbox、attachments、auditEvents 四张表开始。每个 draft 都有 draftId、schemaVersion、updatedAt 和 baseRemoteVersion。每个 outbox item 都有 opId、draftId、payloadHash、retryCount 和 status。同步器只消费 outbox,不直接扫描草稿表。
接着给升级过程加 migration event。每次版本升级前记录旧版本,升级完成后记录新版本和耗时;如果失败,页面进入只读恢复视图,先让用户导出草稿或选择重建本地库。这样处理会多一层交互成本,但比静默清空库更可控。
最后是冲突处理。远端版本领先时,我不会直接覆盖本地草稿,会生成一个 conflict review 记录,把本地变更、远端快照和模型建议分开展示。用户做出选择后,再写入一个 resolved event。后续排查时,只看 audit 表就能知道这份草稿为什么变成现在这样。
可复用经验
前端 AI 草稿箱的重点在可恢复、可解释、可同步。IndexedDB 负责承载结构化数据,Dexie 或 idb 负责改善开发体验,outbox 负责把离线动作排队,audit 负责让恢复有证据。只要这几层拆清楚,刷新和断网就会变成可处理的状态变化,版本升级也不需要靠运气。
我现在做这类本地恢复,会先问四个问题:草稿能不能按对象仓库拆开,写入能不能用事务包住,待同步动作有没有独立队列,恢复结果有没有审计事件。四个答案都明确后,再讨论 UI 怎么提示用户,工程上会稳很多。