Commit daebde4d5f43f1e16781c2752a789dfefa056742

Authored by yibo
1 parent 15ff261e

Add dressup-dance live room requirements, current-state, and architecture docs

Documents the new outfit-swap dance live room (dance.html) requirements
alongside the existing text-prompt live channel baseline, plus the
combined system architecture covering both.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdtXTsDRz5QWMjvt1HBXLz
docs/done/live-channel-v1.md 0 → 100644
  1 +# AI 短剧频道 v1 —「文字点播」直播间(已完成)
  2 +
  3 +状态:已上线运行(`server.js` + `live.html`,pm2 进程 `minimax-live`)。
  4 +本文档记录**当前已实现**的需求范围,作为后续「换装跳舞直播间」(见
  5 +`docs/todo/dressup-dance-room.md`)改造的基线参照。
  6 +
  7 +## 1. 产品概述
  8 +
  9 +一个 7x24 播放的虚拟直播间。所有观众看到同一个「虚拟播出时刻表」
  10 +(server 端统一计算的播放进度,非各自独立播放),可以:
  11 +
  12 +- 直接观看正在播放的 AI 生成短视频(文字弹幕点播 + 随机填充)
  13 +- 在聊天区发一句话 → 命中素材库则很快排入播放;不命中则提交给 H3
  14 + 后台生成新片段,生成完成后排入播放
  15 +- 给候选片段投票,得票最高的优先播出
  16 +- 浏览「最新 / 最早 / 最热」三种非直播的素材库列表,任意点播
  17 +
  18 +## 2. 已实现的核心能力
  19 +
  20 +### 2.1 虚拟播出时刻表(直播的核心机制)
  21 +
  22 +- `server.js` 维护内存中的 `schedule`(`[{clip, startAt, endAt, requestedBy}]`),
  23 + 每 2 秒 tick 一次(`ensureSchedule`),始终保证时刻表填充到「未来至少
  24 + `FILLER_DURATION_MS`(30s)」。
  25 +- 选片优先级:投票最高的「已生成待播」(`ready`)> 投票最高的「素材库命中」
  26 + (`matched`)> 素材库随机填充(避开上一条、避开 20 分钟内播过的,两者都
  27 + 没有时退化为纯随机,保证时刻表永不断流)。
  28 +- 所有观众通过轮询 `GET /api/live/state`(4s 一次)拿到同一份
  29 + `{current, upNext, queue}`,前端用「服务端 endAt 减本地时间」精确定时
  30 + 切下一条,轮询只作为纠偏/兜底,不是切片的唯一触发点。
  31 +- 状态持久化到 `live-state.json`,进程重启后从中恢复,不会冷启动断播。
  32 +
  33 +### 2.2 文字点播驱动生成
  34 +
  35 +- 观众聊天发送文本 → 先做违禁词审核(`moderateText`,子串黑名单,非语义
  36 + 审核)→ 通过后 `submitPromptToQueue`:
  37 + - 命中素材库(中/英文 prompt 精确或子串匹配)→ 直接进入待播队列
  38 + (`status: matched`)。
  39 + - 未命中 → 调用 H3 Console `POST /v1/videos`(文生视频,832x480/120帧/
  40 + 20步)异步生成,`status: generating`,生成完成后自动写入
  41 + `library.json` 并推入待播队列(`status: ready`)。
  42 +- 生成是完全后台化的(H3 自建集群单次约 200s),绝不阻塞直播时刻表或
  43 + HTTP 响应。
  44 +- `POST /api/prompt` 是同一条提交路径的非聊天版本(供测试/直接调用)。
  45 +
  46 +### 2.3 排队投票
  47 +
  48 +- `POST /api/queue/vote/:id`:按 `uid`(cookie)对某条排队中的
  49 + matched/generating/ready 条目投票/取消投票,票数决定谁先播(2.1)。
  50 +
  51 +### 2.4 素材库浏览模式
  52 +
  53 +- 三个 tab:最新 / 最早 / 最热(`GET /api/library?sort=`),点击任意条目
  54 + 立即播放,播完自动顺延下一条(`ended` 事件),与直播模式的时刻表机制
  55 + 完全独立。
  56 +
  57 +### 2.5 观众身份
  58 +
  59 +- 首次进聊天框(focus 触发,不需要先点发送)弹昵称输入框,生成
  60 + `uid`(`crypto.randomUUID`)+ `nickname`,写入 cookie(365 天)。
  61 +- 顶部常驻「昵称 ✎」可随时改名,`uid` 不变(投票/点播历史不丢失)。
  62 +- 无正式账号体系——身份完全基于浏览器 cookie,换设备/清 cookie 即视为
  63 + 新观众。**这是 v2 用户注册需求要解决的缺口**,见 `docs/todo`。
  64 +
  65 +### 2.6 在线人数
  66 +
  67 +- 每次 `/api/live/state` 轮询带 `cid`(随机 clientId,非 cookie,纯前端
  68 + 内存变量)打点到 `viewers: Map<cid, lastSeenAt>`,12s 内有心跳视为在线,
  69 + 服务端返回 `watching` 计数。
  70 +
  71 +### 2.7 内容审核 / 限流
  72 +
  73 +- 审核:仅关键词黑名单(`BANNED_WORDS`),非语义级别;生成内容本身的
  74 + 安全检查依赖 H3 Console 自身的生成时安全策略(被拒会以「生成失败」
  75 + 形式体现在队列里)。
  76 +- 限流:按 IP,5 分钟内最多 3 次聊天/点播提交(`RATE_LIMIT_MAX/WINDOW_MS`)。
  77 + 无账号维度限流。
  78 +
  79 +### 2.8 后台管理(`admin.html`)
  80 +
  81 +- 表格形式管理 `library.json`:搜索(名称/prompt/id)、按来源筛选
  82 + (h3console / manual)、任意列排序、行内编辑、删除。
  83 +- 「新增」表单可手动挂载一条外部视频地址(`source: manual`)的素材,
  84 + 不经过 H3 生成。
  85 +- 编辑/新增会置 `edited: true`,让后续自动同步(2.9)不覆盖人工修改。
  86 +- 删除一条 `h3console` 来源的素材,会级联调用 H3 Console 的
  87 + `DELETE /v1/videos/:id`(移入 H3 自己的回收站),避免下次同步又冒出来。
  88 +- **无鉴权**——`admin.html` 和所有 `/api/admin/*` 接口对能访问到该端口的
  89 + 任何人开放,依赖网络边界(内网/反代)而非应用层鉴权。
  90 +
  91 +### 2.9 与 H3 Console 的集成
  92 +
  93 +- `ingest.js` 的 `syncLibrary()`:启动时 + 每 5 分钟,从 H3 Console 拉取
  94 + 全量 job 列表重建 `library.json`(规则提取短 prompt,过滤基础设施
  95 + 烟雾测试 job,保留 `likes`,跳过 `edited: true` 的条目)。这让直接在
  96 + H3 Console 网页端产出的内容,5 分钟内自动出现在直播间素材库。
  97 +- 视频本体不落本地盘:`GET /lib/video/:id` 服务端代理转发 H3 Console 的
  98 + `/v1/videos/:id/file`(`H3_CONSOLE_TOKEN` 不下发到浏览器);若素材有
  99 + `videoUrl`(manual 来源或管理后台设置过)则 302 跳转到该地址。
  100 +- 详细 API 契约见 `H3-CONSOLE.md`(H3 Console 是独立于本项目的内部服务,
  101 + 4 卡 GPU 单节点,无按次计费)。
  102 +
  103 +## 3. 数据模型(现状)
  104 +
  105 +`library.json`(数组,逐条追加,无数据库):
  106 +
  107 +```jsonc
  108 +{
  109 + "id": "19c6b745a68e", // h3console: H3 job id;manual: manual-<ts><rand>
  110 + "source": "h3console", // "h3console" | "manual"
  111 + "edited": false, // true = 后台改过/手动新增,同步时跳过重建
  112 + "prompt": "...", // 展示用短 prompt(中文)
  113 + "promptEn": "...", // 可选,英文短 prompt
  114 + "fullPrompt": "...", // 完整生成 prompt(H3 结构化 prompt 或用户原话)
  115 + "videoUrl": "...", // 可选,外部视频地址(manual 或后台改过播放源)
  116 + "width": 832, "height": 480,
  117 + "duration": 5, // 秒
  118 + "partition": "text", // text | ref2va | fl2va | manual
  119 + "likes": 0,
  120 + "lastPlayedAt": 1788250666831, // 可选,用于播出冷却
  121 + "createdAt": 1788250666831
  122 +}
  123 +```
  124 +
  125 +`live-state.json`:`{ schedule, promptQueue, chatMessages }`,结构见
  126 +`server.js` 顶部注释,逐 tick 全量覆写。
  127 +
  128 +## 4. 已知限制(未做的事,供 v2 参考)
  129 +
  130 +- 无正式用户账号(仅 cookie 身份)、无积分/消费体系。
  131 +- 素材类型单一:只有「生成好的视频片段」,没有可复用的「人物」「服装」
  132 + 「参考动作/音乐视频」这类结构化中间资产——每条素材都是一次性的最终
  133 + 产物,无法组合复用。
  134 +- 生成路径只有「纯文字 → H3 文生视频」一条,没有「参考图/参考视频驱动」
  135 + 的生成路径(H3 Console 本身支持 `ref2va`/`fl2va`,但本项目尚未从
  136 + `/api/prompt`、`/api/chat` 之外的路径调用过)。
  137 +- 无人工审核队列,内容一旦生成完成即自动排入公开播放,无「先审后播」
  138 + 开关。
  139 +- 无任何鉴权,管理后台和写接口都是「网络可达即可操作」。
docs/structure/architecture.md 0 → 100644
  1 +# 系统架构文档
  2 +
  3 +覆盖现状(v1,已上线)与规划中的「换装跳舞直播间」(v2,见
  4 +`docs/todo/dressup-dance-room.md`)。需求范围见对应文档,本文档只谈
  5 +组件划分、数据流、集成方式和存储演进。
  6 +
  7 +## 1. 现状架构(v1)
  8 +
  9 +### 1.1 组件
  10 +
  11 +```mermaid
  12 +flowchart LR
  13 + subgraph Browser
  14 + live[live.html<br/>直播间/浏览]
  15 + admin_ui[admin.html<br/>素材后台]
  16 + end
  17 +
  18 + subgraph Node["server.js (单文件 Node http server, PORT 8934)"]
  19 + api[路由处理<br/>/api/*, /lib/video/*]
  20 + sched[schedule + promptQueue<br/>2s tick]
  21 + ingest[ingest.js<br/>syncLibrary 每5分钟]
  22 + end
  23 +
  24 + libjson[(library.json)]
  25 + statejson[(live-state.json)]
  26 + h3[(H3 Console<br/>内部 MiniMax H3 集群<br/>/v1/videos)]
  27 + fal[(fal.ai queue API<br/>/fal/* 反代)]
  28 +
  29 + live -- 轮询 /api/live/state, /api/chat --> api
  30 + live -- POST /api/chat, /api/prompt, /api/queue/vote --> api
  31 + admin_ui -- CRUD /api/admin/library --> api
  32 + api -- 读写 --> libjson
  33 + sched -- 落盘 --> statejson
  34 + api --> sched
  35 + sched -- POST /v1/videos, 轮询状态 --> h3
  36 + api -- GET /v1/videos/:id/file 代理 --> h3
  37 + ingest --> h3
  38 + ingest -- 重建 --> libjson
  39 +
  40 + legacy[index.html/app.js/newtask.html<br/>独立的 fal.ai 任务提交小工具<br/>与直播间无数据关联] -.-> fal
  41 +```
  42 +
  43 +### 1.2 关键设计点
  44 +
  45 +- **单进程、无框架、无数据库**:`server.js` 用 Node 原生 `http` 模块手写
  46 + 路由,持久化就是两个 JSON 文件(`library.json` 素材库、`live-state.json`
  47 + 直播状态),全量读写、无锁、无并发写保护——量级小的场景下够用,见
  48 + 1.4 存储演进建议里什么时候该换。
  49 +- **服务端权威时钟**:直播"当前播放的是哪条"由服务端 `schedule` 数组
  50 + 决定,浏览器只是被动轮询同步,杜绝"每个观众各自播各自的"——这是
  51 + "直播感"的核心,v2 任何改造都不能破坏这个前提。
  52 +- **H3 Console 是外部服务,本项目只是它的一个消费方**:内部 MiniMax H3
  53 + 视频生成集群,独立部署、独立鉴权(`H3_CONSOLE_TOKEN`),契约见
  54 + `H3-CONSOLE.md`。`server.js` 里所有跟"生成视频"相关的逻辑,都是在编排
  55 + 这个外部 API(提交任务 → 轮询 → 落库),本身不做任何 GPU 推理。
  56 +- **`/fal/*` 反代通道**:转发到 `queue.fal.run`(fal.ai 官方队列 API),
  57 + 目前只被仓库里独立的 `index.html`/`app.js`/`newtask.html`/`taskdetail.html`
  58 + 这套"手动提交 H3/H3-Max 生成任务"小工具使用,跟直播间(`live.html`)
  59 + 没有数据关联。**v2 的付费实时生成路径会正式启用这条通道**,理由见
  60 + 2.3。
  61 +- **无鉴权边界**:管理后台 (`admin.html`) 和所有写接口都没有应用层鉴权,
  62 + 依赖网络可达性(内网/反代层面控制)。v2 引入用户账号体系后,管理后台
  63 + 这块的"零鉴权"现状需要重新评估(至少后台管理接口应该要求管理员身份,
  64 + 这不是本次需求主线但是个不能一直拖的技术债)。
  65 +
  66 +### 1.3 直播时刻表状态机(供 v2 复用/扩展参考)
  67 +
  68 +```mermaid
  69 +sequenceDiagram
  70 + participant V as 观众浏览器
  71 + participant S as server.js
  72 + participant H3 as H3 Console
  73 +
  74 + V->>S: POST /api/chat {text}
  75 + S->>S: moderateText() 关键词审核
  76 + alt 命中 library.json
  77 + S-->>V: {status: matched}
  78 + else 未命中
  79 + S->>H3: POST /v1/videos {prompt}
  80 + S-->>V: {status: generating}
  81 + loop 轮询直到 done/error
  82 + S->>H3: GET /v1/videos/:id
  83 + end
  84 + S->>S: 写入 library.json, 状态置 ready
  85 + end
  86 + Note over S: ensureSchedule() 每 2s tick<br/>按投票数从 ready/matched 里选片<br/>不够则从素材库随机填充
  87 + loop 每 4s
  88 + V->>S: GET /api/live/state
  89 + S-->>V: {current, upNext, queue}
  90 + end
  91 + Note over V: 前端用 endAt 精确定时切片<br/>轮询只是纠偏/兜底
  92 +```
  93 +
  94 +### 1.4 存储演进建议
  95 +
  96 +JSON 文件方案在 v1(单一素材类型、无账号、无并发写压力)是合适的
  97 +"够用就好"选择。v2 一旦引入下列任一项,建议评估迁移到嵌入式数据库
  98 +(如 SQLite,单文件、无需额外部署,跟现有"零运维依赖"的风格最接近):
  99 +
  100 +- 用户账号 + 密码/凭证存储(JSON 明文文件不适合放凭证,即便是 hash 后
  101 + 的也建议至少上 SQLite)。
  102 +- 积分流水 + 人物"剩余可播 10 秒片段数"这类计数器(需要"扣积分 + 创建
  103 + 生成任务 + 扣减片段配额"这几步操作的原子性,全量重写 JSON 文件在
  104 + 并发下有丢更新风险)。
  105 +- 资产种类变多(人物/衣服/参考视频/合成图/生成视频五张表,互相有外键
  106 + 引用关系,继续用一个大 JSON 数组的心智负担会明显上升)。
  107 +
  108 +不建议现在就迁移——v2 第一期(`docs/todo` 第 9 节分期建议)如果只是先
  109 +用内部 H3 Console 跑冷启动种子内容、不涉及账号和并发写,继续加 JSON
  110 +文件(比如新增 `characters.json`/`garments.json`/`dance-refs.json`,
  111 +沿用 `library.json` 的读写模式)完全够用;到第三/四期引入账号+积分时
  112 +再迁移,避免过早优化。
  113 +
  114 +## 2. 规划架构(v2:换装跳舞直播间)
  115 +
  116 +### 2.1 两个独立的直播间页面
  117 +
  118 +`live.html`(文字点播)与 `dance.html`(换装点播)是**两条并行的产品
  119 +线**,不是同一个页面的两个 tab:
  120 +
  121 +- 各自维护独立的 `schedule`/`promptQueue`(各自的播出时刻表和排队),
  122 + 互不影响对方的播放进度。
  123 +- 共用同一套底层基础设施:素材库读写模式(`library.json` 风格)、
  124 + H3 Console/fal.ai 生成编排、观众身份(cookie/账号)、投票机制、
  125 + 在线人数统计。
  126 +- `dance.html` 复用 `live.html` 的视觉语言(同一套 CSS 风格、播放器、
  127 + 排队列表 UI),保留在线人数/点赞/排队展示,**唯一的差别是左侧点播
  128 + 面板**:`live.html` 是文字输入框,`dance.html` 是"衣柜选择 / 传衣服 /
  129 + 传人"操作面板,没有文字输入。
  130 +
  131 +### 2.2 新增组件
  132 +
  133 +```mermaid
  134 +flowchart TB
  135 + subgraph Browser
  136 + dance[dance.html<br/>衣柜/传衣服/传人,无文字输入]
  137 + admin2[admin.html<br/>+ 人物/衣服/参考视频/用户 管理页]
  138 + end
  139 +
  140 + subgraph Node["server.js(扩展)"]
  141 + api2[路由扩展<br/>/api/dressup/*, /api/admin/characters, garments, dance-refs, users]
  142 + pipeline[生成流水线编排<br/>Stage1 → Stage2<br/>按请求类型路由到不同算力]
  143 + moderation[审核<br/>模式二:图片内容审核<br/>模式三:人脸黑名单过滤]
  144 + credit[积分校验/扣减/退还<br/>+ 人物剩余片段配额]
  145 + end
  146 +
  147 + charjson[(characters.json)]
  148 + garmjson[(garments.json)]
  149 + dancejson[(dance-refs.json)]
  150 + libjson2[(library.json<br/>+ characterId/garmentId/refDanceVideoId 溯源字段)]
  151 + userdb[(users + credits<br/>建议 SQLite,见 1.4)]
  152 +
  153 + fal2[(fal.ai<br/>Stage1 换装图合成<br/>+ 付费实时 Stage2 视频生成)]
  154 + h32[(内部 H3 Console<br/>冷启动/后台种子内容<br/>批量 Stage2 视频生成)]
  155 +
  156 + dance -- 免费:选衣柜 --> api2
  157 + dance -- 付费:传衣服/传人 --> api2
  158 + api2 --> moderation
  159 + api2 --> credit
  160 + moderation -- 通过 --> pipeline
  161 + credit -- 扣减通过 --> pipeline
  162 + pipeline -- Stage1 --> fal2
  163 + fal2 -- 换装参考图 --> pipeline
  164 + pipeline -- Stage2 冷启动/种子 --> h32
  165 + pipeline -- Stage2 付费实时 --> fal2
  166 + h32 -- 跳舞视频 --> pipeline
  167 + fal2 -- 跳舞视频 --> pipeline
  168 + pipeline -- 落库 --> libjson2
  169 + admin2 --> charjson
  170 + admin2 --> garmjson
  171 + admin2 --> dancejson
  172 + admin2 --> userdb
  173 +```
  174 +
  175 +### 2.3 双算力策略:冷启动 vs 付费实时(已确认,见 `docs/todo` 4.3)
  176 +
  177 +Stage2(视频生成)按"是否是付费实时请求"路由到两条不同算力:
  178 +
  179 +- **冷启动/后台种子内容**(admin 批量预生成,铺满衣柜库/默认人物库)
  180 + → 走内部 H3 Console(免费,但单条约 200s,见 `H3-CONSOLE.md`)。这条
  181 + 路径不面向单个观众的实时请求,批量跑不受时间压力。
  182 +- **观众付费实时请求**(模式二付费传衣服、模式三付费传人物)→ 走
  183 + fal.ai 云端(`minimax/h3*` 系列端点,约 4s 级别,见 `H3-CONSOLE.md`
  184 + 的实测对比),成本由观众支付的积分覆盖。
  185 +
  186 +Stage1(换装图合成)两条路径下都走 fal.ai——H3 Console 本身是纯视频
  187 +生成 API,不产图,见 `H3-CONSOLE.md` 明确说明。这也是 `/fal/*` 反代
  188 +通道(1.2 节)真正被直播间业务启用的地方:现状它只服务于仓库里跟
  189 +直播间无关的旧工具,v2 里会成为 Stage1 全量、以及付费 Stage2 的核心
  190 +依赖。fal API Key 现状是前端 `newtask.html` 里手填(见 1.2 节代码原样),
  191 +v2 必须挪到服务端 `.env`,不能再让 key 走浏览器。
  192 +
  193 +`reference_video_urls` 这个字段目前只是从 H3 Console 控制台 UI tab
  194 +推断存在、未经本项目实测调用过(`H3-CONSOLE.md` 原话),v2 落地 Stage2
  195 +前第一步就是用真实调用验证这条路径,而不是假设它一定按预期工作
  196 +(`docs/todo` 第 8.2 条)。
  197 +
  198 +### 2.4 视频规格:默认竖屏,移动端优先(仅 `dance.html`)
  199 +
  200 +`dance.html` 的生成请求默认用 **480×832**(`live.html` 现有默认
  201 +832×480 的宽高对调),总像素量与现状完全一致,理论上不会拖慢 H3
  202 +Console/fal.ai 的生成耗时(`docs/todo` 4.4 节)。`live.html` 现有横屏
  203 +内容/默认值不变,两个直播间的规格是独立配置,不共享同一份
  204 +`H3_GEN_PARAMS`。播放器 UI 上,`dance.html` 需要一套按 9:16 设计的
  205 +播放器容器,不能直接复用 `live.html` 现有横屏播放器的宽高比。
  206 +
  207 +### 2.5 与现状的集成点
  208 +
  209 +- **浏览三 tab 不动**:v2 生成的视频最终落地也是 `library.json` 风格的
  210 + 一条 clip(多了几个溯源字段),复用现状浏览逻辑,`dance.html` 只是
  211 + 拥有自己独立的一份 schedule 实例(2.1 节)。
  212 +- **"生成中/排队中"展示复用**:换装流水线本质也是"提交 → 后台异步生成 →
  213 + 完成后进队列",跟现有 `generateForPrompt` 的状态机(generating →
  214 + ready/failed,任务 id 落盘可断点续传)同构,Stage2 直接复用这套逻辑,
  215 + Stage1 失败则更早终止。
  216 +- **`ensureSchedule` 选片逻辑需要实质性扩展**,不是"复用不动"——见 2.6。
  217 +
  218 +### 2.6 排播优先级 + 去重缓存(已确认,对 v1 排片算法的改动)
  219 +
  220 +`dance.html` 的 `promptQueue`/`schedule` 不能直接照抄 v1 的"纯投票排序"
  221 +逻辑,需要两处新行为(`docs/todo` 3 节"共同后置流程"、4.6 节):
  222 +
  223 +- **免费模式一走"先查缓存再生成"**:提交时先用 `(characterId,
  224 + garmentId)` 查是否已有产物,命中直接排入队列(跟 v1
  225 + `findLibraryMatch` 是同一个套路,只是匹配维度从文本相似度换成资产
  226 + id 组合),未命中才真正走 Stage1/Stage2。免费内容排入队列后仍按 v1
  227 + 现有投票机制排序。
  228 +- **付费模式三新人物内容插队播放**:Stage2 完成后**不进入投票排序**,
  229 + 而是直接插入 `schedule`(紧跟在当前播放的片段之后,或队首),跳过
  230 + `pickBestIndexByStatus` 这条"按票数选"的路径。`ensureSchedule` 需要
  231 + 一个新的"优先插入"通道,跟现有"从 promptQueue 里挑最高票"逻辑并行
  232 + 存在,而不是复用同一个排序比较器。
  233 +- 这两点意味着 `dance.html` 的排片模块虽然复用 v1 的整体结构(数据
  234 + 持久化到 `live-state.json` 风格的文件、`ensureSchedule` 定时 tick 的
  235 + 思路),但选片函数本身要重写,不是简单的参数化复用。
  236 +
  237 +### 2.7 用户账号、积分与人物生命周期
  238 +
  239 +- 现有 `viewer_uid`/`viewer_nickname` cookie 身份保留作为过渡态;免费
  240 + 模式一(选衣柜)不强制登录。上传新资产/花积分(模式二、三)才要求
  241 + 邮箱登录(`docs/todo` 7.1)。注册后把 cookie `uid` 绑定到账号 id,
  242 + 历史投票/点播记录不丢。
  243 +- 积分校验点:提交付费流水线(Stage1 触发前)先校验+预扣积分,Stage1
  244 + 或 Stage2 失败要退还——这个扣减+退还路径要求原子性,是 1.4 节里
  245 + "该上 SQLite 了"的触发点之一。
  246 +- **人物生命周期**(`docs/todo` 3.3 节第 7 点、9.5 期):付费生成的新
  247 + 人物带一个"剩余可播 10 秒片段数"配额,每播放一段消耗一份;配额耗尽
  248 + 自动从排播池移除(但人物、换装图、已生成视频全部保留,不删除),
  249 + 用户再充值可以直接续播,无需重新走 Stage1/Stage2——这意味着"人物"
  250 + 和"该人物名下的已生成视频"要能独立于"当前是否在直播池中"被检索到,
  251 + 数据模型上是一个显式的 `status: active|expired` + 关联的积分流水,
  252 + 不是简单的软删除。
  253 +
  254 +## 3. 待验证事项汇总(技术验证,非产品决策分歧)
  255 +
  256 +避免重复散落在需求文档各处,详见 `docs/todo` 第 8 节:
  257 +
  258 +| 待验证项 | 影响范围 |
  259 +|---|---|
  260 +| Stage1 换装模型在 fal.ai 上的具体选型 | 换装一致性、速度、成本 |
  261 +| `reference_video_urls` 在 H3 `ref2va` 下的实测效果 | Stage2 冷启动路径是否可行 |
  262 +| fal.ai 端 `minimax/h3*` 端点是否支持同等的多参考图生视频 | Stage2 付费实时路径是否可行 |
  263 +| 人脸识别黑名单模型/服务选型 | 模式三自动审核能力 |
  264 +| 图片内容审核供应商/接口 | 模式二审核能力 |
  265 +| JSON 文件 → SQLite 的迁移时机 | 存储层改造范围,见本文档 1.4 |
  266 +| 支付网关选型(初步倾向 Stripe,需确认是否要叠加支付宝/微信支付) | 充值流程开发方向,见 `docs/todo` 7.3 |
docs/todo/dressup-dance-room.md 0 → 100644
  1 +# 换装跳舞直播间 — 需求文档(TODO / 未实现)
  2 +
  3 +对应现状基线见 `docs/done/live-channel-v1.md`;架构层面的落地方案见
  4 +`docs/structure/architecture.md`。本文档记录**已与产品方确认的决策**
  5 +(第 2~7 节),以及仍需要技术验证/调研、非产品决策问题的部分(第 8 节)。
  6 +
  7 +## 1. 背景与目标
  8 +
  9 +新增一个跟现有文字点播直播间并列的产品:**换装跳舞直播间**。点播动作从
  10 +"打字描述想看什么"变成"选衣服 / 传衣服 / 传人",本质是把"自由文本生成"
  11 +收窄成"资产库组合生成":
  12 +
  13 +> 人物照片(资产库) + 衣服照片(资产库或观众上传) →
  14 +> 生成"新衣服小姐姐"参考图 → 参考图 + 参考跳舞视频 → 生成新跳舞视频
  15 +> → 排入直播间播放队列
  16 +
  17 +## 2. 产品形态:两个独立的直播间页面(已确认)
  18 +
  19 +- **`live.html`(现状,不变)**:文字点播直播间,观众打字描述想看什么,
  20 + 详见 `docs/done/live-channel-v1.md`。
  21 +- **`dance.html`(新增)**:换装跳舞直播间,**没有文字输入框**,点播动作
  22 + 全部是"选衣柜 / 传衣服 / 传人"。与 `live.html` 样式保持一致(同一套
  23 + 视觉语言、同一套播放器/队列 UI 组件),但左侧点播面板换成换装操作,
  24 + 右侧保留非文字的互动展示(在线人数、点赞、排队列表——这些跟
  25 + `live.html` 一样保留,只是没有聊天输入)。
  26 +- 两个页面各自维护独立的播出时刻表(各自的 `schedule`/`promptQueue`),
  27 + 不是同一个直播间的两个 tab——它们是两条并行的产品线,共用后端的
  28 + 素材库/生成流水线/账号体系等基础设施。
  29 +- **`dance.html` 默认竖屏、移动端优先**(`live.html` 现状横屏不变,
  30 + 已上线跑着,不在本次改造范围内)——具体规格见第 4.4 节。
  31 +
  32 +## 3. 观众体验流程(已确认:使用已有资产库免费,新增资产付费)
  33 +
  34 +### 模式一:从衣柜选衣服(免费)
  35 +
  36 +1. 观众打开"衣柜"面板,浏览后台预置/其他观众上传后已沉淀的服装素材
  37 + (缩略图 + 名称/分类)。
  38 +2. 点选一件 → 人物固定用系统默认人物(免费用户不能指定/上传人物,只能
  39 + 换衣服)。**第一期默认人物固定只有一个**(不做多个轮换/观众选人物,
  40 + 前端不需要人物选择器;后台仍以列表形式管理人物库,为以后想扩展成
  41 + 多个预留空间,见 6.1 节)。
  42 +3. 提交 → 进入生成流水线(第 4 节)→ 完成后排入播放队列。同一件衣服被
  43 + 多个观众点中时,走去重缓存直接复用已有产物,见 4.6 节。
  44 +4. **完全免费,不消耗积分**——这是主力免费入口,降低参与门槛。
  45 +
  46 +### 模式二:上传自己的衣服照片(付费,"新增衣服")
  47 +
  48 +1. 观众上传一张衣服/穿搭图片,走轻量内容审核。
  49 +2. 消耗积分(因为这是往衣服库里"新增"一件素材,成本高于直接选用现成
  50 + 素材)。
  51 +3. 审核通过后默认沉淀进公共衣柜库,之后所有观众(含免费用户)都能在
  52 + 模式一里选到这件衣服——相当于"付费的人帮全站扩充了衣柜"。
  53 +4. 人物仍然是系统默认人物(模式二不涉及换人物)。
  54 +
  55 +### 模式三:上传喜欢的小姐姐照片(付费,"新增人物")
  56 +
  57 +1. 观众上传一张人物照片。
  58 +2. 经自动化审核(第 5 节)后即可生成——不是强制人工先审。
  59 +3. **充值/消耗的积分数量,直接决定这个新人物能播放几个 10 秒片段**——
  60 + 花得越多,这个新角色在直播间露出的时长越长。
  61 +4. 衣服可以在提交时一并选/传,也可以不选(系统随机挑一件衣柜库服装
  62 + 搭配)。
  63 +5. 参考跳舞视频固定走系统随机(第 3.4 点)。
  64 +6. 新人物**默认不沉淀进公共人物池**,只服务上传者本人这次付费请求——
  65 + 不会出现"张三充值上传的照片被李四的免费请求随机抽中生成"的情况。
  66 +7. **到期处理**:购买的那几个 10 秒片段播完后,自动下线,但人物、换装
  67 + 参考图、已生成的视频**都保留记录**——用户之后再充值,可以直接续播
  68 + (复用已有素材,不用重新走一遍 Stage1/Stage2 生成)。
  69 +
  70 +### 三种模式的共同后置流程
  71 +
  72 +- 提交后立刻在"生成中"列表出现一条(带上传者昵称),复用现有排队 UI。
  73 +- 生成成功 → 排入待播队列。**免费内容(模式一/二)按 v1 现有投票机制
  74 + 排序**;**模式三付费新人物内容不参与投票,直接插队/优先播放**(已
  75 + 确认——付费买的是"确定性上镜",不能让它跟免费内容拼投票数),这一点
  76 + 是对 v1 `ensureSchedule` 选片算法的实质性改动,见
  77 + `docs/structure/architecture.md` 1.3/2.6 节。
  78 +- 生成失败/审核不过 → 现有"❌"状态展示;**若是付费请求失败,需要退还
  79 + 已扣的积分**,这是底线(见第 7.2 节)。
  80 +
  81 +## 4. 生成流水线设计(两段式 + 双算力策略)
  82 +
  83 +### 4.1 Stage 1:人物 + 衣服 → 换装参考图
  84 +
  85 +输入一张人物参考图 + 一张衣服参考图,输出"该人物穿该衣服"的合成图,
  86 +人脸/体型一致,只换装扮。走 fal.ai(具体模型见第 8 节,需要小范围实测
  87 +后确定),复用 `server.js` 已有的 `/fal/*` 反代通道——H3 Console 本身
  88 +不产出图片,Stage1 无论如何都得走别的模型。
  89 +
  90 +### 4.2 Stage 2:换装参考图 + 参考跳舞视频 → 生成跳舞视频
  91 +
  92 +换装图(`reference_image_urls`)+ 参考跳舞视频(`reference_video_urls`,
  93 +提供舞蹈动作/运镜/配乐节奏,系统随机/轮换挑选,观众不选风格)→ 生成
  94 +10 秒左右的跳舞视频片段。产物字段是 v1 `library.json` clip 的超集,加
  95 +`characterId`/`garmentId`/`composedImageId`/`refDanceVideoId` 溯源字段。
  96 +
  97 +### 4.3 双算力策略(已确认)
  98 +
  99 +同一个 Stage2(甚至 Stage1)流程,跑在两条不同的算力路径上,按"是否是
  100 +付费实时请求"切换:
  101 +
  102 +| 场景 | 算力 | 理由 |
  103 +|---|---|---|
  104 +| **冷启动/后台种子内容**(admin 预先批量生成,铺满衣柜库/默认人物库的初始内容,不面向单个观众的实时请求) | 内部 H3 Console(自建 GPU 集群) | 免费、无按次成本,但单条约 200s(见 `H3-CONSOLE.md`),批量提前跑不受实时性压力,适合"不着急"的场景 |
  105 +| **观众付费实时请求**(模式二付费传衣服、模式三付费传人物) | fal.ai 云端(`minimax/h3*` 系列端点,`/fal/*` 反代已有雏形,见 `app.js`) | 快得多(约 4s 级别 vs 内部集群约 200s,见 `H3-CONSOLE.md` 实测对比),付费用户对等待时间敏感,成本由用户支付的积分覆盖 |
  106 +
  107 +即:**免费内容靠自己的卡慢慢攒库存,付费内容花钱买 fal.ai 的实时速度**。
  108 +Stage1(换装图合成)在两条路径下大概率都是 fal.ai(H3 Console 不产图),
  109 +只有 Stage2(视频生成)真正分叉成两条算力。
  110 +
  111 +### 4.4 视频规格:默认竖屏,移动端优先(已确认,仅 `dance.html`)
  112 +
  113 +- `dance.html` 生成的视频**默认竖屏**(`live.html` 现有横屏内容不受影响,
  114 + 两个直播间各自独立的规格,见第 2 节)。
  115 +- **默认分辨率 480×832**——现有 `server.js` 里 `H3_GEN_PARAMS` 的横屏
  116 + 默认是 832×480(总像素量 ≈ 40 万),480×832 只是宽高对调,总像素量
  117 + 完全不变,理论上不会因为改成竖屏而拖慢生成耗时(H3 集群单条本来就
  118 + 约 200s,见 `H3-CONSOLE.md`,竖屏化不能再让这个数字变得更差)。
  119 +- 更高分辨率(576×1024/720×1280 等)画质更好,但会明显增加生成耗时,
  120 + **不作为默认**,可以作为后续付费选项预留(呼应第 7.2 节"新增资产才
  121 + 消耗积分"的思路——高清可以是另一档付费能力,不是本期范围,先不做)。
  122 +- 前端播放器/排版要跟着调整:`dance.html` 复用 `live.html` 的 CSS 风格
  123 + (2.1 节),但播放器容器要按 9:16 竖屏比例设计,不能直接套用
  124 + `live.html` 现有横屏播放器的宽高比,否则会出现大黑边——这是
  125 + `dance.html` 相对 `live.html` 除了"没有文字输入框"之外的第二个视觉
  126 + 差异点。
  127 +
  128 +### 4.5 失败/超时处理
  129 +
  130 +- Stage1 失败:直接终止,不进入 Stage2,不占用 GPU/API 额度;付费请求
  131 + 的话退还积分。
  132 +- Stage2 失败/超时:复用 v1 `generateForPrompt` 现成的重试/超时/持久化
  133 + 套路(`h3JobId`/fal 任务 id 落盘,进程重启能续投)。
  134 +
  135 +### 4.6 相同人物+衣服组合去重缓存(已确认,限模式一免费路径)
  136 +
  137 +- 模式一固定用同一个默认人物(2 节),衣服总量有限,多个观众大概率会
  138 + 选中同一件——**相同"人物+衣服"组合直接复用已有的换装图(Stage1)和
  139 + 生成视频(Stage2),不重新生成**,直接把已有产物排入播放队列。
  140 +- 目的:省 GPU/API 成本,也让免费入口的点播延迟大幅降低(命中缓存约等
  141 + 于 v1 现有"素材库命中"那种秒级体验,见 `docs/done` 2.2 节)。
  142 +- 缓存 key 是 `(characterId, garmentId)` 组合;命中判断在提交时做,
  143 + 跟 v1 `findLibraryMatch` 的"先查库再生成"套路是同一个思路,只是匹配
  144 + 维度从"文本相似度"换成"资产 id 组合",实现上更简单可靠。
  145 +- **模式二/三(付费路径)不做去重**——每次付费传衣服/传人物本身就是
  146 + 在新增独特资产,不存在"组合已存在"的情况。
  147 +
  148 +## 5. 内容审核(已确认:自动黑名单 + 事后人工兜底,非强制先审后播)
  149 +
  150 +- **模式二(传衣服)**:基础审核,复用/扩展 v1 关键词审核思路,但要能
  151 + 过图片——需要图片内容审核能力(供应商待定,见第 8 节)。
  152 +- **模式三(传人)**:走**自动化人脸识别黑名单过滤**(比如过滤涉政人物
  153 + 形象),不在黑名单里的人物照片**自动通过、直接进入生成**,不做强制
  154 + 人工预审。风险兜底靠两层:
  155 + 1. 后台运营发现问题内容可以随时手工下架(第 6.5 节播放管理)。
  156 + 2. 新人物内容本身生命周期短——播完购买的那几个 10 秒片段就自动下线
  157 + (第 3.3 节第 7 点),把"有问题内容能公开曝光多久"这个风险窗口
  158 + 天然限制住了,不需要靠"生成前卡审核"来兜底。
  159 +- 无论哪种模式,最终生成内容仍会过 H3 Console / fal.ai 自身的生成时
  160 + 安全策略兜底(`docs/done` 2.7 已有此保底),是最后一道防线,不是
  161 + 唯一防线。
  162 +- 人脸识别黑名单模型具体选型待定,见第 8 节。
  163 +
  164 +### 5.1 年龄确认与免责声明(已确认)
  165 +
  166 +- `dance.html` 涉及付费、真人照片上传、可能包含泳装等服饰分类,且对外
  167 + 公开——进入前要求勾选"我已满18岁"确认框 + 展示免责声明。基础年龄
  168 + 确认,不做第三方实名认证(成本更高,暂不需要,见第 8 节留作后续
  169 + 评估项)。
  170 +- 免费模式一是否也要挂这道确认,还是只在涉及付费/传人操作时弹出:
  171 + 建议统一放在 `dance.html` 首次进入时做一次性确认(跟现有 `live.html`
  172 + 首次进聊天框弹昵称框的交互套路一致,见 `docs/done` 2.5 节),而不是
  173 + 每个操作都弹一次。
  174 +
  175 +## 6. 后台资产管理需求
  176 +
  177 +在现有 `admin.html`(v1 素材表格)基础上,新增几类资产的管理页面/模块,
  178 +保持同一套"表格 + 行内编辑 + 新增表单"交互风格。
  179 +
  180 +### 6.1 人物图片管理
  181 +
  182 +- CRUD:上传人物参考图、命名、打标签、设为"直播默认人物"。
  183 +- 来源区分:`admin_seed`(后台预置/冷启动批量生成) vs
  184 + `user_paid`(模式三付费上传)。`user_paid` 来源不进入"系统随机挑选
  185 + 免费用户默认人物"的池子(3.3 节第 6 点),但需要能在后台看到、关联到
  186 + 付费用户、追踪其"剩余可播 10 秒片段数"。
  187 +- 「设为直播默认人物」开关:模式一/二里"系统指定"的人物来源于此。
  188 + **第一期只允许同时有一个人物被设为默认**(2 节已确认,不做多人物
  189 + 轮换),开关设计上仍按"列表 + 单选"做,方便以后放开多选/轮换不用
  190 + 改数据模型。
  191 +
  192 +### 6.2 衣服物品管理
  193 +
  194 +- CRUD:上传衣服图、命名、分类/标签、缩略图、上下架开关。
  195 +- 来源区分:`admin_seed` vs `user_paid`(模式二上传,审核通过后默认
  196 + 沉淀进公共库,3.2 节第 3 点)。
  197 +
  198 +### 6.3 参考跳舞视频/音乐管理
  199 +
  200 +- CRUD:上传参考跳舞视频(含配乐),命名、风格标签、时长、上下架开关。
  201 +- 版权来源字段:当前**先用测试素材占位**(正式版权素材待确认,见第 8
  202 + 节),后台需要能记录/后续批量替换。
  203 +
  204 +### 6.4 生成后的视频管理
  205 +
  206 +- 扩展现有 `admin.html` 素材表格:新增 `characterId`/`garmentId`/
  207 + `composedImageId`(可点开看 Stage1 合成图)/`refDanceVideoId` 列,
  208 + 追溯"谁的脸 + 谁的衣服 + 哪条参考跳舞视频"生成的。
  209 +- 人工下架/加精;批量删除入口。
  210 +
  211 +### 6.5 播放管理
  212 +
  213 +- 插播/置顶下一条、手动跳过当前、暂停自动填充、拉黑某个用户/资产(对接
  214 + 第 5 节的人工兜底手段)。
  215 +
  216 +## 7. 用户体系与积分(已确认)
  217 +
  218 +### 7.1 用户注册
  219 +
  220 +- 起步方式:**邮箱登录**(账号+邮箱验证)。
  221 +- 后续迭代:加 **Google / Apple / GitHub** 三方登录。
  222 +- 现有 `viewer_uid`/`viewer_nickname` cookie 身份平滑绑定到账号,历史
  223 + 投票/点播记录不丢(沿用 `docs/structure/architecture.md` 2.4 节的
  224 + 过渡方案)。
  225 +
  226 +### 7.2 积分模型
  227 +
  228 +- **充值 → 通用积分池**,而不是"充值金额直接换算成固定数量的 10 秒
  229 + 片段"——积分是通用货币,未来别的付费场景(比如优先排队)也能复用
  230 + 同一套机制,跟原始需求里"未来消耗积分限制生成频率/质量"的说法一致。
  231 +- **计费边界(已确认)**:
  232 + - 使用已有资产库(衣柜里选衣服、系统默认人物)—— **免费,不扣积分**。
  233 + - 新增资产(模式二付费传衣服、模式三付费传人物)—— **消耗积分**。
  234 + - 新增人物额外规则:消耗的积分数量决定该角色可播放的 10 秒片段数
  235 + (3.3 节第 3、7 点);片段播完后角色下线但记录保留,用户可再充值
  236 + 续播而不需要重新生成。
  237 +- 具体每次操作扣多少积分、充值档位等数值,还没定(不阻塞开发,先把
  238 + 余额+流水+配置化扣费额这套机制打好,数值上线前再定,见第 8 节)。
  239 +- 失败退还:Stage1/Stage2 任一环节失败,已扣积分必须退还,不能出现
  240 + "扣了积分但没生成成功也不退"的黑洞。
  241 +
  242 +### 7.3 充值支付渠道(机制已定,具体渠道待定)
  243 +
  244 +- 初步倾向 **Stripe**,但具体接哪家先不锁死——积分系统本身设计成
  245 + "内部余额+流水",支付渠道只是"给余额充值"的一个外部入口,换渠道
  246 + 不影响积分模型本身。
  247 +- ⚠️ **需要提醒的风险点**:Stripe 对中国大陆发卡的信用卡/本地支付方式
  248 + 支持有限(合规和可用性都有限制)。如果这个直播间的目标观众以国内
  249 + 用户为主(现有 `live.html`/`admin.html` UI 全是中文,产品语言明显
  250 + 面向国内),大概率还需要**同时接入支付宝/微信支付**才能覆盖主力
  251 + 用户的实际支付习惯,不能只上 Stripe。这一点建议在真正开发充值流程
  252 + 前明确一下目标用户地域,见第 8 节。
  253 +
  254 +### 7.4 个人中心「我的作品」(已确认)
  255 +
  256 +- 付费生成的内容(模式二上传的衣服、模式三的新人物及其视频),即使
  257 + 已经从直播排播/复用池下线(3.3 节第 7 点"到期处理"),用户仍要能在
  258 + 自己的账号下单独查看/回放——不是"下线=消失",只是"下线=不再出现在
  259 + 公开直播间和其他人的衣柜/随机池里"。
  260 +- 数据模型含义:生成的视频/换装图/人物记录要挂在 `userId` 下长期可查,
  261 + 和"是否还在直播池中"是两个独立的状态维度(呼应
  262 + `docs/structure/architecture.md` 2.7 节"人物生命周期"里提到的
  263 + `status: active|expired` 设计,这里进一步要求 `expired` 状态下这些
  264 + 记录依然可被拥有者检索,不是软删除后就不可访问)。
  265 +- 具体页面/交互(列表、是否能重新分享到直播间等)留到对应分期再细化,
  266 + 这里只确认"数据要保留、要能查"这个底线。
  267 +
  268 +## 8. 待验证事项(不是产品决策分歧,是需要技术验证/调研才能定的点)
  269 +
  270 +1. **Stage1 换装模型具体选型**:fal.ai 平台上有哪些图片编辑/换装类
  271 + 模型适合"人物+服装 → 换装图"这个场景,需要小范围测试几个人物+衣服
  272 + 组合,比较一致性/速度/成本后再定。
  273 +2. **H3 Console `reference_video_urls` 实测**:`H3-CONSOLE.md` 里这个
  274 + 字段目前"未实测,仅从控制台 UI tab 推断存在",Stage2 落地前第一步
  275 + 要用真实调用验证这条路径是否按预期工作。
  276 +3. **参考跳舞视频/配乐正式素材来源**:目前先用测试素材占位,正式版权
  277 + 素材(自制还是购买授权)待确认,后台字段已预留(6.3 节)。
  278 +4. **人脸识别黑名单模型选型**:用什么模型/服务做"涉政等敏感人物"识别
  279 + 过滤,需要调研候选方案。
  280 +5. **积分具体数值**:充值档位、每次新增衣服/新增人物扣多少积分、新
  281 + 人物每个 10 秒片段的积分单价——机制已定(7.2 节),数值上线前再定。
  282 +6. **支付网关选型**:初步倾向 Stripe,但需要先确认目标观众地域——若
  283 + 以国内用户为主,大概率要同时/改为接入支付宝/微信支付(7.3 节),
  284 + 这一点建议尽早拍板,避免影响充值流程的开发方向。
  285 +7. **"我的作品"页面的第三方实名认证需求**:第一期只做"我已满18岁"
  286 + 勾选式确认(5.1 节),是否需要更严格的第三方实名认证,看实际运营
  287 + 风险再评估,不预设一定要做。
  288 +
  289 +## 9. 分期建议(不是承诺的排期,是依赖顺序参考)
  290 +
  291 +1. **第一期**:后台先把「人物库 / 衣服库 / 参考跳舞视频库」CRUD 模块
  292 + 搭出来,用内部 H3 Console 批量跑冷启动种子内容(4.3 节),验证
  293 + Stage1→Stage2 全流水线可行性(不接入付费/审核/账号)。
  294 +2. **第二期**:`dance.html` 上线(含首次进入的年龄确认+免责声明,5.1
  295 + 节),接入「衣柜选衣服」免费模式(3.1 节)+ 相同人物+衣服组合去重
  296 + 缓存(4.6 节),复用现有排播队列基础设施(此阶段全部免费内容仍按
  297 + v1 投票排序,还不涉及"付费插队"这条新规则)。
  298 +3. **第三期**:用户注册体系上线(邮箱登录),把匿名 cookie 身份绑定
  299 + 到账号——账号是后面付费功能的前提,必须先落地。
  300 +4. **第四期**:积分体系(余额/流水/配置化扣费)+ 充值支付渠道接入
  301 + (7.3 节,先确认目标地域再选具体渠道),先上线免费换衣服 + 付费
  302 + 传衣服(模式二),验证扣费/退还/公共库沉淀链路。
  303 +5. **第五期**:付费传人物(模式三)+ 自动人脸黑名单审核 + "购买 10 秒
  304 + 片段数 / 到期下线 / 续播"这套人物生命周期管理 + 排播优先级改造(付费
  305 + 新人物插队播放,见 3 节"共同后置流程")+ 个人中心"我的作品"页面
  306 + (7.4 节),接入 fal.ai 实时算力路径(4.3 节)。