当前位置:首页 > 编程开发

没有服务器的画布,数据到底存在哪?——扒一扒 infinite-canvas 的存储设计

webgou18小时前编程开发12

最近在用 infinite-canvas 这个开源项目(就是 Linux.do 社区比较火的那款无限画布工具, basketikun/infinite-canvas)。用着用着产生了一个疑问:它明明没有自己的后端服务器,模型 API 也是浏览器直连的,那我生成的图片、视频、还有画布上的那堆节点,到底存在哪?会不会哪天一刷新全没了?

带着这个疑问我把源码翻了一遍,把它的存储机制彻底搞清楚了。结论先说:所有东西都在你浏览器的 IndexedDB 里,浏览器就是它唯一的数据库。

三个库,各管一摊

打开 DevTools 的 Application 面板,IndexedDB 下能看到一个叫 infinite-canvas 的库,里面分成三个 store,由 localforage 统一管理:

  • app_state:画布工程本体。节点、连线、会话记录,全是 JSON,通过 zustand 的 persist 中间件写进来。

  • image_files:生成的图片 Blob,外加一份缩略图缓存。

  • media_files:视频和音频 Blob。

配置(API Key、模型选择)也在这套体系里,zustand persist 挂了六个 store,localforage 挂了就降到 localStorage。所以理论上你把浏览器数据一清,这个工具就回到了出厂状态。

生成链路(浏览器内闭环,无服务器) 模型渠道配置 AiConfig: url / key / model 画布节点 + 提示词 上游连线 = 参考图/视频 直调模型 API 图片: requestGeneration / requestEdit (multipart) 视频: createVideoTask + pollVideoTask (openai/gemini) taskId 存入节点 metadata 返回结果 dataUrl / video blob 落 IndexedDB image:nanoid → image_files video:nanoid → media_files + 缩略图 previewStore 写回节点 metadata content = objectURL(临时) storageKey = 持久键 尺寸/字节/primaryImageId zustand persist → app_state (IndexedDB) 画布工程 JSON:节点/连线/会话(含 storageKey,不含 objectURL) 刷新后 hydration 按 storageKey 重建 objectURL 重读 Blob

一张图的完整旅程

看代码最有意思的是 services/file-storage.ts 里那个 uploadMediaFile 函数。名字叫"上传",但它根本没有网络请求——就是把 Blob 塞进 IndexedDB,然后 URL.createObjectURL 生成一个临时链接。

真实的生成流程是这样的:你在画布上点生成,前端拿着你在设置里填的 API 地址和 Key,用 axios 直接请求模型接口(文生图或者带参考图的编辑接口)。模型返回一张 base64 的 dataUrl,前端把它转成 Blob,生成一个 image:<nanoid> 这样的 key,Blob 落进 image_files,同时把 objectURL 写进节点的 metadata。

这里有个设计我觉得挺讲究:节点的 metadata 里同时存了两个字段——content 和 storageKey。

content 存的是 objectURL,这东西只在当前会话有效。浏览器刷新之后,所有 objectURL 全部失效,哪怕 Blob 还好好躺在 IndexedDB 里,句柄也没了。storageKey 才是持久键,跟着画布 JSON 一起被 zustand persist 存下来。

于是每次刷新页面,前端都要做一遍"补水"(hydration):遍历所有节点,拿着 storageKey 回 IndexedDB 把 Blob 读出来,重新 createObjectURL,再把新的 URL 写回 content。顺带还干了一件事:老版本节点里直接存 base64 的 dataUrl,会在这次遍历中迁移成 storageKey 形态。base64 直接进 JSON 会让画布文件膨胀得很难看,这个迁移等于做了一次瘦身。

视频多了一层保险

图片是同步等结果的,视频不行——视频生成都要几分钟,总不能让用户按着刷新键等。

infinite-canvas 的做法是任务创建成功后,立刻把 taskId 写进视频节点的 metadata(videoTaskId 和 videoTaskProvider 两个字段)。因为 metadata 会跟着画布一起持久化,所以你中途刷新页面甚至关掉浏览器,重新打开后前端能认出"这个节点还有个没完成的任务",接着轮询要结果。

这是我在这套纯前端架构里看到的唯一一处异步韧性设计。没有任务队列,没有服务端重试,就靠这一条:把 taskId 塞进持久化的节点数据里,剩下的交给轮询。

连线即引用

画布上把一个图片节点连线到视频节点,连线不是装饰——上游节点会自动成为下游生成的参考输入。比如视频的 frames 模式,参考图第一张作为 first_frame、第二张作为 last_frame,两图之间插值出视频。

引用传递时有个细节:发给模型 API 之前,前端要把 storageKey 转回 dataUrl(从 IndexedDB 读 Blob,转 base64)。也就是说同一份图片数据,显示走 objectURL,喂给模型走 dataUrl,持久化靠 storageKey——三种形态各司其职。

这套设计的边界

好处显而易见:零部署成本,打开网页就能用,不依赖任何服务器,隐私数据不经过第三方。

代价也很直白。浏览器就是全部,意味着:

  1. 清浏览器缓存 = 全部画布和素材蒸发。项目 README 自己也用 CAUTION 标了"不保证历史数据兼容"。

  2. 换设备?IndexedDB 不跟着走。

  3. 大量视频 Blob 堆在 IndexedDB 里,会碰到浏览器的存储配额。

它留了两条退路:一是 WebDAV 同步,配置一个自己的 WebDAV 服务器(坚果云就行),可以把画布、素材、生图/生视频工作台的数据同步过去,带 manifest 和删除日志;二是画布导出,把工程 JSON 和媒体文件打包成一个 zip 下载。

写在最后

看完这套实现,我对"纯前端 AI 工具"的上限有了新的认识。它没有把存储问题甩给用户,而是在浏览器这个沙箱里,用三个 IndexedDB store、storageKey 持久键、刷新后 hydration、taskId 恢复这几招,把一个没有后端的产品做出了七八成的可用性。

剩下那两成——多设备、数据安全、大文件——它选择交给用户自己解决(WebDAV 或者导出备份)。这个取舍是否合理,取决于你怎么用。如果只是偶尔画画图,无所谓;如果是认真的生产环境,建议把 WebDAV 同步配上,或者养成定期导出的习惯。

毕竟,你的"数据库"是浏览器给的那几 GB 配额,别等清缓存的时候才想起来。


扫描二维码推送至手机访问。

版权声明:本文由知了博客发布,如需转载请注明出处。

本文链接:https://www.webgou.info/?id=719

分享给朋友:

“没有服务器的画布,数据到底存在哪?——扒一扒 infinite-canvas 的存储设计” 的相关文章

Agent 到底是什么?从 AI 聊天到自主执行

Agent 并不是“更聪明的聊天机器人”,也不是简单地给大模型套一层 UI。它更接近一种新的软件系统:以大语言模型作为决策核心,通过工具与外部世界交互,并围绕目标持续执行任务。…

大端小端(Big- Endian和Little-Endian)

字节序(Endian),大端(Big-Endian),小端(Little-Endian)…

递归算法学习系列之经典背包问题

今天打算把下面的问题看完,可胃不听话,静不下来,所以把这个留在这里做个记号,等胃听话的时候再过来品味品味. 1.引子  我们人类是一种贪婪的动物,如果给您一个容量一定的背包和一些大小不一的物品,裝到背包里面的物品就归您,遇到这种好事大家一定不会错过,用力塞不一定是最好的办法,用脑…

Linux平台编程新手入门 C语言中的移位操作

C语言中的移位操作,内容不多…

AI 使用 Unity MCP 自动操作 Unity:从连接配置到生成五子棋场景的实战记录

MCP Server 启动验证Unity 插件连接验证新建 Unity 场景创建并挂载 MonoBehaviour 脚本实现单机五子棋Play Mode 验证Console 错误检查对于 Unity 开发来说,这类工具很适合做原型验证和重复性编辑器操作。它还不能完全替代开发者,但已经可以显著减少“写…

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。