From 30e370ca45e765d01b347f25f0ac71fc1e823c46 Mon Sep 17 00:00:00 2001 From: yibo Date: Wed, 2 Sep 2026 10:47:54 +0800 Subject: [PATCH] Plan second-phase tasks: dance.html + free wardrobe mode (T11-T17) --- docs/todo/dev-plan-2.md | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+), 0 deletions(-) create mode 100644 docs/todo/dev-plan-2.md diff --git a/docs/todo/dev-plan-2.md b/docs/todo/dev-plan-2.md new file mode 100644 index 0000000..7bbdece --- /dev/null +++ b/docs/todo/dev-plan-2.md @@ -0,0 +1,115 @@ +# 换装跳舞直播间 — 第二期开发任务规划 + +需求见 `docs/todo/dressup-dance-room.md`(下称"需求文档")第 3 节模式一、 +第 5.1 节年龄确认;设计见 `docs/todo/dressup-dance-room-mvcp.md`(下称 +"MVCP")V.1.1/M.8/P.1;架构见 `docs/structure/architecture.md`(下称 +"架构文档")2.1/2.5/2.6 节。第一期(后台三库 + 冷启动批量生成)任务表 +见 `docs/todo/dev-plan.md`,T1-T10 已全部完成并提交 +(`59c7398`/`4d437a4`/`7361786` 等),但**第一期出口清单里"批量跑出 ≥30 +条冷启动视频、人眼抽查"这一步还没做**——那是运营往三个库里灌真实素材 ++ 实跑 H3 GPU 集群的操作性步骤,不是代码任务,不阻塞本文档规划,但阻塞 +本文档任务落地后的"有内容可看"这个体验(衣柜是空的话,模式一自然选不 +出东西)。 + +工时是**粗估**,单位人天,用来排先后不用来考核。任务编号接着第一期的 +T1-T10 往下编,避免和 `dev-plan.md` 混淆。 + +--- + +## 0. 第二期范围(需求文档 9 节第 2 条) + +**做**:`dance.html` 上线(含首次进入的年龄确认+免责声明,5.1 节), +接入"衣柜选衣服"免费模式(3.1 节)+ 相同人物+衣服组合去重缓存(4.6 +节,M.7 唯一索引已在 T8 落地),复用现状排播队列基础设施思路但**要为 +`dance` 频道重新实现一份独立的 schedule/promptQueue**(架构文档 2.1/2.5 +节:两条频道各自维护自己的时刻表,不是共享一份)。此阶段全部免费内容 +仍按 v1 现有投票机制排序,**不涉及"付费插队"这条新规则**(那是 +`priority: paid_boost`,架构文档 2.6 节,第五期才有)。 + +**明确不做**(留到后续分期,见需求文档 9 节 3-5): + +- 模式二(付费传衣服)、模式三(付费传人物)——两者都要求账号+积分体系 + 先落地(第三/四期),本期不做上传+审核+扣积分链路。 +- 用户注册/登录(`sessions` 完整实现,M.10)——本期沿用现有匿名 + `viewer_uid` cookie,跟 `live.html` 一致(架构文档 2.7 节:"免费模式一 + 不强制登录")。 +- 积分、`pricing_config`、充值、退款(第四期)。 +- `blacklist`(M.11)人工拉黑表、人脸黑名单审核(第四/五期;模式一本身 + 不接受用户上传内容,风险面本来就比模式二/三小得多,可以先不接这道 + 口子)。 +- `admin-playback.html`(插播/置顶/暂停填充,6.5 节)——锦上添花,不是 + 第二期出口条件,见第 4 节"明确不在第二期范围"。 +- fal.ai 接入(第五期)。 + +--- + +## 1. 依赖关系 + +```mermaid +flowchart TD + T11[T11 dance 频道独立播出状态机] --> T15[T15 POST /api/dance/pick] + T12[T12 GET /api/dance/wardrobe] --> T16[T16 衣柜 tab 前端接线] + T15 --> T16 + T13[T13 dance.html 页面骨架] --> T16 + T13 --> T14[T14 年龄确认弹窗] + T11 --> T17[T17 点赞/在线人数] + T13 --> T17 + + classDef ready fill:#d4edda,stroke:#28a745 + class T11,T12,T13,T14 ready +``` + +T11/T12/T13/T14 互不依赖,可以并行开工;T15 需要 T11 的时刻表先能收 +`videoId` 入队;T16 是前端接线,需要 T12/T13/T15 都有了才能真正跑通端 +到端(但页面骨架本身可以先搭起来,用假数据联调);T17 依赖 T11(复用它 +的 tick)和 T13(页面上要有展示位)。 + +--- + +## 2. 任务表 + +| # | 任务 | 依赖 | 验收标准 | 工时 | +|---|---|---|---|---| +| T11 | **dance 频道独立播出状态机**:新增 `dance-state.json`(`{ schedule, promptQueue }`,结构参照 `live-state.json` 但按 MVCP M.8 的**要求**——`schedule` 条目存 `videoId` 引用,不内联整份 clip 快照,读取时 join `library.json`);`ensureSchedule`/tick 逻辑参照 v1(`server.js` 现有 `schedule`/`promptQueue`/`ensureSchedule`),改成只从 `library.json` 里 `channel === "dance"` 的 clip 里选片 | T1(已完成,读写模式复用)、T8(已完成,产出的 clip 已带 `channel:"dance"`) | 独立于 `live.html` 的 schedule 运行,互不干扰;进程重启后时刻表从 `dance-state.json` 恢复,不会因为读到旧的内联 clip 快照而跟 `library.json` 当前数据不一致(这是 M.8 强调的坑);新增 `GET /api/dance/live/state`,返回结构镜像 `/api/live/state`(`current`/`upNext`/`queue`) | 1.5 | +| T12 | **`GET /api/dance/wardrobe`**:公开只读接口,列出 `garments` 里 `isActive === true` 的条目(复用 `dressup-store.js` 的 `garments` store,不需要 admin 鉴权——这是观众端接口) | T4(已完成) | 下架的衣服不出现在返回列表里;无需登录/cookie 也能拿到 200 | 0.3 | +| T13 | **`dance.html` 页面骨架**(MVCP V.1.1):顶部条(Logo/在线人数占位)+ 左侧衣柜 tab(先只做这一个 tab,传衣服/传人 tab 留空占位,第三/四期再接)+ 播放器容器按 **9:16** 设计(不是 `live.html` 现有横向容器拉伸)+ 右侧点赞/排队展示(无文字输入框)+ 移动端底部 tab 切换 | 无 | 页面能独立打开(不依赖登录);播放器容器在桌面和一个典型移动端视口下都不出现大黑边;跟 `live.html` 视觉语言一致(同一套 `style.css`/组件风格) | 1.5 | +| T14 | **首次进入年龄确认弹窗**(5.1 节):非模态强制(不能通过点击背景关闭,必须点"已满18岁"才能继续),确认后写 cookie(**不需要**登录态,架构文档 2.7 节:免费模式一不强制登录,这条确认走 v1 昵称弹窗同款的纯前端 cookie 记忆套路),刷新页面不再弹 | T13 | 首次进入必弹;确认一次后关闭浏览器再打开(cookie 还在)不再弹;显式展示一句免责声明文案 | 0.3 | +| T15 | **`POST /api/dance/pick` {garmentId}**(P.1 流程):取当前 `characters` 里 `isDefault === true` 的那条(T3 已支持"只有一条默认"),调用 T8 已实现的 `submitGeneration({characterId, garmentId})`(`server.js`,已支持 M.7 去重缓存);命中缓存或生成完成后,把 `videoId` 推进 T11 的 dance `promptQueue`(而不是止步于"落进 library.json"——T8 原本只做到落库,这里要接上"排入队列"这一步,是 T8 的直接延伸) | T11 | 提交一件下架的衣服(`isActive:false`)返回 400;同一衣服两个不同 cookie 各提交一次,第二次是缓存命中、秒级返回并排入队列(不重新起 H3 任务);进程重启后,仍在 `generating` 状态的 pick 请求能续上(复用 T8 `resumeDressupGenerations` 的机制,不用重写) | 1 | +| T16 | **衣柜 tab 前端接线**:调 T12 拿列表渲染缩略图网格,点击调 T15 提交,提交后在"生成中"区域展示一条状态(复用 `live.html` 现有"生成中"列表的交互思路——`promptQueue` 里 `status` 驱动 UI,不是重新设计一套状态展示);播放器轮询 T11 的 `/api/dance/live/state` 按 `endAt` 精确切片(同 `live.html` 现有逻辑) | T12, T13, T15 | 选一件衣服 → 出现"生成中"提示 → 完成后自动进入播放队列,不需要手动刷新页面;两个浏览器 tab 看到的"当前播放"一致(服务端权威时钟,架构文档 1.2 节这条铁律在 dance 频道也要成立) | 1 | +| T17 | **点赞 + 在线人数**(dance 频道独立计数):复用 v1 `POST /api/queue/vote/:id`/在线人数统计的逻辑,但作用域限定在 dance 频道自己的 `promptQueue`/viewer 心跳(不能跟 `live.html` 共用同一份计数,否则"在线人数"会把两个频道的观众混在一起) | T11, T13 | 点赞数变化影响 dance 免费队列的排序(v1 现有投票机制,本期不做付费插队);`live.html` 和 `dance.html` 分别打开,两边"在线人数"独立变化,互不叠加 | 0.5 | + +**总工时约 6.1 人天**,T11/T12/T13/T14 四条(约 3.6 人天)可以完全并 +行;T15-T17(约 2.5 人天)依赖前面几条但彼此间也有一定并行空间(T15 +后端 和 T16 前端骨架可以同时开工,最后再联调)。 + +--- + +## 3. 第二期出口 + +- `dance.html` 能独立打开,首次进入弹年龄确认,确认后不再弹; +- 衣柜 tab 能看到后台已上架的衣服(**依赖第一期出口"批量生成 ≥30 条" + 先落地,不然衣柜是空的,见本文档开头的提示**); +- 免费选一件衣服 → 提交 → 排入独立于 `live.html` 的播出队列 → 播放; +- 同一件衣服被多个观众选中时,第二次起命中缓存,秒级排队,不重新起 + GPU 任务; +- 点赞/在线人数在 dance 频道内正常工作,且跟 `live.html` 的计数互不 + 干扰; +- 全程不涉及登录、积分、上传、审核。 + +达成后才进第三期(用户注册体系,见需求文档 9 节第 3 条)。 + +--- + +## 4. 明确不在第二期范围 + +避免执行时范围蔓延: + +- 模式二/三(付费传衣服/传人)的任何后端或前端部分(第三/四期,需要先 + 有账号+积分) +- 用户注册/登录 `sessions` 完整实现(第三期) +- 积分、`pricing_config`、充值、退款(第四期) +- `blacklist`(M.11)CRUD 后台页面、人脸黑名单审核(第四/五期) +- `admin-playback.html` 插播/置顶/暂停填充(6.5 节,非本期出口条件, + 有余力可顺手做但不阻塞验收) +- "我的作品"页面(7.4 节,依赖账号体系,第五期) +- fal.ai 接入、付费插队排播优先级(`priority: paid_boost`,第五期) -- libgit2 0.21.4