dressgen-admin-tool.md 6.74 KB

人衣图生成 admin 工具 — 现状与待办

独立于主线换装跳舞直播间任务序列(T1-T39,见 dev-plan*.md)之外的一个 standalone 工具:admin-dressgen.html(新建任务)+ admin-dressgenlist.html(任务列表)+ admin-dressgendetail.html(任务 详情)。输入人物定妆照(全身/脸部 × 正/侧/背,传几张算几张,至少 1 张)+ 1 张新衣服图,依次生成这位人物穿新衣服的全身正/侧/背 3 张图。 2026-09-03 当天从头做到这里,记录一下现状,避免下次接着做时重新踩一遍 已经踩过的坑。


1. 已完成

  • 异步任务队列(不是同步阻塞接口——最早的同步版本真的把前端代理 拖到 502 超时过一次):POST /api/admin/dressgen 立即返回 status:"queued" 的任务行,runDressGenViews()(server.js)在响应 发出后 fire-and-forget 依次(不并发)跑 3 个视角,每一步都写回 dressGenRecords(dress-gen.json),前端轮询 GET /api/admin/dressgen/:id 看进度。
  • 重试:POST /api/admin/dressgen/:id/retry(整体重试,只重跑没成 功的视角)、POST /api/admin/dressgen/:id/retry/:view(单张重试, 可选带 {prompt} 覆盖那一张的 prompt)——都复用任务自己存的图片/ prompt,不用重新上传、不用重新填 prompt。两个重试接口都额外接受可选 {provider} 覆盖,手动把某个任务/某一张换供应商重跑。
  • 两个供应商都已接入并验证成功:
    • GPT Image 2(万界方舟):https://maas-openapi.wanjiedata.com /api/v1/images/edits,model:"GPT Image 2"(带空格,写错会被 WAF 拦 410 或报 unauthorized),rejectUnauthorized:false(万界这个 host 的证书链在这台机器上验证不过,-k),300s 超时,60-180秒/张。
    • Seedream v4(wavespeed.ai 的 bytedance/seedream-v4/edit):上传 POST /api/v3/media/upload/binary → 提交 POST /api/v3/{model} → 轮询 GET /api/v3/predictions/{id}/result,约 30 秒/张,$0.027/次, 无 TLS 证书问题、无内容审核。.env 已配置 WAVESPEED_API_KEY。
    • 默认行为(2026-09-03 当天二次确认的产品决策,覆盖了更早的"不做 自动切换"那版):新任务默认用 image2;某一视角 image2 失败(不 区分技术故障还是审核拒绝)→ 自动改用 wavespeed 重试一次,成功的话 该视角 viewProvider[view] 记为 "wavespeed",前端详情页会显示 "实际使用: Seedream v4"。手动重试接口仍可传 {provider} 强制指定。 这条决策的取舍写在 server.js 里 WAVESPEED_API_KEY 声明上方的 注释里,包含为什么会改主意——下次改这块之前先看那段注释。
  • 17 个自动化测试全过(dressgen-generate.test.js:任务创建即时 返回、轮询到终态、per-view 自定义 prompt、单视角失败不拖累其它两个、 整体/单视角重试(含 provider 覆盖)、非法路径防护、wavespeed 供应商 路径、image2→wavespeed 自动降级等)。
  • 前端拆成三个页面(原来是"新建+列表"揉在一张页面里,"详情"按钮在 宽表格右侧、窄窗口下需要横向滚动才能看到,容易被当成"消失了"):
    • admin-dressgen.html:新建任务表单,提交后跳到该任务的详情页。
    • admin-dressgenlist.html:任务列表,操作列(详情/重试/删除)用 position: sticky; right: 0 钉住,横向滚动时始终可见;所有缩略图 (人物参考图/衣服图/生成结果)点击可放大预览(一个简单的 lightbox,点哪张放大哪张,点遮罩关闭)。
    • admin-dressgendetail.html:单任务详情(原 admin-dressgen-task. html 改名),生成结果图也支持点击放大,每张视角显示"实际使用"的 供应商。
    • 其它 admin 页面导航栏的"人衣图生成"链接都改指向 admin-dressgenlist.html(列表作为默认落地页,列表页右上角 "+新建任务" 去创建页)。

2. 待办

  • [ ] 生成结果目前是完全独立的列表(dress-gen.json),不会进 characters.json/garments.json(人物库/衣服库)。现状确认 (2026-09-03):人物库现在每个人物只有单张 imageUrl,不是"一个 人物对应多张可复用资产图"的模型——characters.json 唯一一条 种子记录也只有一个 imageUrl 字段,没有相册/资产列表这回事。 "存为新人物"要做成什么形态(人物库本身要不要先改成多资产模型、 用 dressgen 的哪张视角图当定妆照等)还没定,做之前需要先对齐。

3. 已完成(第二批,2026-09-03 当天晚些时候)

  • 任务僵死检测:server.js 的 resumeDressgenTasks(),进程启动时 (resumeGenerations()/resumeDressupGenerations() 旁边)扫一遍 dressGenRecords,把仍停在 queued/generating 的视角标记为 failed(错误信息"进程重启,任务丢失,请点重试",前端详情页会照常 显示),并按 T8 generationJobs 同款逻辑重算整条任务的 status。 没有可恢复的 in-flight 请求 id(image2/wavespeed 调用是 runDressGenViews 里直接 await 的 HTTP 往返,进程一退什么都不剩), 所以是标失败而不是真的续跑——用户点"重试"即可,跟真实生成失败的 体验一致。测试:dressgen-generate.test.js 里专门起了一个独立 cwd/mock(mock 故意不响应,制造一个真实卡在 generating 的视角), SIGKILL 模拟崩溃,重新起一个指向同一份 dress-gen.json 的进程, 断言恢复后的状态。
  • 列表分页:GET /api/admin/dressgen 现在接受 ?page=&pageSize= (默认 pageSize=20,上限 100),按 createdAt 倒序(最新的在前) 切片,返回 {items, total, page, pageSize}(之前是裸数组,前端自己 .reverse()——这是个破坏性的响应格式变更,前端和测试已同步改掉)。 admin-dressgenlist.html 加了"上一页/下一页",操作列还是 sticky, 互不冲突。

3. 相关文件

  • server.js:IMAGE2_*/WAVESPEED_* 常量、generateDressImage/ generateDressImageViaImage2/generateDressImageViaWavespeed/ runDressGenViews(含自动降级分支)/dressGenImageAbsPaths、 /api/admin/dressgen* 路由
  • dressup-store.js:dressGenRecords
  • dress-gen.json:数据文件
  • admin-dressgen.html(新建)/ admin-dressgenlist.html(列表)/ admin-dressgendetail.html(详情):前端
  • dressgen-generate.test.js:测试
  • .env:IMAGE2_API_KEY / IMAGE2_API_BASE / WAVESPEED_API_KEY / WAVESPEED_API_BASE