# 人衣图生成 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`