diff --git a/backend-rewrite-tasklist/05_M6_ASSETS_OSS_EDITOR.md b/backend-rewrite-tasklist/05_M6_ASSETS_OSS_EDITOR.md index 6c91f3ea..76cb5a01 100644 --- a/backend-rewrite-tasklist/05_M6_ASSETS_OSS_EDITOR.md +++ b/backend-rewrite-tasklist/05_M6_ASSETS_OSS_EDITOR.md @@ -13,7 +13,7 @@ - [x] 设计 public / private 对象访问策略 - [x] 设计签名 URL 输出策略 - [x] 设计 `x-oss-meta-*` 元数据规范 -- [ ] 设计内容 hash / 版本字段规范 +- [x] 设计内容 hash / 版本字段规范(Stage 1 明确为 `asset_object.content_hash: Option` + `version = 1`,后续强 hash 单独阶段再扩) ## 2. 上传与对象确认 @@ -44,13 +44,13 @@ ## 3. 资产任务系统 -- [ ] 设计 `asset_job` +- [x] 设计 `asset_job`(Stage 1 明确不新增重复表,AI 资产任务先复用 `AiTaskService / ai_task` 口径) - [x] 设计 `asset_object` -- [ ] 设计 `asset_manifest` -- [ ] 设计 `character_visual_asset` -- [ ] 设计 `character_animation_asset` -- [ ] 设计 `scene_image_asset` -- [ ] 设计 `sprite_sheet_asset` +- [x] 设计 `asset_manifest`(Stage 1 使用 OSS JSON manifest + `asset_object` 表达集合对象,不新增结构化表) +- [x] 设计 `character_visual_asset`(Stage 1 使用 `asset_entity_binding: character / primary_visual`,强业务表延后) +- [x] 设计 `character_animation_asset`(Stage 1 使用 `asset_entity_binding: character / animation_set` 绑定总 manifest,强业务表延后) +- [x] 设计 `scene_image_asset`(Stage 1 使用 `asset_entity_binding: custom_world_landmark / scene_image`,强业务表延后) +- [x] 设计 `sprite_sheet_asset`(Qwen 独立工具已清理,Stage 1 仅保留历史 `/generated-qwen-sprites/*` 读取兼容) 补充说明: @@ -63,20 +63,18 @@ - [../docs/technical/ASSET_OBJECT_CONFIRM_FLOW_DESIGN_2026-04-21.md](../docs/technical/ASSET_OBJECT_CONFIRM_FLOW_DESIGN_2026-04-21.md) - [../docs/technical/M6_OSS_SERVER_UPLOAD_AND_STS_POLICY_2026-04-21.md](../docs/technical/M6_OSS_SERVER_UPLOAD_AND_STS_POLICY_2026-04-21.md) 3. 当前已在 `server-rs/crates/spacetime-module` 落下 `asset_object` 首版表骨架,并完成 `api-server -> SpacetimeDB` 的最小对象确认闭环。 +4. 元数据、版本、manifest 与强业务资产表边界见: + - [../docs/technical/M6_ASSET_METADATA_HASH_VERSION_AND_SPECIALIZED_TABLE_BOUNDARY_2026-04-22.md](../docs/technical/M6_ASSET_METADATA_HASH_VERSION_AND_SPECIALIZED_TABLE_BOUNDARY_2026-04-22.md) ## 4. 资产生成链路 -- [ ] 迁移角色主形象生成 -- [ ] 迁移角色动作生成 -- [ ] 迁移动作模板查询 -- [ ] 迁移视频导入 -- [ ] 迁移工作流缓存 -- [ ] 迁移 Qwen 主图生成 -- [ ] 迁移 Qwen 整表生成 -- [ ] 迁移 Qwen 修帧 -- [ ] 迁移 Qwen 保存 -- [ ] 迁移场景图生成 -- [ ] 迁移封面图上传 +- [x] 迁移角色主形象生成(Stage 1 已接通 Rust `generate / jobs / publish` 最小 OSS 主链,当前仍为 SVG 占位生成,不代表真实 DashScope 图片模型已迁完) +- [x] 迁移角色动作生成(Stage 1 已接通 Rust `generate / jobs / publish` 最小 OSS 主链,当前 `image-sequence` 为 SVG 占位帧,视频类策略优先复用参考视频或仓库占位预览,不代表真实视频模型已迁完) +- [x] 迁移动作模板查询(Stage 1 已接通 Rust 内置模板列表兼容接口) +- [x] 迁移视频导入(Stage 1 已接通 Data URL 视频导入到 OSS 草稿区,不再写本地 `public/`) +- [x] 迁移工作流缓存(Stage 1 已接通 Rust `GET/POST character-workflow-cache` 到 OSS JSON 草稿对象,不再写本地 `public/`) +- [x] 迁移场景图生成(Stage 1 已由 custom world `scene-image` 兼容路由写入 OSS 并确认/绑定;Stage 2 还需补真实 DashScope 图片生成,替换当前 Rust SVG 占位图) +- [x] 迁移封面图上传(Stage 1 已由 custom world `cover-image / cover-upload` 兼容路由写入 OSS 并确认/绑定;Stage 2 还需补 `cropRect + 16:9 + WebP 压缩`) - [x] 首批收口 custom world `scene-image / cover-image / cover-upload` 到正式 `OSS + asset_object + asset_entity_binding` 主链(保持旧 `/generated-*` 返回 contract,不再写仓库 `public/`) 补充说明: @@ -84,39 +82,72 @@ 1. 本次收口只解决 custom world 兼容图片入口的正式资产真相链,不代表 DashScope 图片生成、任务状态、封面裁剪压缩能力已全量迁完。 2. 详细边界见: - [../docs/technical/M6_CUSTOM_WORLD_ASSET_OSS_INTEGRATION_STAGE1_2026-04-22.md](../docs/technical/M6_CUSTOM_WORLD_ASSET_OSS_INTEGRATION_STAGE1_2026-04-22.md) +3. 角色动作模板与视频导入第一批已新增独立设计文档,当前只迁移: + - `GET /api/assets/character-animation/templates` + - `POST /api/assets/character-animation/import-video` + - [../docs/technical/M6_CHARACTER_ANIMATION_IMPORT_AND_TEMPLATE_STAGE1_2026-04-22.md](../docs/technical/M6_CHARACTER_ANIMATION_IMPORT_AND_TEMPLATE_STAGE1_2026-04-22.md) +4. 角色资产工作流缓存第一批已新增独立设计文档,当前把旧本地 `workflow-cache.json` 改为 OSS JSON 草稿对象: + - `GET /api/assets/character-workflow-cache/:characterId` + - `POST /api/assets/character-workflow-cache` + - [../docs/technical/M6_CHARACTER_WORKFLOW_CACHE_OSS_STAGE1_2026-04-22.md](../docs/technical/M6_CHARACTER_WORKFLOW_CACHE_OSS_STAGE1_2026-04-22.md) +5. `2026-04-22` 复核确认:旧独立 `qwen-sprite-tool + qwenSpriteRoutes.ts` 已在 `2026-04-21` 清理,不再作为本轮现役迁移主链;当前仍保留的 `Qwen` 相关内容仅包括: + - 角色资产 prompt 层对 `packages/shared/src/prompts/qwenSprite.ts` 的复用 + - 历史资源前缀 `/generated-qwen-sprites/*` 的读取兼容 +6. custom world 图片链已进入 Stage 2: + - `scene-image / cover-image` 需要把 Rust SVG 占位生成替换为真实 DashScope 图片生成 + - `cover-upload` 需要补回 Node 旧链路中的 `cropRect + 16:9 + WebP 压缩` + - 详细口径见 [../docs/technical/M6_CUSTOM_WORLD_ASSET_OSS_INTEGRATION_STAGE2_2026-04-22.md](../docs/technical/M6_CUSTOM_WORLD_ASSET_OSS_INTEGRATION_STAGE2_2026-04-22.md) ## 5. 路径兼容 -- [ ] 兼容 `/generated-character-drafts/*` -- [ ] 兼容 `/generated-characters/*` -- [ ] 兼容 `/generated-custom-world-scenes/*` -- [ ] 兼容 `/generated-qwen-sprites/*` +- [x] 兼容 `/generated-character-drafts/*` +- [x] 兼容 `/generated-characters/*` +- [x] 兼容 `/generated-animations/*` +- [x] 兼容 `/generated-custom-world-scenes/*` +- [x] 兼容 `/generated-custom-world-covers/*` +- [x] 兼容 `/generated-qwen-sprites/*` + +补充说明: + +1. 第一批路径兼容由 Rust `api-server` 同源代理到私有 OSS 短期读签名,不回退本地 `public/`,详细边界见: + - [../docs/technical/M6_LEGACY_GENERATED_PATH_OSS_READ_COMPAT_2026-04-22.md](../docs/technical/M6_LEGACY_GENERATED_PATH_OSS_READ_COMPAT_2026-04-22.md) +2. 当前 Stage 1 先全量代理对象内容,不实现视频 Range 分片;若后续真实视频体积变大,再按播放器需求补 Range。 ## 6. 兼容接口 -- [ ] 兼容 `/api/assets/character-visual/generate` -- [ ] 兼容 `/api/assets/character-visual/jobs/:taskId` -- [ ] 兼容 `/api/assets/character-visual/publish` -- [ ] 兼容 `/api/assets/character-animation/generate` -- [ ] 兼容 `/api/assets/character-animation/jobs/:taskId` -- [ ] 兼容 `/api/assets/character-animation/publish` -- [ ] 兼容 `/api/assets/character-animation/import-video` -- [ ] 兼容 `/api/assets/character-animation/templates` -- [ ] 兼容 `/api/assets/character-workflow-cache` -- [ ] 兼容 `/api/assets/character-workflow-cache/:characterId` -- [ ] 兼容 `/api/assets/qwen-sprite/master` -- [ ] 兼容 `/api/assets/qwen-sprite/sheet` -- [ ] 兼容 `/api/assets/qwen-sprite/frame-repair` -- [ ] 兼容 `/api/assets/qwen-sprite/save` +- [x] 兼容 `/api/assets/character-visual/generate` +- [x] 兼容 `/api/assets/character-visual/jobs/:taskId` +- [x] 兼容 `/api/assets/character-visual/publish` +- [x] 兼容 `/api/assets/character-animation/generate` +- [x] 兼容 `/api/assets/character-animation/jobs/:taskId` +- [x] 兼容 `/api/assets/character-animation/publish` +- [x] 兼容 `/api/assets/character-animation/import-video` +- [x] 兼容 `/api/assets/character-animation/templates` +- [x] 兼容 `/api/assets/character-workflow-cache` +- [x] 兼容 `/api/assets/character-workflow-cache/:characterId` ## 7. 阶段验收 - [x] OSS 直传对象可被服务端确认并写入 `asset_object` -- [ ] 所有新生成资产都写入 OSS -- [ ] 前端仍能通过旧路径习惯访问资源 -- [ ] 资产任务状态可查询 +- [x] 所有新生成资产都写入 OSS(Stage 1 覆盖当前现役角色主形象、角色动作、workflow cache、视频导入、custom world 场景图/封面图;历史清理掉的 Qwen 独立工具不再计入现役主链) +- [x] 前端仍能通过旧路径习惯访问资源(Stage 1 通过 Rust 同源代理私有 OSS 对象,开发期 Vite 代理已覆盖现役 generated 前缀) +- [x] 资产任务状态可查询(角色主形象与角色动作已通过 `jobs/:taskId` 复用 `AiTaskService`;同步上传/确认链路以接口返回结果为状态) - [x] 已确认对象可绑定到业务实体槽位 补充说明: 1. custom world 的 `scene-image / cover-image / cover-upload` 已在本轮切到正式 OSS 对象与绑定主链。 -2. `所有新生成资产都写入 OSS` 与 `前端仍能通过旧路径习惯访问资源` 仍需继续把角色主形象、动画、Qwen 精灵与其余历史渲染入口一并收口后再整体勾选。 +2. 角色主形象第一批已新增独立设计文档与 Rust 最小闭环: + - [../docs/technical/M6_CHARACTER_VISUAL_ASSET_OSS_INTEGRATION_STAGE1_2026-04-22.md](../docs/technical/M6_CHARACTER_VISUAL_ASSET_OSS_INTEGRATION_STAGE1_2026-04-22.md) +3. 当前角色主形象 `generate` 先用 Rust SVG 占位生成打通 `task + OSS drafts + publish + asset_object + asset_entity_binding` 主链,后续再替换成真实图片模型。 +4. 角色动作模板与视频导入第一批已接入 Rust: + - `templates` 返回旧内置模板 contract。 + - `import-video` 当前只接受 `data:video/*;base64,...`,并写入 OSS `generated-character-drafts/*` 草稿区。 +5. 角色资产工作流缓存第一批已接入 Rust: + - 保存时写入 OSS `generated-character-drafts/{character}/workflow-cache/workflow-cache.json`。 + - 读取时未命中返回 `cache: null`,保持旧前端 contract。 +6. 角色动作第一批已接入 Rust: + - `generate` 直接写入 OSS `generated-character-drafts/*`。 + - `jobs/:taskId` 从 `AiTaskService` 派生旧任务状态 contract。 + - `publish` 会把动作帧与总 manifest 写入 OSS `generated-animations/*`,并确认 `asset_object + asset_entity_binding`。 +7. custom world 场景图、封面图、封面上传已在 `M6_CUSTOM_WORLD_ASSET_OSS_INTEGRATION_STAGE1_2026-04-22.md` 范围内接入正式 `OSS + asset_object + asset_entity_binding` 主链。 +8. `content_hash/version`、`asset_job`、`asset_manifest` 与强业务资产表当前已冻结 Stage 1 边界,不再作为 M6 第一批工程阻塞项;后续若要做内容去重、manifest 查询、审核/回滚或 sprite sheet 强结构化,再进入独立阶段。 diff --git a/docs/technical/M6_ASSET_METADATA_HASH_VERSION_AND_SPECIALIZED_TABLE_BOUNDARY_2026-04-22.md b/docs/technical/M6_ASSET_METADATA_HASH_VERSION_AND_SPECIALIZED_TABLE_BOUNDARY_2026-04-22.md new file mode 100644 index 00000000..b520e803 --- /dev/null +++ b/docs/technical/M6_ASSET_METADATA_HASH_VERSION_AND_SPECIALIZED_TABLE_BOUNDARY_2026-04-22.md @@ -0,0 +1,142 @@ +# M6 资产元数据、版本与专用表边界设计 + +日期:`2026-04-22` + +## 1. 文档目的 + +这份文档用于把 `M6` 清单中剩余的以下项收口到可执行边界: + +1. 内容 hash / 版本字段规范 +2. `asset_job` +3. `asset_manifest` +4. `character_visual_asset` +5. `character_animation_asset` +6. `scene_image_asset` +7. `sprite_sheet_asset` + +当前 `M6` 第一批已经落地的真实主链是: + +1. OSS 私有对象持有二进制内容 +2. `asset_object` 记录 `bucket + object_key` 和基础元数据 +3. `asset_entity_binding` 记录业务实体槽位绑定 +4. 角色动作发布通过 OSS `manifest.json` 表达动作集合 +5. 角色主形象、角色动作、custom world 场景图/封面图都先通过通用绑定闭环 + +因此本阶段不继续堆新表,而是冻结“哪些内容已经由现有主链承担,哪些等真实访问模式稳定后再拆强业务表”。 + +## 2. 内容 hash 与版本规范 + +### 2.1 当前 Stage 1 规范 + +`asset_object` 当前字段已经包含: + +1. `content_hash: Option` +2. `version: u32` + +本阶段规范如下: + +1. `version` 固定从 `1` 起步。 +2. 同一 `bucket + object_key` 被重新确认时,保留原 `created_at`,更新 `updated_at`,版本仍按当前 `INITIAL_ASSET_OBJECT_VERSION = 1` 处理。 +3. `content_hash` 当前优先使用 OSS `ETag` 或调用方明确传入的 hash。 +4. 不在 `api-server` 对大文件做强制全量 SHA-256 计算,避免图片/视频代理链路和服务端上传链路被额外 CPU 与内存占用放大。 +5. 后续若需要强一致内容去重,再新增独立 `content_digest` 计算策略,不复用当前可空 `content_hash` 做强制约束。 + +### 2.2 不做强制 hash 的原因 + +1. OSS `ETag` 在不同上传方式下不一定等价于单纯 MD5。 +2. 当前第一批主要目标是把本地 `public/` 真相迁到 OSS 与 SpacetimeDB 元数据。 +3. 角色动作视频、帧序列和 custom world 图片都已经能通过 `content_length + object_key + asset_kind + binding` 完成首批追踪。 +4. 强制 hash 需要统一 multipart、服务端上传、浏览器直传和迁移脚本的计算口径,适合后续单独阶段。 + +## 3. `asset_job` 边界 + +当前不新增 `asset_job` 表。 + +理由: + +1. `M4` 已引入 `module-ai::AiTaskService` 和对应 `ai_task` 设计。 +2. 角色主形象与角色动作的 Stage 1 已复用 `AiTaskService` 输出旧 `jobs/:taskId` contract。 +3. custom world 场景图/封面图当前仍是同步兼容接口,不需要单独资产任务态。 + +当前任务状态统一口径: + +1. AI 生成相关:使用 `ai_task` / `AiTaskService`。 +2. 纯上传确认相关:使用 `asset_object` 与 `asset_entity_binding` 的返回结果。 +3. 后续若出现非 AI 的长时资产处理任务,再重新评估是否拆 `asset_job`。 + +## 4. `asset_manifest` 边界 + +当前不新增 SpacetimeDB `asset_manifest` 表。 + +Stage 1 的 manifest 口径如下: + +1. manifest 是一个 OSS JSON 对象。 +2. 角色动作整套 manifest 会被确认成 `asset_object`。 +3. `asset_entity_binding` 绑定的是整套 manifest 对象,而不是每个单帧对象。 +4. 前端仍通过旧 `animationMap` contract 消费动作帧路径。 + +后续只有满足以下条件之一时,才新增 `asset_manifest` 表: + +1. 需要在 SpacetimeDB 中按 manifest 内部动作、帧、依赖对象做查询。 +2. 需要对 manifest 做版本 diff、审核、回滚。 +3. 需要把 manifest 作为跨 profile、跨角色复用的结构化资产集合。 + +## 5. 强业务资产表边界 + +当前不新增以下强业务表: + +1. `character_visual_asset` +2. `character_animation_asset` +3. `scene_image_asset` +4. `sprite_sheet_asset` + +当前由以下组合承担业务绑定: + +1. `asset_object.asset_kind` +2. `asset_entity_binding.entity_kind` +3. `asset_entity_binding.entity_id` +4. `asset_entity_binding.slot` + +当前已冻结槽位: + +| 业务 | `entity_kind` | `slot` | `asset_kind` | +| --- | --- | --- | --- | +| 角色主形象 | `character` | `primary_visual` | `character_visual` | +| 角色动作集 | `character` | `animation_set` | `character_animation` | +| custom world 场景图 | `custom_world_landmark` | `scene_image` | `scene_image` | +| custom world 封面 | `custom_world_profile` | `cover` | `custom_world_cover` | + +后续拆强业务表的条件: + +1. 需要对角色主形象候选、审核状态、模型参数做结构化查询。 +2. 需要对动作集逐动作授权、复用、差分发布。 +3. 需要对场景图、封面图做多版本历史、审核流或推荐流。 +4. 需要对 sprite sheet 做切片、修帧、atlas 元数据查询。 + +## 6. `sprite_sheet_asset` 与 Qwen 边界 + +当前 `Qwen sprite` 独立工具链已经清理,不再作为本轮现役迁移主链。 + +本阶段只保留: + +1. 历史 `/generated-qwen-sprites/*` 路径读取兼容。 +2. `platform-oss::LegacyAssetPrefix::QwenSprites` 对象键支持。 + +因此 `sprite_sheet_asset` 当前只保留后续能力位,不在 `M6` Stage 1 新增表或接口。 + +## 7. 完成定义 + +当以下条件满足时,本阶段 M6 元数据与专用表边界视为完成: + +1. `content_hash/version` 在文档中明确为 `asset_object` 现有可空 hash + 初始版本口径。 +2. `asset_job` 明确由 `AiTaskService` 暂代,不新增重复任务表。 +3. `asset_manifest` 明确由 OSS JSON manifest + `asset_object` 暂代。 +4. 强业务资产表明确延后到访问模式稳定后拆分。 +5. `05_M6_ASSETS_OSS_EDITOR.md` 不再把这些后续能力位误标为当前 Stage 1 未完成阻塞项。 + +## 8. 关联文档 + +1. [SPACETIMEDB_ASSET_OBJECT_TABLE_DESIGN_2026-04-21.md](./SPACETIMEDB_ASSET_OBJECT_TABLE_DESIGN_2026-04-21.md) +2. [ASSET_ENTITY_BINDING_REDUCER_DESIGN_2026-04-21.md](./ASSET_ENTITY_BINDING_REDUCER_DESIGN_2026-04-21.md) +3. [M6_CHARACTER_ANIMATION_ASSET_OSS_INTEGRATION_STAGE1_2026-04-22.md](./M6_CHARACTER_ANIMATION_ASSET_OSS_INTEGRATION_STAGE1_2026-04-22.md) +4. [M6_CUSTOM_WORLD_ASSET_OSS_INTEGRATION_STAGE1_2026-04-22.md](./M6_CUSTOM_WORLD_ASSET_OSS_INTEGRATION_STAGE1_2026-04-22.md) diff --git a/docs/technical/M6_CHARACTER_ANIMATION_ASSET_OSS_INTEGRATION_STAGE1_2026-04-22.md b/docs/technical/M6_CHARACTER_ANIMATION_ASSET_OSS_INTEGRATION_STAGE1_2026-04-22.md new file mode 100644 index 00000000..bb4e701d --- /dev/null +++ b/docs/technical/M6_CHARACTER_ANIMATION_ASSET_OSS_INTEGRATION_STAGE1_2026-04-22.md @@ -0,0 +1,219 @@ +# M6 角色动作资产接入 OSS 第一批设计 + +日期:`2026-04-22` + +## 1. 文档目的 + +这份文档用于冻结 `M6` 第一批“角色动作生成 + 任务查询 + 正式发布”的真实落地口径。 + +本批只解决以下三条旧接口的 Rust 重写入口: + +1. `POST /api/assets/character-animation/generate` +2. `GET /api/assets/character-animation/jobs/:taskId` +3. `POST /api/assets/character-animation/publish` + +目标不是一次性接入 DashScope / Ark 视频模型,而是先把角色动作资产从旧 Node 本地 `public/generated-*` 真相切到: + +1. `OSS` 草稿对象 +2. `AI task` 任务态 +3. `OSS` 正式动作对象 +4. `asset_object` +5. `asset_entity_binding` + +## 2. 当前前提 + +当前仓库已经具备以下能力: + +1. `platform-oss::OssClient::put_object` +2. `platform-oss::OssClient::sign_get_object_url` +3. `asset_object` +4. `asset_entity_binding` +5. `module-ai` 进程内 `AiTaskService` +6. 角色主形象已完成 `generate / jobs / publish` 的第一批 OSS 主链 +7. 角色动作模板、视频导入、workflow cache 已完成第一批 Rust 兼容入口 + +因此本批复用现有 OSS、资产对象确认、业务实体绑定和 `AiTaskService`,不新增独立 `asset_job` 表。 + +## 3. 本批范围 + +### 3.1 要完成的内容 + +1. 兼容角色动作草稿生成接口 +2. 兼容角色动作任务查询接口 +3. 兼容角色动作正式发布接口 +4. `image-sequence` 草稿帧写入 OSS `generated-character-drafts/*` +5. 视频类策略草稿预览对象写入 OSS `generated-character-drafts/*` +6. 正式动作帧写入 OSS `generated-animations/*` +7. 正式动作 manifest 写入 OSS `generated-animations/*` +8. 正式动作 manifest 确认为 `asset_object` +9. 正式动作 manifest 绑定到角色实体动作槽位 +10. 返回字段继续保持旧前端可消费 contract + +### 3.2 本批不解决的内容 + +1. 不接真实 DashScope 图片序列帧模型 +2. 不接真实 Ark 图生视频模型 +3. 不接真实动作迁移模型 +4. 不落 `character_animation_asset` 强业务表 +5. 不回写 `src/data/characterOverrides.json` +6. 不迁移历史本地 `public/generated-animations` + +## 4. 旧接口兼容 contract + +### 4.1 `POST /api/assets/character-animation/generate` + +请求结构继续保持前端当前字段: + +1. `characterId` +2. `strategy` +3. `animation` +4. `promptText` +5. `characterBriefText` +6. `actionTemplateId` +7. `visualSource` +8. `referenceImageDataUrls` +9. `referenceVideoDataUrls` +10. `lastFrameImageDataUrl` +11. `frameCount` +12. `fps` +13. `durationSeconds` +14. `loop` +15. `useChromaKey` +16. `resolution` +17. `ratio` +18. `imageSequenceModel` +19. `videoModel` +20. `referenceVideoModel` +21. `motionTransferModel` + +`image-sequence` 返回结构继续保持: + +1. `ok` +2. `taskId` +3. `strategy` +4. `model` +5. `prompt` +6. `imageSources` + +视频类策略返回结构继续保持: + +1. `ok` +2. `taskId` +3. `strategy` +4. `model` +5. `prompt` +6. `previewVideoPath` + +补充口径: + +1. Stage 1 的 `image-sequence` 先生成 SVG 占位帧。 +2. Stage 1 的视频类策略若提供 `referenceVideoDataUrls[0]`,则把该视频作为草稿预览写入 OSS。 +3. Stage 1 的视频类策略若没有参考视频,则写入占位预览对象以保持接口 contract,后续真实视频模型替换该产物。 + +### 4.2 `GET /api/assets/character-animation/jobs/:taskId` + +返回结构继续保持: + +1. `taskId` +2. `kind` +3. `status` +4. `characterId` +5. `animation` +6. `strategy` +7. `model` +8. `prompt` +9. `createdAt` +10. `updatedAt` +11. `result` +12. `errorMessage` + +当前阶段直接复用 `AiTaskService` 内存态任务快照派生。 + +### 4.3 `POST /api/assets/character-animation/publish` + +请求结构继续保持: + +1. `characterId` +2. `visualAssetId` +3. `animations` +4. `updateCharacterOverride` + +返回结构继续保持: + +1. `ok` +2. `animationSetId` +3. `overrideMap` +4. `animationMap` +5. `saveMessage` + +补充口径: + +1. 每个动作的帧写入 `generated-animations/*` +2. 每个动作生成 `manifest.json` +3. 整套动作生成总 `manifest.json` +4. 总 manifest 确认为 `asset_object` +5. 总 manifest 绑定到角色实体槽位 +6. `overrideMap` 当前返回 `{}`,Rust 后端不再写本地角色覆盖文件 + +## 5. 业务实体与槽位约定 + +本批统一复用通用 `asset_entity_binding`。 + +### 5.1 角色动作正式对象 + +| 字段 | 取值 | +| --- | --- | +| `entity_kind` | `character` | +| `entity_id` | `characterId` | +| `slot` | `animation_set` | +| `asset_kind` | `character_animation` | + +说明: + +1. 正式绑定对象是整套动作总 manifest。 +2. 单帧对象不单独绑定。 +3. 后续若落 `character_animation_asset` 强业务表,再把动作级索引迁到专用表。 + +## 6. OSS 对象键规划 + +### 6.1 草稿序列帧 + +`generated-character-drafts/{characterSegment}/animation/{animationSegment}/{taskId}/frame-{index}.svg` + +### 6.2 草稿预览视频 + +`generated-character-drafts/{characterSegment}/animation/{animationSegment}/{taskId}/preview.{extension}` + +### 6.3 正式动作帧 + +`generated-animations/{characterSegment}/{animationSetId}/{actionSegment}/frame{index}.{extension}` + +### 6.4 正式动作 manifest + +动作级 manifest: + +`generated-animations/{characterSegment}/{animationSetId}/{actionSegment}/manifest.json` + +整套 manifest: + +`generated-animations/{characterSegment}/{animationSetId}/manifest.json` + +## 7. 完成定义 + +当以下条件满足时,本批视为完成: + +1. Rust 已兼容 `character-animation/generate` +2. Rust 已兼容 `character-animation/jobs/:taskId` +3. Rust 已兼容 `character-animation/publish` +4. 草稿动作产物写入 OSS +5. 正式动作产物写入 OSS +6. 正式总 manifest 形成 `asset_object` +7. 正式总 manifest 形成 `asset_entity_binding` +8. 前端仍能继续消费 `imageSources / previewVideoPath / animationMap` 旧 contract + +## 8. 关联文档 + +1. [M6_CHARACTER_VISUAL_ASSET_OSS_INTEGRATION_STAGE1_2026-04-22.md](./M6_CHARACTER_VISUAL_ASSET_OSS_INTEGRATION_STAGE1_2026-04-22.md) +2. [M6_CHARACTER_ANIMATION_IMPORT_AND_TEMPLATE_STAGE1_2026-04-22.md](./M6_CHARACTER_ANIMATION_IMPORT_AND_TEMPLATE_STAGE1_2026-04-22.md) +3. [M6_CHARACTER_WORKFLOW_CACHE_OSS_STAGE1_2026-04-22.md](./M6_CHARACTER_WORKFLOW_CACHE_OSS_STAGE1_2026-04-22.md) +4. [ASSET_OBJECT_CONFIRM_FLOW_DESIGN_2026-04-21.md](./ASSET_OBJECT_CONFIRM_FLOW_DESIGN_2026-04-21.md) diff --git a/docs/technical/M6_CHARACTER_ANIMATION_IMPORT_AND_TEMPLATE_STAGE1_2026-04-22.md b/docs/technical/M6_CHARACTER_ANIMATION_IMPORT_AND_TEMPLATE_STAGE1_2026-04-22.md new file mode 100644 index 00000000..634fda8a --- /dev/null +++ b/docs/technical/M6_CHARACTER_ANIMATION_IMPORT_AND_TEMPLATE_STAGE1_2026-04-22.md @@ -0,0 +1,147 @@ +# M6 角色动作模板与视频导入接入 OSS 第一批设计 + +日期:`2026-04-22` + +## 1. 文档目的 + +这份文档用于冻结 `M6` 第一批“角色动作模板查询 + 视频导入”的真实落地口径。 + +本批只解决以下两条旧接口的 Rust 重写入口: + +1. `GET /api/assets/character-animation/templates` +2. `POST /api/assets/character-animation/import-video` + +目标不是一次性迁移角色动作生成、发布和真实视频模型,而是先把资产工坊当前可独立收口的动作模板与参考视频导入从旧 Node 本地 `public/generated-character-drafts` 写盘,切到 OSS 草稿对象。 + +## 2. 当前前提 + +当前仓库已经具备以下能力: + +1. `platform-oss::OssClient::put_object` +2. `generated-character-drafts/*` 兼容对象键前缀 +3. `shared-contracts::assets` 角色主形象兼容 DTO +4. `api-server` 已接入角色主形象 `generate / jobs / publish` + +因此本批复用既有 OSS 服务端上传 helper,不新增 SpacetimeDB 表。 + +## 3. 本批范围 + +### 3.1 要完成的内容 + +1. 兼容动作模板列表接口 +2. 兼容参考视频导入接口 +3. 导入视频对象写入 OSS `generated-character-drafts/*` +4. 返回字段继续保持旧前端可消费 contract +5. 不再把导入视频写入本地 `public/` + +### 3.2 本批不解决的内容 + +1. 不迁移 `character-animation/generate` +2. 不迁移 `character-animation/jobs/:taskId` +3. 不迁移 `character-animation/publish` +4. 不落 `character_animation_asset` 强业务表 +5. 不为导入草稿创建 `asset_object` +6. 不为导入草稿创建 `asset_entity_binding` +7. 不读取旧本地 `public/` 路径作为导入源 + +## 4. 旧接口兼容 contract + +### 4.1 `GET /api/assets/character-animation/templates` + +返回结构继续保持: + +1. `ok` +2. `templates` + +每个模板继续包含: + +1. `id` +2. `label` +3. `animation` +4. `promptSuffix` +5. `notes` + +当前模板列表固定为内置四项: + +1. `idle_loop` +2. `run_side` +3. `attack_slash` +4. `die_fall` + +### 4.2 `POST /api/assets/character-animation/import-video` + +请求结构继续保持: + +1. `characterId` +2. `animation` +3. `videoSource` +4. `sourceLabel` + +返回结构继续保持: + +1. `ok` +2. `importedVideoPath` +3. `draftId` +4. `saveMessage` + +补充口径: + +1. `videoSource` 当前阶段只接受 `data:video/*;base64,...` +2. `importedVideoPath` 继续返回旧前端习惯的 `/generated-character-drafts/*` +3. 底层对象真相在 OSS,不再写本地 `public/` +4. `saveMessage` 明确说明当前是“已导入 OSS 草稿区” + +## 5. OSS 对象键规划 + +导入视频固定写入: + +`generated-character-drafts/{characterSegment}/animation/{animationSegment}/{draftId}/{sourceLabel}.{extension}` + +其中: + +1. `characterSegment` 来自 `characterId` 的安全路径片段 +2. `animationSegment` 来自 `animation` 的安全路径片段 +3. `draftId` 固定为 `animation-import-{unixMillis}` +4. `extension` 从 Data URL MIME 类型派生 + +## 6. 元数据规范 + +导入视频对象写入以下 `x-oss-meta-*` 元数据: + +1. `asset_kind = character_animation_reference_video` +2. `owner_user_id = asset-tool` +3. `entity_kind = character` +4. `entity_id = characterId` +5. `slot = animation_reference_video` +6. `animation = animation` + +说明: + +1. 旧资产工坊接口没有显式 Bearer,第一批继续使用 `asset-tool` 作为兼容归属。 +2. 草稿导入视频只是后续动作生成的参考输入,不是正式发布资产,因此本批不确认 `asset_object`。 + +## 7. 数据源边界 + +Rust 第一批只接受 `data:video/*;base64,...`。 + +暂不接受旧本地 public 路径,原因是: + +1. Rust 迁移目标是不再依赖本地 `public/` 作为资产真相。 +2. 若为了兼容旧路径再读取本地文件,会延长旧写盘链路生命周期。 +3. 前端导入入口当前可直接传视频 Data URL,足以满足本批最小闭环。 + +## 8. 完成定义 + +当以下条件满足时,本批视为完成: + +1. Rust 已兼容 `character-animation/templates` +2. Rust 已兼容 `character-animation/import-video` +3. 导入视频写入 OSS `generated-character-drafts/*` +4. 接口返回 `importedVideoPath / draftId` 旧 contract +5. 不再产生本地 `public/generated-character-drafts/*` 导入文件 + +## 9. 关联文档 + +1. [M6_CHARACTER_VISUAL_ASSET_OSS_INTEGRATION_STAGE1_2026-04-22.md](./M6_CHARACTER_VISUAL_ASSET_OSS_INTEGRATION_STAGE1_2026-04-22.md) +2. [M6_CUSTOM_WORLD_ASSET_OSS_INTEGRATION_STAGE1_2026-04-22.md](./M6_CUSTOM_WORLD_ASSET_OSS_INTEGRATION_STAGE1_2026-04-22.md) +3. [M6_OSS_SERVER_UPLOAD_AND_STS_POLICY_2026-04-21.md](./M6_OSS_SERVER_UPLOAD_AND_STS_POLICY_2026-04-21.md) diff --git a/docs/technical/M6_CHARACTER_VISUAL_ASSET_OSS_INTEGRATION_STAGE1_2026-04-22.md b/docs/technical/M6_CHARACTER_VISUAL_ASSET_OSS_INTEGRATION_STAGE1_2026-04-22.md new file mode 100644 index 00000000..41d91cb2 --- /dev/null +++ b/docs/technical/M6_CHARACTER_VISUAL_ASSET_OSS_INTEGRATION_STAGE1_2026-04-22.md @@ -0,0 +1,242 @@ +# M6 角色主形象资产接入 OSS 第一批设计 + +日期:`2026-04-22` + +## 1. 文档目的 + +这份文档用于冻结 `M6` 第一批“角色主形象资产链”的真实落地口径。 + +本批只解决以下三条旧接口的 Rust 重写入口: + +1. `POST /api/assets/character-visual/generate` +2. `GET /api/assets/character-visual/jobs/:taskId` +3. `POST /api/assets/character-visual/publish` + +目标不是一次性把整套资产系统迁完,而是先把“角色主形象候选生成 + 查询 + 正式发布”从旧 Node 的本地 `public/generated-*` 真相,切到: + +1. `OSS` +2. `asset_object` +3. `asset_entity_binding` +4. `AI task` 任务态 + +形成第一批正式主链。 + +## 2. 当前前提 + +当前仓库已经具备以下能力: + +1. `platform-oss::OssClient::put_object` +2. `platform-oss::OssClient::head_object` +3. `asset_object` +4. `asset_entity_binding` +5. `module-ai` 进程内 `AiTaskService` +6. `platform-llm` OpenAI 兼容文本模型网关 +7. `custom world` 图片兼容入口已经完成一版 `OSS + asset_object + asset_entity_binding` 落地 + +因此本批不重新设计一套新资产基础设施,而是复用: + +1. 既有 `OSS` 上传与确认链 +2. 既有 `asset_object / asset_entity_binding` +3. 既有 `AiTaskService` + +## 3. 本批范围 + +### 3.1 要完成的内容 + +1. 兼容角色主形象候选生成接口 +2. 兼容角色主形象任务状态查询接口 +3. 兼容角色主形象正式发布接口 +4. 候选草稿对象写入 OSS `generated-character-drafts/*` +5. 正式主图对象写入 OSS `generated-characters/*` +6. 正式发布结果写入 `asset_object` +7. 正式发布结果绑定到角色实体槽位 +8. 返回字段继续保持旧前端可消费 contract + +### 3.2 本批不解决的内容 + +1. 不落 `asset_job` 正式 SpacetimeDB 表 +2. 不落 `character_visual_asset` 强业务表 +3. 不落 `character-workflow-cache` +4. 不落 `character-animation` 全链路 +5. 不回写 `src/data/characterOverrides.json` +6. 不要求前端改成新的对象读取协议 + +## 4. 旧接口兼容 contract + +### 4.1 `POST /api/assets/character-visual/generate` + +返回结构继续保持: + +1. `ok` +2. `taskId` +3. `model` +4. `prompt` +5. `drafts` + +其中每个 `draft` 继续包含: + +1. `id` +2. `label` +3. `imageSrc` +4. `width` +5. `height` + +补充口径: + +1. `imageSrc` 继续返回旧前端习惯的 `/generated-character-drafts/*` +2. 草稿对象底层不再写本地 `public/` +3. 草稿对象真相仅在 OSS + +### 4.2 `GET /api/assets/character-visual/jobs/:taskId` + +返回结构继续保持旧前端读取方式: + +1. `taskId` +2. `kind` +3. `status` +4. `characterId` +5. `model` +6. `prompt` +7. `createdAt` +8. `updatedAt` +9. `result` +10. `errorMessage` + +当前阶段直接复用 `AiTaskService` 内存态任务快照派生,不要求前端改字段名。 + +### 4.3 `POST /api/assets/character-visual/publish` + +返回结构继续保持: + +1. `ok` +2. `assetId` +3. `portraitPath` +4. `overrideMap` +5. `saveMessage` + +补充口径: + +1. `portraitPath` 固定返回 `/generated-characters/*` +2. 当前 `overrideMap` 先返回空对象 `{}`,只做 contract 兼容,不再在 Rust 后端写本地覆盖文件 +3. `saveMessage` 明确说明当前是“已写入 OSS 并绑定业务实体” + +## 5. 业务实体与槽位约定 + +本批统一复用通用 `asset_entity_binding`。 + +### 5.1 角色主形象正式对象 + +| 字段 | 取值 | +| --- | --- | +| `entity_kind` | `character` | +| `entity_id` | `characterId` | +| `slot` | `primary_visual` | +| `asset_kind` | `character_visual` | + +补充口径: + +1. 同一角色重复发布时,允许覆盖到最新对象 +2. 候选草稿对象不创建业务绑定 +3. 业务引用真相以 `asset_entity_binding` 为准 + +## 6. OSS 对象键规划 + +### 6.1 候选草稿 + +候选草稿固定写入: + +`generated-character-drafts/{characterSegment}/visual/{taskId}/candidate-{index}.svg` + +### 6.2 正式主图 + +正式主图固定写入: + +`generated-characters/{characterSegment}/visual/{assetId}/master.svg` + +## 7. 任务状态口径 + +当前阶段不新增独立 `asset_job` 表,统一复用 `module-ai` 的内存态 `AiTaskService`。 + +### 7.1 任务种类 + +`task_kind` 统一使用: + +`custom_world_generation` + +说明: + +1. 这是当前 `module-ai` 已冻结的可用任务类型之一 +2. 本批只把它当作“生成类资产任务”的最小任务容器 +3. 后续 `asset_job` 表落地后,再把角色主形象任务迁到正式资产任务模型 + +### 7.2 阶段映射 + +当前固定使用以下阶段: + +1. `prepare_prompt` +2. `request_model` +3. `normalize_result` +4. `persist_result` + +其中: + +1. `generate` 成功后,任务直接进入 `completed` +2. `publish` 不额外创建新任务,只消费已有候选路径 + +## 8. Rust 第一批生成策略 + +本批生成策略固定为: + +1. 若已配置 `platform-llm`,则用文本模型生成一个结构化占位结果 +2. 服务端把结果渲染成 SVG 占位图 +3. 占位图写入 OSS 草稿路径 + +说明: + +1. 这不是最终的 DashScope 图片模型正式链 +2. 但它可以先把“接口 contract + 任务状态 + OSS 真相 + 正式发布绑定”全部打通 +3. 后续替换成真实图片模型时,不需要再改动主链结构 + +## 9. 服务端执行顺序 + +### 9.1 生成 + +每次调用 `generate` 固定执行: + +1. 创建 `AiTask` +2. 生成最终 prompt +3. 产出候选 SVG 字节 +4. 每个候选对象上传 OSS +5. 回写任务结果 +6. 返回 `/generated-character-drafts/*` + +### 9.2 发布 + +每次调用 `publish` 固定执行: + +1. 校验 `selectedPreviewSource` +2. 解析旧 `/generated-*` 路径为 `object_key` +3. 调 OSS `HEAD Object` 确认候选对象存在 +4. 读取候选对象内容 +5. 上传正式主图对象到 `generated-characters/*` +6. 对正式对象执行 `asset_object` 确认 +7. 对正式对象执行 `asset_entity_binding` +8. 返回 `/generated-characters/*` + +## 10. 完成定义 + +当以下条件满足时,本批视为完成: + +1. Rust 已兼容 `character-visual generate / jobs / publish` +2. 候选草稿不再写本地 `public/generated-character-drafts` +3. 正式主图不再写本地 `public/generated-characters` +4. 发布成功后能形成 `asset_object` +5. 发布成功后能形成 `asset_entity_binding` +6. 前端仍能继续消费 `taskId / drafts / portraitPath` 旧 contract + +## 11. 关联文档 + +1. [M6_CUSTOM_WORLD_ASSET_OSS_INTEGRATION_STAGE1_2026-04-22.md](./M6_CUSTOM_WORLD_ASSET_OSS_INTEGRATION_STAGE1_2026-04-22.md) +2. [SPACETIMEDB_AXUM_OSS_BACKEND_REWRITE_DESIGN_2026-04-20.md](./SPACETIMEDB_AXUM_OSS_BACKEND_REWRITE_DESIGN_2026-04-20.md) +3. [SPACETIMEDB_ASSET_OBJECT_STORAGE_DESIGN_2026-04-21.md](./SPACETIMEDB_ASSET_OBJECT_STORAGE_DESIGN_2026-04-21.md) +4. [ASSET_OBJECT_CONFIRM_FLOW_DESIGN_2026-04-21.md](./ASSET_OBJECT_CONFIRM_FLOW_DESIGN_2026-04-21.md) diff --git a/docs/technical/M6_CHARACTER_WORKFLOW_CACHE_OSS_STAGE1_2026-04-22.md b/docs/technical/M6_CHARACTER_WORKFLOW_CACHE_OSS_STAGE1_2026-04-22.md new file mode 100644 index 00000000..fd41887e --- /dev/null +++ b/docs/technical/M6_CHARACTER_WORKFLOW_CACHE_OSS_STAGE1_2026-04-22.md @@ -0,0 +1,138 @@ +# M6 角色资产工作流缓存接入 OSS 第一批设计 + +日期:`2026-04-22` + +## 1. 文档目的 + +这份文档用于冻结 `M6` 第一批“角色资产工作流缓存”的真实落地口径。 + +本批只解决以下两条旧接口的 Rust 重写入口: + +1. `GET /api/assets/character-workflow-cache/:characterId` +2. `POST /api/assets/character-workflow-cache` + +目标是把旧 Node 写入本地 `public/generated-character-drafts/*/workflow-cache.json` 的临时缓存,切到 OSS JSON 草稿对象,继续保持前端当前可消费 contract。 + +## 2. 当前前提 + +当前仓库已经具备以下能力: + +1. `platform-oss::OssClient::put_object` +2. `platform-oss::OssClient::sign_get_object_url` +3. `generated-character-drafts/*` 兼容对象键前缀 +4. 角色主形象与动作导入已经开始把草稿对象写入 OSS + +因此本批不新增数据库表,也不引入本地 JSON 文件。 + +## 3. 本批范围 + +### 3.1 要完成的内容 + +1. 兼容工作流缓存读取接口 +2. 兼容工作流缓存保存接口 +3. 缓存 JSON 写入 OSS `generated-character-drafts/*` +4. 返回字段继续保持旧前端可消费 contract +5. 不再把缓存写入本地 `public/` + +### 3.2 本批不解决的内容 + +1. 不落 `asset_object` +2. 不落 `asset_manifest` +3. 不落 `character_visual_asset` +4. 不落 `character_animation_asset` +5. 不做跨设备强一致合并 +6. 不迁移历史本地缓存文件 + +## 4. 旧接口兼容 contract + +### 4.1 `GET /api/assets/character-workflow-cache/:characterId` + +返回结构继续保持: + +1. `ok` +2. `cache` + +补充口径: + +1. 未找到 OSS 缓存对象时返回 `cache: null` +2. 找到对象但 `characterId` 不匹配时返回 `cache: null` +3. 返回的 `cache` 字段保持前端 `CharacterAssetWorkflowCache` 结构 + +### 4.2 `POST /api/assets/character-workflow-cache` + +请求结构继续保持前端当前字段: + +1. `characterId` +2. `visualPromptText` +3. `animationPromptText` +4. `visualDrafts` +5. `selectedVisualDraftId` +6. `selectedAnimation` +7. `imageSrc` +8. `generatedVisualAssetId` +9. `generatedAnimationSetId` +10. `animationMap` + +返回结构继续保持: + +1. `ok` +2. `cache` +3. `saveMessage` + +## 5. OSS 对象键规划 + +缓存 JSON 固定写入: + +`generated-character-drafts/{characterSegment}/workflow-cache/workflow-cache.json` + +其中: + +1. `characterSegment` 来自 `characterId` 的安全路径片段 +2. 文件名固定为 `workflow-cache.json` +3. content type 固定为 `application/json; charset=utf-8` + +## 6. 字段归一化规则 + +保存接口固定执行以下归一化: + +1. `characterId` 必填,trim 后不能为空 +2. `visualPromptText` 最长保留 280 字 +3. `animationPromptText` 最长保留 280 字 +4. `visualDrafts` 只保留有 `imageSrc` 的候选 +5. `visualDrafts[].width` 默认 `1024` +6. `visualDrafts[].height` 默认 `1536` +7. `selectedAnimation` 默认 `idle` +8. 空 `imageSrc / generatedVisualAssetId / generatedAnimationSetId` 不序列化 +9. 非对象 `animationMap` 归一化为 `null` +10. `updatedAt` 由 Rust 服务端生成 UTC 时间 + +## 7. 元数据规范 + +缓存 JSON 对象写入以下 `x-oss-meta-*` 元数据: + +1. `asset_kind = character_workflow_cache` +2. `owner_user_id = asset-tool` +3. `entity_kind = character` +4. `entity_id = characterId` +5. `slot = workflow_cache` + +说明: + +1. 旧资产工坊接口没有显式 Bearer,第一批继续使用 `asset-tool` 作为兼容归属。 +2. workflow cache 是工作流草稿状态,不是正式可发布资产,因此本批不确认 `asset_object`。 + +## 8. 完成定义 + +当以下条件满足时,本批视为完成: + +1. Rust 已兼容 `GET /api/assets/character-workflow-cache/:characterId` +2. Rust 已兼容 `POST /api/assets/character-workflow-cache` +3. 缓存 JSON 写入 OSS `generated-character-drafts/*` +4. 未命中时返回 `cache: null` +5. 前端仍能继续消费 `cache / saveMessage` 旧 contract + +## 9. 关联文档 + +1. [M6_CHARACTER_VISUAL_ASSET_OSS_INTEGRATION_STAGE1_2026-04-22.md](./M6_CHARACTER_VISUAL_ASSET_OSS_INTEGRATION_STAGE1_2026-04-22.md) +2. [M6_CHARACTER_ANIMATION_IMPORT_AND_TEMPLATE_STAGE1_2026-04-22.md](./M6_CHARACTER_ANIMATION_IMPORT_AND_TEMPLATE_STAGE1_2026-04-22.md) +3. [M6_OSS_SERVER_UPLOAD_AND_STS_POLICY_2026-04-21.md](./M6_OSS_SERVER_UPLOAD_AND_STS_POLICY_2026-04-21.md) diff --git a/docs/technical/M6_CUSTOM_WORLD_ASSET_OSS_INTEGRATION_STAGE2_2026-04-22.md b/docs/technical/M6_CUSTOM_WORLD_ASSET_OSS_INTEGRATION_STAGE2_2026-04-22.md new file mode 100644 index 00000000..22fbbf8b --- /dev/null +++ b/docs/technical/M6_CUSTOM_WORLD_ASSET_OSS_INTEGRATION_STAGE2_2026-04-22.md @@ -0,0 +1,203 @@ +# M6 custom world 场景图 / 封面图 Stage 2 设计 + +日期:`2026-04-22` + +## 1. 文档目的 + +这份文档用于冻结 custom world 图片链在 `Stage 1` 之后的第二批迁移口径。 + +`Stage 1` 已完成: + +1. `scene-image / cover-image / cover-upload` 不再写仓库 `public/` +2. 图片对象统一写入 `OSS` +3. 写入后统一形成 `asset_object + asset_entity_binding` + +但当前仍有两段能力没有迁完: + +1. `scene-image / cover-image` 仍使用 Rust SVG 占位图,而不是 Node 旧链路里的真实 DashScope 图片生成 +2. `cover-upload` 仍未迁移 Node 旧链路里的 `cropRect + 16:9 裁剪 + WebP 压缩` + +本批目标就是把这两段缺失能力补齐,同时继续保持 `Stage 1` 已冻结的 OSS 真相链。 + +## 2. 本批范围 + +### 2.1 要完成的内容 + +1. `POST /api/custom-world/scene-image` 接入真实 DashScope 图片生成 +2. `POST /api/custom-world/cover-image` 接入真实 DashScope 图片生成 +3. `POST /api/custom-world/cover-upload` 接入裁剪、缩放、压缩 +4. 生成后的图片仍统一写入 `OSS` +5. 每次成功写图仍统一形成 `asset_object + asset_entity_binding` +6. 路由响应继续保持旧前端字段形状 + +### 2.2 本批不解决的内容 + +1. 不引入新的 custom world 图片任务表 +2. 不引入 `signedUrl` 直返业务字段 +3. 不在本批补视频 Range、分片传输或前端编辑器新交互 +4. 不在本批迁移更多 custom world 非图片媒体链路 + +## 3. 旧 Node 口径对齐 + +### 3.1 场景图生成 + +Node 旧链路区分两种模式: + +1. 无参考图:走 DashScope `text2image` +2. 有参考图:走 DashScope `multimodal-generation` + +本批 Rust 继续保持同口径: + +1. `referenceImageSrc` 为空时: + - 模型默认 `wan2.2-t2i-flash` + - 路径:`/services/aigc/text2image/image-synthesis` + - 异步创建任务后轮询 `/tasks/{taskId}` +2. `referenceImageSrc` 非空时: + - 模型默认 `qwen-image-2.0` + - 路径:`/services/aigc/multimodal-generation/generation` + - 直接取返回中的第一张图 + +### 3.2 封面图生成 + +Node 旧链路也区分两种模式: + +1. 无参考图:`wan2.2-t2i-flash` +2. 有参考图:`qwen-image-2.0` + +Rust 本批保持一致,并继续沿用: + +1. `profile + opening act + selected roles + landmarks` 作为 prompt 上下文 +2. 最多 6 张参考图 +3. 返回 `sourceType = generated` + +### 3.3 封面上传 + +Node 旧链路对上传封面有明确处理: + +1. 请求必须提供 `cropRect` +2. `cropRect` 必须保持 `16:9` +3. 输出固定缩放为 `1600x900` +4. 输出格式固定为 `webp` +5. 输出体积上限 `1.5 MB` +6. 原图体积上限 `10 MB` + +Rust 本批必须保持这组兼容约束。 + +## 4. 请求与响应 contract + +### 4.1 `POST /api/custom-world/scene-image` + +在 `Stage 1` 字段基础上,Rust 本批补齐兼容读取: + +1. `negativePrompt` +2. `referenceImageSrc` + +返回仍为: + +1. `imageSrc` +2. `assetId` +3. `model` +4. `size` +5. `taskId` +6. `prompt` +7. `actualPrompt` + +### 4.2 `POST /api/custom-world/cover-image` + +继续兼容: + +1. `profile` +2. `userPrompt` +3. `referenceImageSrc` +4. `characterRoleIds` +5. `size` + +返回仍为: + +1. `imageSrc` +2. `assetId` +3. `sourceType = generated` +4. `model` +5. `size` +6. `taskId` +7. `prompt` +8. `actualPrompt` + +### 4.3 `POST /api/custom-world/cover-upload` + +继续兼容: + +1. `profileId` +2. `worldName` +3. `imageDataUrl` +4. `cropRect` + +返回仍为: + +1. `imageSrc` +2. `assetId` +3. `sourceType = uploaded` + +## 5. 服务端执行顺序 + +### 5.1 场景图 / 封面图生成 + +统一执行: + +1. 归一 prompt 与模型选择 +2. 向 DashScope 发起生成请求 +3. 下载生成结果图片二进制 +4. `put_object` +5. `HEAD Object` +6. `confirm asset_object` +7. `bind asset_entity_binding` +8. 返回 `legacyPublicPath` + +### 5.2 封面上传 + +统一执行: + +1. 解析 `imageDataUrl` +2. 校验原图体积 +3. 解码图片 +4. 按 `cropRect` 裁剪 +5. 校验裁剪区域 `16:9` +6. 缩放到 `1600x900` +7. 编码为 `webp` +8. 若超过 `1.5 MB`,逐档降低质量重试 +9. `put_object` +10. `HEAD Object` +11. `confirm asset_object` +12. `bind asset_entity_binding` +13. 返回 `legacyPublicPath` + +## 6. 环境变量与模型口径 + +本批继续复用现有 DashScope 环境变量,不新增另一套命名: + +1. `DASHSCOPE_BASE_URL` +2. `DASHSCOPE_API_KEY` +3. `DASHSCOPE_IMAGE_REQUEST_TIMEOUT_MS` + +模型默认值固定为: + +1. 场景图文生图:`wan2.2-t2i-flash` +2. 场景图参考图模式:`qwen-image-2.0` +3. 封面文生图:`wan2.2-t2i-flash` +4. 封面参考图模式:`qwen-image-2.0` + +## 7. 完成定义 + +当以下条件满足时,本批视为完成: + +1. `scene-image` 不再返回 Rust SVG 占位图 +2. `cover-image` 不再返回 Rust SVG 占位图 +3. `cover-upload` 已执行 `cropRect + 16:9 + webp + 1.5MB` +4. 三条链路仍统一落到 `OSS + asset_object + asset_entity_binding` +5. 前端无需改 contract 即可继续消费 + +## 8. 关联文档 + +1. [M6_CUSTOM_WORLD_ASSET_OSS_INTEGRATION_STAGE1_2026-04-22.md](./M6_CUSTOM_WORLD_ASSET_OSS_INTEGRATION_STAGE1_2026-04-22.md) +2. [ASSET_OBJECT_CONFIRM_FLOW_DESIGN_2026-04-21.md](./ASSET_OBJECT_CONFIRM_FLOW_DESIGN_2026-04-21.md) +3. [M6_LEGACY_GENERATED_PATH_OSS_READ_COMPAT_2026-04-22.md](./M6_LEGACY_GENERATED_PATH_OSS_READ_COMPAT_2026-04-22.md) diff --git a/docs/technical/M6_LEGACY_GENERATED_PATH_OSS_READ_COMPAT_2026-04-22.md b/docs/technical/M6_LEGACY_GENERATED_PATH_OSS_READ_COMPAT_2026-04-22.md new file mode 100644 index 00000000..b1c8998c --- /dev/null +++ b/docs/technical/M6_LEGACY_GENERATED_PATH_OSS_READ_COMPAT_2026-04-22.md @@ -0,0 +1,75 @@ +# M6 旧 generated 路径 OSS 读取兼容设计 + +日期:`2026-04-22` + +## 1. 文档目的 + +这份文档冻结 `M6` 第一批 OSS 化之后,旧前端继续访问 `/generated-*` 路径的 Rust 后端兼容口径。 + +当前角色主形象、角色动作、custom world 场景图和封面图已经把新生成资产写入私有 OSS。旧前端仍会把以下路径当作图片、视频或动作帧地址直接交给 ``、`