十一个星期从零到 223MB Windows 便携包:我做了一套 AI 种草 Live 素材生成工具,让视频模型只出无字底片是最省钱的设计
2026 年 7 月 21 日下午 2 点,我敲下这个项目的第一个 git commit(内容是 .env.example、AGENTS.md 和一堆设计文档);10 月 8 日下午 3 点 45 分,一个 223MB、3573 个文件的 zip 落进 portable-dist/——那是可以在 Windows 上双击 BAT 启动的完整便携版。中间隔着 11 个星期、132 个提交、201 个测试文件里的 1114 项测试。更早的两周(7 月 7 日起)其实已经在写竞品研究和单页向导设计规格了,但以 git 纪元论,这是完整的一生。这篇文章是复盘:为什么做、架构怎么定、踩了哪些坑、最后怎么在 macOS 上打出一个 Windows 包。
一、为什么做这个系统
我们内容团队的日常是给商品做种草素材:一张产品图,要变成 16 秒的 Live 感视频,配上中文文案、价格角标、贴纸,发到小红书和抖音;还要追着平台上的爆款笔记做「手抄式图文」——模仿真实手写笔记的排版和语气做二创。这两件事的人工流程是这样的:运营自己想文案、开剪辑软件拉时间轴、逐字调整贴字位置;想做爆款二创,就把别人的笔记截图存下来,对照着一句句改。
每一条单看都不可怕,可怕的是量:一天几十条,每条都要重跑一遍「想文案 → 剪视频 → 排版」。而且文案改一个价格数字,视频就得重新导出一次。所以我决定做一套工具,把「策划文案、图片、视频、合成」全部模型化、流水线化,并且守住一条底线:用户只改文字的时候,绝对不重新调用视频模型——这条底线后来成了整个架构的锚点。
二、系统全景
最终形态是一套 Next.js 16.3(App Router + standalone 产出)+ Prisma/SQLite + FFmpeg 的单体应用,生产构建生成 87 个页面/路由:
浏览器(Next.js 前端,创作工作台 / 爆款内容工作台 / 素材库 / 知识库) │ /api/*(飞书 OAuth 会话,多租户隔离) ▼Node 服务端(真实 Provider 只在这里被调用) ├─ LLMPlannerProvider ── gpt-5.6-terra:策划、文案、提示词、排版图层动效 JSON ├─ ImageProvider ────── gpt-image-2 系列:首帧图 / 参考图 / 图中文字替换 ├─ VideoProvider ────── Seedance 2.0:只生成无字视频底片 ├─ Compositor ───────── 本地 FFmpeg:底片 + 文案/价格/Logo/贴纸/评论框/箭头 → 成片 └─ AssetLibrary ─────── 一切上传与生成结果都落为 Asset(核心模块)几个关键决策说清楚:
- 四大 Provider 抽象,边界写进项目宪法。
AGENTS.md里第一天就写死:Planner 只出 JSON、VideoProvider 只出无字底片、ImageProvider 只管图、Compositor 只管合成;文案、价格、Logo、贴纸、评论框、箭头一律是独立图层,后期合成。不允许任何业务代码写死某个平台名。真实 Provider 文件第一阶段只放 TODO,不许杜撰 endpoint 和请求体。 - 本地默认 Mock,真实调用显式开启。所有付费调用前有费用确认(12 张图片的批次会明确告知),每次真实调用写
ProviderUsageRecord。API Key 只从服务端环境变量或 AES-256-GCM 加密配置读取,前端永远只见掩码。 - 所有核心数据结构 TypeScript 类型 + Zod schema 双重契约。Planner 的输出 JSON、任务快照、画布图层全都要过 Zod,这是后面 1114 项测试的地基。
- 测试是验收货币。从最初单页向导完成时的 20 项单元测试,一路涨到 7 月底的 684 项、8 月底的 1094 项,最终 201 个文件 / 1114 项。任何一个改动都要过全量 vitest + TypeScript + ESLint + 生产构建四道门。
功能模块按使用频率排:两步创作工作台(素材 → 贴字/预览/发布文案 → 成片)、爆款内容中台(采集雷达 / 待审核 / 爆款库 / 内容池 / 成品项目 / 知识库 / 任务中心七个工作区)、沉浸式 AI 精修工作台(三栏:贴字与 IP 模板 / 3<4>4> 画布 / 图中文字与发布文案)、素材库与作品库、任务中心、系统设置,最后是两个分发形态:云端生产环境(一台云服务器,细节不展开)和 Windows 便携版。
三、开发周期复盘
7 月上旬(07-07 ~ 07-21,git 纪元前):文档先行 + 骨架。 第一天写的是竞品研究:ClipForge、Beav、几个 ComfyUI 对接项目逐个列出可借鉴点和不采用原因(AGPL/非商用的坚决不碰),结论是 AssetLibrary + 四 Provider + JobService 自建。然后是单页向导设计规格、实施计划,再是类型和 Zod schema、Prisma 模型、Mock Provider、API、页面、测试——这个顺序是 AGENTS.md 里定死的执行顺序,全程没破过例。
7 月中旬(07-15 ~ 07-20):创作工作台成型 + 飞书多租户。 产品图/场景图双输入、AI 候选图直选、作品库与素材库分家、公司自定义字体 FFmpeg 合成,阶段号一路刷到 8.21。同期把飞书 OAuth 接进来做了真正的多租户:全公司部门目录同步、一级部门展示、首次登录自动计费归属、Seedance 中转按部门路由、Image 2.0 公司统一定价。7 月 20 日做了一轮全项目审查与部署前加固。
7 月下旬(07-28 ~ 07-29):ChatGPT 收敛 + 第一版 Windows 便携包。 两天干了三件大事:文本链路从旧的 Ark 方案收敛到统一的 ChatGPT 文本 Provider(文本与图片共用一套公司公共加密 Key);第二件是 Windows x64 便携版——随包带 Node、FFmpeg、Next.js standalone,员工解压双击 BAT 就能用,凭证 AES-256-GCM 加密、主密钥拆两半放不同目录,发行脚本扫描明文凭证、拒绝 .env 和数据库进 ZIP、在中文空格路径重新解压复验,第一版 261MB;第三件是生成可靠性:主动取消、Seedance 多图参考模式修正、异常任务五分钟规则 + 审计、ChatGPT 文本六类安全错误与有限重试、A1 自适应工作台。便携版 HTTP 回调读不到 OAuth state Cookie 的坑也是这天修的(本地 HTTP 不能带 Secure)。
8 月上旬(08-06 ~ 08-08):AI CTO 接管治理 + 安全升级。 项目当时本地领先远端 116 个提交、工作区一堆来历不明的文件,出了「单机故障全丢」的风险。8 月 6 日做了一次冻结基线接管:PROJECT_STATE / 证据册 / 健康报告 / Gate 全套建立,接管期业务代码修改授权为 NO,不自动 reset、clean、stash;先把 116 个提交推上远端解除单机风险,再以「协调式最小升级」把 next 从 16.2.10 升到 16.3.0(拒绝 npm audit fix 这种范围不可控的操作),清掉生产依赖里的 3 个 High。
8 月中旬(08-10 ~ 08-18):两步工作台 + 第一次整包替换发布。 两步创作向导、发布文案编辑器、抖音完整字段(旧建模只有 title,正文和话题标签压根没生成过)、React Strict Mode 草稿丢失、Mock 合成 spawn ffmpeg ENOENT(图形应用启动的进程不继承 shell PATH)都在这周修。8 月 18 日是里程碑日:图片模型逐个探针实测后切到可用的 gpt-image-2-vip(详见踩坑第 1 条)、修复图片「200 假成功」、采集器补上独立 HMAC 密钥、付费入口补数据库级幂等租约,然后本地完整版第一次以 release-*-full-replacement 整包替换方式发布到云端——从此每次发布都是:先备份可回滚快照、隔离目录装依赖构建核验、切换、健康检查、SQLite integrity_check。
8 月 19 日 ~ 8 月 27 日:爆款中台九天冲刺。 这九天密度最大:小红书采集从 Chrome 扩展(两阶段:搜索候选 + 详情页补全,配对码换长期设备令牌)做到本地 CDP 执行器(只监听 127.0.0.1<9222>9222>,只读已登录页面可见卡片,不碰 Cookie);沉浸式 AI 精修工作台 v2 三栏重写;RAG 知识库(14 本书、3662 页、2438 个片段,FTS5 + sqlite-vec 1536 维混合检索);手写风格提示词从 V5 迭代到 V21;逐页洗稿、动态画布模板、A/B/C 三种真人手写风格;「我的 IP / 封面模板」重构。期间 8 月 26 日用智能纪要加 32 分钟录音拆出 M01—M20 二十项改动清单,逐项修复逐项验收,光 8 月 26 日一天就发了 7 个 release-*-full-replacement 正式版。8 月 27 日做本地精简审计:包管理统一回 npm、工作区从 10GB 清到 1.6GB、删掉 50 条高耦合的 CSS 类名断言(这种测试会阻碍正常重构)。
10 月 8 日:自配便携版,最后的冲刺。 新需求来了:安装包要上传到公司内部工具系统给外部用户下载,所以包里不能含任何公司凭证,用户在本机自配模型。一天之内做了 self-config 风味:免登录(首次访问自动创建本机用户)、「我的模型」配置页、凭证 AES-256-GCM 加密存本机 SQLite、密钥首次运行随机生成、未配置时生成接口返回 409 引导配置;模型名从固定枚举放开为非空字符串——这是对项目宪法「模型名固定」规则的经批准偏离,专门写了 ADR。同日在 macOS 上交叉打包出 223MB 的 Windows 包(详见踩坑第 6 条),中途还处理了一场 70 个文件被外部进程回退的事故(详见踩坑第 5 条)。
四、踩坑实录:每一条都有日志佐证
1. 图片生成「200 假成功」:最阴险的一类 bug
用户反馈「生成图片时没生成就停了」——没报错,也没出图。追 /api/images/create → 图片 Provider → 中转站全链路,生产日志里的记录是 CHATGPT_IMAGE_RESPONSE_INVALID、httpStatus: 200、responseShape: 'keys=none;data0=undefined':中转偶发返回 HTTP 200 但响应体是空的。而 handleRouteError 的逻辑是 httpStatus < 500 就透传,200 被当成成功一路放行,前端 response.ok 为真、忽略错误体、把候选图读成空数组。修复三件套:图片错误只透传上游 4xx(比如 429),2xx/5xx 一律落 502 杜绝假成功;错误文案区分「通道临时异常」;生成超时从 120 秒放宽到 180 秒(中转实测单张 40~80 秒,两张并行约 60 秒,120 秒太紧),后来又一次放宽到 360 秒——因为一条成功记录实际耗时 332 秒。配套纪律:HTTP 超时不能证明上游没计费,所以永远不自动重试付费生成请求,重试只能由用户主动发起;同 URL 的结果下载重试 3 次倒是安全的(覆盖超时/5xx/429,付费的生成 POST 仍只发一次)。
同一天还做了图片模型逐个探针:gpt-image-2 返回 502 或 200 空对象,gpt-image-2-pro 360 秒后空对象,gpt-image-2-vip 明确拒绝 response_format 参数——去掉后 40.5 秒返回可用图片。「接口列表里有这个模型」和「这个模型在这个账号分组里真的能用」是两回事,这一课和上一套投流系统里「接口存在不等于权限可用」一模一样。
2. 保存竞态:SQLite 唯一索引允许很多个 NULL
精修工作台同一来源首次进入时,React 开发模式双重加载并发调用项目初始化,而 creationKey 默认为空——SQLite 的唯一索引对 NULL 全部放行,于是同一用户、同一来源在 1ms 内创建了两个项目。接着 800ms 防抖保存只取消还没触发的定时器,拦不住已在途的 PATCH,两个请求带相同 revision,后完成者必然 409「保存冲突」。修复:默认项目用确定性创建键保证并发幂等;服务端 revision 校验改成数据库原子 compare-and-swap,堵掉「先读再写」的时间窗;前端保存抽成串行协调器。同一个bug家族还有:撤销/重做没清掉未触发的自动保存定时器导致撤销结果被旧快照覆盖;pointermove 每次都压历史导致一次拖动手势产生几十份像素级差异快照、点撤销像没反应——改成手势开始只记一次完整前态。后来干脆按用户要求移除了防抖,编辑即存。
3. 数据迁移把用户的 6 页项目重建成 1 页
一次发布后的强制刷新验收发现,init-db.mjs 的历史来源页迁移没排除空白创作项目,启动时把一个 6 页项目错建成 1 页。处理过程成了之后所有迁移的模板:先离开在线编辑页避免二次写入,另存数据库快照,再从发布前的 SQLite 备份把这个项目精确恢复到 revision 1141、6 页、原第 6 页选中;迁移代码明确跳过空白与无来源项目,并补一条「重复启动保持空白项目 6 页不变」的真实 SQLite 回归。另一个数据事故:初始化脚本每次启动强插两个命名 IP 模板,其中一个还挂着已不存在的 Asset 变成灰色坏卡片——确认零引用后从开发库删掉,备份先行。
4. 「打开原帖」失效:安全参数被自己剥掉了
内容池的「打开原帖」进入 error_code=300031 失效页。定位发现是自己的代码干的:采集执行器和 Chrome 扩展在详情页用 location.href.split("?")[0] 清理 URL,把小红书需要的 xsec_token / xsec_source 删了。修复后两条采集路径都优先保留当前地址的安全参数,页面没带令牌就用搜索候选提供的带令牌地址,并从历史搜索快照恢复了那条记录的链接(改前备份了数据库)。
5. 70 个文件被外部进程回退:从 git 对象里把工作「捞」回来
10 月 8 日做自配便携版时,测试突然大面积莫名失败。排查发现工作区约 70 个源码文件被某个外部进程(疑似同时开着的其他编辑器或同步工具)在中途逐步回退成旧版本,而部分新功能(IP 素材、我的 IP、任务暂停恢复)当时只存在于 git 暂存区对象里;更糟的是我先做的 git stash 验证实验把损坏状态固化了一层。恢复方法:git fsck --lost-found 找出 dangling commit(stash 产生的 index 快照和工作区快照),逐文件比对、两个快照取并集合并——比如服务文件里合入 pause/resume 方法、模板 schema 补回新增字段——最后全量 vitest 201 文件 / 1114 项全绿收尾。这次之后立了两条规矩:大批量改动前后先 git add 建保护点;测试莫名从通过变失败,第一反应先怀疑文件被外部改了,而不是怀疑测试。
6. 在 macOS 上交叉打包 Windows:中文文件名乱码逼我写了个 ZIP 写入器
最后一公里全是工程细节。prisma/schema.prisma 固定 binaryTargets=["native","windows"] 让一份代码带两份查询引擎;npm i --no-save --force 注入 win32 的 FFmpeg/FFprobe 安装包和 sqlite-vec,因为 @ffmpeg-installer/ffmpeg 在模块加载时就 require 平台包,缺了直接崩;再下载与本机版本一致的 Windows node.exe(v24.19.0)。打包器把 win32 原生件注入 standalone 的 node_modules、剪掉 darwin 件。最坑的是压缩:macOS 系统的 zip 和 ditto 都不给中文文件名写 UTF-8(EFS)标志,中文 Windows 解压必乱码——最后写了个纯 Node 的 ZIP 写入器专门处理 EFS 标志,而 macOS 自带的 unzip 6.0 又不认 EFS,校验解压改用 ditto -x -k。产物 AI种草Live-0.1.0-186d2d6-win-x64.zip:223MB、3573 个文件,打包器内置全量哈希解压复验,自配风味的校验规则反过来要求凭证文件必须不存在。唯一没能闭环的是 Mac 无法执行 PE,Windows 真机双击验收留给了用户。
7. 取消一个生成,比生成本身难
「停止生成」要贯穿全栈才算数:前端统一的可取消 AI 操作控制器(页面卸载自动中断、取消后不应用迟到结果),AbortSignal 从 Route Handler 一路传进 Planner、图片 Provider、OCR Provider 和文案服务,Provider 返回后、落库前再检查一次取消状态——不然用户停了,结果又写回去了。视频后台任务走另一条持久化取消链路:优先请求原通道取消,通道不支持就如实告知「供应商任务可能仍在运行」,绝不自动重新付费提交。pending/planning 超过 5 分钟没有 Provider 任务号判定为「等待异常」,提供检查状态 / 终止 / 返回草稿,每次操作写不可变审计。
五、「无字底片 + 后期合成」与真人手写的执念
这两个是整个系统最值得单独讲的设计。
无字底片。 Seedance 2.0 永远只生成无字视频底片,中文文案、价格、Logo、贴纸、评论框、箭头全部是独立图层,由 FFmpeg Compositor 后期合成。直接动机是视频模型写不好中文,但真正的回报是经济性:用户改一个价格数字、挪一下贴字位置、换个 Logo——只重新合成,几秒出结果,视频模型一次都不用再调。配套的省钱设计还有:成片 16/27 秒里动态段只占 1~16 秒可配,静止尾段是提前截取的清晰关键帧拉长(「Live 感柔化收尾」用 FFmpeg blend 时间表达式做 0.25 秒柔化再回到清晰帧,零模型费用);发布文案在视频任务创建后立即并行生成,不等成片,也绝不会重复触发视频调用。二创三版批量走数据库租约 + 逐卡检查点,失败重试只补缺失的图片,重复请求零 Planner 调用。
真人手写。 手抄式图文要的是「像真人写的」。这条路走了四个阶段,方向绕了整整一圈:最早想用 CSS 手写字体伪造——否,一眼假;第二阶段写了 CanvasHandwritingRenderer,用 @napi-rs/canvas 按字符做确定性扰动(旋转、基线、字宽、墨色、行倾斜,种子可重放),默认打包 SIL OFL 的霞鹜文楷——解决了重放和稳定,但本质还是字体渲染;第三阶段转向提示词工程,一天之内从 V5 迭代到 V18:纸纤维微压痕、笔压可见、掠射光、凹槽积墨、行草基线……每一版都在逼近真实,但也每一版都在「教模型表演手写」;最后在 V21 和 8 月 27 日的终版上想通了——别伪造,用真实样本。终版方案:A「清晰自然」/ B「日常随写」/ C「松弛连写」三档,弹窗里展示的全是本租户真实抓取、已完成 OCR 的笔记图片,配一个确定性的字迹摘要分类器(「潦草/连写/行草」归 C,「工整/楷写」归 A),用户必须亲自挑一张真实参考图;生成时 editImageText 带两张参考——第一张锁定当前页背景与文字区域,第二张只约束笔画动作、笔压、墨色、连写习惯,明确禁止复制样本内容。图片模型调用次数不增加,真实感却发生了质变。绕了一圈回到「数据比提示词诚实」,这是本项目我最认的一个产品结论。
六、效果
11 周后交付了什么:
- 一个生产系统:云端多租户部署 + Windows 便携版双形态。创作链路(素材 → 策划 → 无字底片 → 合成 → 发布文案)全程服务端 Provider,前端零凭证。
- 87 个页面/路由的生产构建、201 个测试文件 / 1114 项测试全绿;生产构建从 37 个路由长到 87 个。
- 8 月 18 日到 27 日十天里十几次
release-*-full-replacement整包替换发布,每次带发布前备份与回滚快照;期间最严重的一次线上事故——启动迁移把用户 6 页项目误重建为 1 页——也是先另存快照、再从发布前 SQLite 备份精确恢复到 revision 1141,没有丢过用户数据。 - 爆款采集链路:Chrome/Edge 扩展 + 本地 CDP 执行器,两阶段采集(搜索候选 + 详情补全),图片走 HTTPS 主机白名单 + MIME + 魔数校验后落私有 Asset(
moduleScope隔离,不污染通用素材库);RAG 知识库 14 本书 / 3662 页 / 2438 片段,FTS5 + sqlite-vec 混合检索。 - 便携版从 261MB 共享凭证版进化到 223MB 自配版:包内零凭证(打包校验强制)、免登录、用户模型配置 AES-256-GCM 加密只存本机,敏感信息规则扫描在 tracked 文件与全部 git 历史中 0 命中。
- 给运营省下的真金白银:改文案/贴纸零视频成本重合成、批量二创失败只补缺失图片、付费前强制确认 + 每次调用记账、超时永不自动重试付费请求。
七、反思与后续
做对了的:文档先行且执行顺序写进宪法、四大 Provider 边界第一天定死(后期所有「只改文字不重新生成」的收益都源于此)、Mock 默认 + 付费显式确认、Zod 与 TypeScript 双契约让 1114 项测试有了着力点、发布永远先备份、迁移永远幂等且跳过不该碰的项目。7 月 21 日第一个 commit 里就有 AGENTS.md,这份项目宪法让 AI 协作开发 11 周没有跑偏过边界。
没做好的:无标准数据库 migration,schema 靠 Prisma 与手写 DDL 幂等初始化共同维护,升级漂移风险一直挂着;8 月 19 日到 27 日九天冲刺密度太大,23 号一天修了七个 bug 说明前面的验收债欠了;git 提交在 8 月 18 日后基本停滞、大量成果长期躺在暂存区——10 月 8 日那场 70 文件回退事故之所以惊险,根源就是「工作只存在一个地方」;Turbopack 构建在本机因 PostCSS 子进程端口权限长期失败,靠 Webpack 兜底,始终没根治。
后续方向:Windows 真机双击验收和上架内部工具系统是眼前的事;数据库 migration 基线化必须排上;三风格手写的视觉终验还在等真实双用户浏览器隔离测试。以及一条留给自己的原则,跟上一套系统收尾时写的是同一句:宁可不动,也不乱动;宁可备份多余,也不信任单一副本。
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!























