This commit is contained in:
2026-05-08 20:48:29 +08:00
parent abf1f1ebea
commit 94975e4735
82 changed files with 7786 additions and 1012 deletions
@@ -43,6 +43,7 @@ Default body:
"model": "gpt-image-2",
"prompt": "<prompt>",
"n": 1,
"official_fallback": true,
"size": "1:1"
}
```
@@ -237,6 +237,7 @@ async function generateOne(env, template, outDir) {
model: 'gpt-image-2',
prompt: buildPrompt(template),
n: 1,
official_fallback: true,
size: '1:1',
};
const payload = await fetchJson(
@@ -304,6 +305,7 @@ if (dryRun) {
model: 'gpt-image-2',
prompt: buildPrompt(template),
n: 1,
official_fallback: true,
size: '1:1',
},
})),
+8
View File
@@ -16,6 +16,14 @@
---
## 2026-05-08 APIMart 接口统一携带 `official_fallback`
- 背景:APIMart 的图片生成和 Responses 接口在仓库内分散于 `api-server``platform-llm` 和本地 skill 脚本,若只修单点,容易出现不同入口的上游请求体不一致。
- 决策:凡是仓库内调用 APIMart 的 OpenAI 兼容接口,请求体统一携带 `official_fallback: true`;其中图片生成请求直接固定写入,`platform-llm` 的 APIMart GPT-5 client 通过显式开关开启,不默认扩散到 Ark 等其它 provider。
- 影响范围:`server-rs/crates/api-server/src/openai_image_generation.rs``server-rs/crates/api-server/src/puzzle.rs``server-rs/crates/api-server/src/state.rs``server-rs/crates/platform-llm/src/lib.rs``.codex/skills/gpt-image-2-apimart/` 和相关技术文档。
- 验证方式:图片生成与 creative-agent APIMart 路径的单测都应断言 `official_fallback` 已写入请求 JSON;编码检查和相关 Rust 测试应持续通过。
- 关联文档:`docs/technical/PUZZLE_APIMART_IMAGE_MODEL_ROUTING_2026-05-01.md``docs/technical/RPG_IMAGE_GENERATION_GPT_IMAGE_2_MIGRATION_2026-05-02.md``docs/technical/CREATIVE_INTERACTIVE_CONTENT_AGENT_TECHNICAL_SOLUTION_2026-05-05.md`
## 2026-05-07 server-rs Cargo 依赖集中到 workspace
- 背景:`server-rs` 多 crate 已稳定成 DDD workspace,成员 `Cargo.toml` 中重复散写第三方版本和本地 path 依赖,升级 SpacetimeDB SDK、`serde``reqwest``tokio` 等依赖时容易漂移。
+18 -1
View File
@@ -77,13 +77,30 @@
- 验证:不打印密钥内容,只检查 `APIMART_API_KEY` 非空;重启后触发拼图生成不再返回本地配置缺失的 503。
- 关联:`docs/technical/PUZZLE_APIMART_IMAGE_MODEL_ROUTING_2026-05-01.md``.codex/skills/gpt-image-2-apimart/SKILL.md`
## 拼图图片生成 98% 后报 OSS V4 签名时间格式化失败
- 现象:拼图创作表单生成进度卡在 98%,`POST /api/runtime/puzzle/agent/sessions/{sessionId}/actions` 返回 `502 Bad Gateway`,前端提示 `拼图图片生成失败:OSS V4 签名时间格式化失败`
- 原因:`platform-oss` 曾用 `OffsetDateTime::time().to_string()` 拼接 `x-oss-date`,UTC 小时、分钟或秒为个位数时可能缺少前导零,导致 V4 签名时间不是固定 `YYYYMMDDTHHMMSSZ`
- 处理:OSS V4 签名日期统一显式补零格式化;签名 scope 用 `YYYYMMDD`,完整签名时间用 `YYYYMMDDTHHMMSSZ`,不要再依赖 `time().to_string()`
- 验证:运行 `cargo test -p platform-oss``cargo check -p api-server`;重启 `npm run api-server` 后检查 `/healthz`,再重新触发拼图生成。
- 关联:`server-rs/crates/platform-oss/src/lib.rs``server-rs/crates/api-server/src/assets.rs``docs/technical/M6_OSS_SERVER_UPLOAD_AND_STS_POLICY_2026-04-21.md`
## 拼图生成完成后图片只显示破图或 alt 文案
- 现象:拼图结果页生成完成后,“画面图”区域出现破图图标和作品名,图片无法正常预览;但打开历史拼图素材时同一张图可能可以正常预览。
- 原因:拼图正式图保存为 `/generated-puzzle-assets/*` 兼容标识,旧 `/generated-*` 直读代理已删除;如果前端没有通过 `ResolvedAssetImage` / `/api/assets/read-url` 换签,或收到无前导斜杠的 `generated-puzzle-assets/*` object key 后未识别为 generated 私有资源,浏览器会直接请求裸路径并失败。生成完成后的结果图还会传入 `refreshKey`,它只能用于重新请求 `/api/assets/read-url`,不能给 OSS V4 签名 URL 追加 `_v`;OSS 会把 query 纳入签名,额外参数会让签名失效,历史素材常因未传 `refreshKey` 而表现正常。
- 处理:拼图结果页、发布预览、运行态和历史素材预览都走 `ResolvedAssetImage``useResolvedAssetReadUrl``isGeneratedLegacyPath(...)` 必须同时识别 `/generated-*``generated-*``refreshKey` 只绕过前端签名缓存并重新换签,不修改已返回的 OSS 签名 URL;禁止恢复 `/generated-puzzle-assets` 直读代理。
- 验证:运行 `npm run test -- src\services\assetReadUrlService.test.ts src\hooks\useResolvedAssetReadUrl.test.tsx src\components\puzzle-result\PuzzleResultView.test.tsx`,再触发一次真实生成确认 Network 中先请求 `/api/assets/read-url`,图片 `src` 为未追加 `_v` 的签名 URL。
- 关联:`src/services/assetReadUrlService.ts``src/components/ResolvedAssetImage.tsx``docs/technical/PUZZLE_IMAGE_ASSET_PROXY_FIX_2026-04-27.md`
## 本地短信登录页签突然消失
- 现象:登录弹窗只剩密码登录,短信登录页签看起来像被删掉,但 `LoginScreen` 中手机号验证码表单仍存在。
- 原因:前端根据 `GET /api/auth/login-options` 返回的 `availableLoginMethods` 渲染页签;常见根因有两类:
- 本地启动脚本没有让 `.env.local` 覆盖 `.env``SMS_AUTH_ENABLED=true` 不生效,后端只返回 `["password"]`
- Rust API 直连已返回 `["phone","password"]`,但 Vite 代理目标指向未监听端口,导致 3000 域名下的 `login-options` 返回 `500``AuthGate` 降级成 `["password"]`
- 处理:优先用 `npm run api-server``npm run dev:rust``npm run dev` 启动,这些入口应保持 shell 环境变量最高优先级,并允许 `.env.local` 覆盖 `.env`;完整栈启动时还要确保脚本计算出的 `RUST_SERVER_TARGET` 不被 `.env.local` 里的旧值覆盖。排查时先请求 3000 域名下的 `/api/auth/login-options`,再直连 Rust API 目标,并核对 `.env.local``SMS_AUTH_ENABLED` 与代理端口。
- 3000 端口被旧 `dev:web` 占用后,新的完整栈 Vite 自动漂移到 3001/3002;浏览器仍打开旧 3000 页面,旧页面继续代理到已经下线的端口。
- 处理:优先用 `npm run api-server``npm run dev:rust``npm run dev` 启动,这些入口应保持 shell 环境变量最高优先级,并允许 `.env.local` 覆盖 `.env`;完整栈启动时还要确保脚本计算出的 `RUST_SERVER_TARGET` 不被 `.env.local` 里的旧值覆盖。排查时先请求 3000 域名下的 `/api/auth/login-options`,再直连 Rust API 目标,并核对 `.env.local``SMS_AUTH_ENABLED` 与代理端口;若 3001/3002 才返回正确结果,说明当前 3000 是旧前端进程,应清理旧进程后重启。
- 验证:`http://127.0.0.1:3000/api/auth/login-options` 返回至少 `{"availableLoginMethods":["phone","password"]}` 后,登录弹窗会恢复短信登录页签和“获取验证码”按钮。
- 关联:`scripts/api-server-dev.mjs``scripts/api-server-maincloud.mjs``scripts/dev-rust-stack.sh``scripts/dev-web-rust.mjs``docs/technical/AUTH_LOGIN_OPTIONS_DESIGN_2026-04-21.md`
+14
View File
@@ -39,6 +39,20 @@ APIMART_BASE_URL=
APIMART_API_KEY=
APIMART_IMAGE_REQUEST_TIMEOUT_MS=180000
VECTOR_ENGINE_BASE_URL=
VECTOR_ENGINE_API_KEY=
VECTOR_ENGINE_AUDIO_REQUEST_TIMEOUT_MS=180000
VOLCENGINE_SPEECH_API_KEY=
VOLCENGINE_SPEECH_APP_ID=
VOLCENGINE_SPEECH_ACCESS_KEY=
VOLCENGINE_SPEECH_ASR_RESOURCE_ID=volc.seedasr.sauc.concurrent
VOLCENGINE_SPEECH_TTS_RESOURCE_ID=seed-tts-2.0
VOLCENGINE_SPEECH_REQUEST_TIMEOUT_MS=180000
VOLCENGINE_SPEECH_ASR_WS_URL=wss://openspeech.bytedance.com/api/v3/sauc/bigmodel_async
VOLCENGINE_SPEECH_TTS_BIDIRECTION_WS_URL=wss://openspeech.bytedance.com/api/v3/tts/bidirection
VOLCENGINE_SPEECH_TTS_SSE_URL=https://openspeech.bytedance.com/api/v3/tts/unidirectional/sse
ARK_CHARACTER_VIDEO_BASE_URL=
ARK_CHARACTER_VIDEO_API_KEY=
ARK_CHARACTER_VIDEO_MODEL=
@@ -37,3 +37,4 @@
4. 移动端卡片仍以紧凑横滑为主,参考图使用暗色遮罩承接标题,文本不得溢出卡片。
5. 当前创作 Tab 的可见玩法卡必须真实渲染 `img`,不能只在隐藏弹窗或旧入口中配置图片。
6. 参考图卡片上的标题和副标题必须显式使用白色文字,并配合底部加深渐变与文字阴影;禁止依赖 `text-inherit`,避免黑字叠在暗蒙版上。
7. 当前创作 Tab 顶部不再保留“10分钟创作一个精品互动玩法”标题,玩法参考图卡带直接作为首屏入口;移动端卡带必须支持横向拖动滑动。
@@ -27,12 +27,15 @@
## 3. 推荐页
移动端推荐页默认不展示搜索和频道横滑条,进入一级“推荐”后直接渲染公开作品
移动端推荐页默认不展示搜索和频道横滑条,进入一级“推荐”后直接渲染公开作品启动后的内容
- 数据来源沿用 `featuredEntries + latestEntries` 去重后的公开作品列表。
- 卡片保留现有作品读模型字段:封面、作者、游玩、改造、点赞、标签
- 移动端推荐卡使用近全屏大画面比例,底部展示互动指标、作者和主操作,不写规则说明类文案
- 无数据、加载中和错误态沿用短状态文案。
- 首个可运行作品自动进入推荐页内嵌运行态,主视口不再展示作品封面卡
- 主视口占据顶部栏与底部作品区之间的主要空间,保持黑色运行容器、圆角边界和短加载态,直接承载作品启动后的玩法画面
- 主视口下方展示当前作品的游玩、点赞、评论/改造等紧凑指标、作者头像、作者名与作品名,不写规则说明类文案。
- 底部作品区使用横向滑动切换器,条目只展示作品名、类型和核心指标;点击或滑动到其他作品时切换上方运行内容。
- 点击作品元信息仍可进入既有详情页,点赞、改造、复制作品号等完整操作继续收敛在详情页。
- 无数据、加载中、启动失败和暂不支持内嵌运行的作品沿用短状态文案。
桌面端仍保持现有首页布局,只把一级导航文案从“首页”改为“推荐”。
@@ -72,12 +75,13 @@
## 7. 验收
1. 移动端底部导航显示“推荐 / 发现 / 创作 / 草稿 / 我的”,未登录时显示“推荐 / 创作 / 发现”。
2. 点击“推荐”直接看到公开作品推荐流,不再先看到搜索框频道 Tab。
2. 点击“推荐”直接看到公开作品启动后的内容,不再先看到搜索框频道 Tab 或封面卡流
3. 点击“发现”可看到搜索、推荐、今日、分类、排行子 Tab。
4. 点击“草稿”看到原创作页作品列表。
5. 点击“创作”只看到新建创作入口。
6. “我的”里的“玩过”弹层包含原存档列表入口,点击存档能继续恢复。
7. 移动端底部导航为悬浮胶囊样式,保留当前明暗主题色变量,不新增第三套主题。
8. 推荐页底部作品区可横向滑动并切换作品,切换后上方运行视口同步进入对应作品内容。
## 8. 2026-05-07 未登录三栏补充
@@ -45,6 +45,7 @@
1. 玩家需要优先依赖画面主体、构图和色块识别位置。
2. 编号覆盖会削弱“完整图片被逐步复原”的视觉奖励。
3. 合成后的拼图块只保留原图切片、外轮廓描边和必要的拖拽层级,不叠加额外的色块、暗层或蒙版,避免破坏原图识别。
### 3. 设置能力
+5
View File
@@ -118,6 +118,11 @@
- 早期方案曾在 `AuthGate` 层提供右上角全局账号信息条,并在 `GameShellRuntime` 中临时隐藏。
- 2026-04-20 起,这个全局悬浮入口已整体下线,不再区分“平台显示 / 冒险隐藏”。
- 原因是右上角高频观察区不适合承载账号入口,且平台内已经有更明确的页面内入口。
## 9. 2026-05-08 创作首页通知入口下线
- `CreativeAgentHome` 顶栏右上角不再展示“通知与账户”按钮,避免创作首页把通知入口放在首屏高频区域。
- 账号入口仍保留在侧边栏底部,创作首页顶栏维持左侧菜单、居中品牌的轻量结构。
- 当前账号相关入口统一保留在平台首页受保护动作、个人页、存档页与账号弹窗,不再占用全局悬浮层。
---
@@ -468,8 +468,9 @@ interface CustomWorldCoverProfile {
1. 上传后先进入独立裁剪面板
2. 裁剪框比例固定为 `16:9`
3. 作者只能平移和缩放,不允许自由改比例
4. 裁剪完成后,再提交给后端保存
3. 作者直接在图片上拖拽裁剪框内部移动区域,拖拽四边或四角调整裁剪范围,不再通过参数滑杆调整
4. 裁剪框调整过程中必须持续锁定 `16:9`,不允许自由改比例
5. 裁剪完成后,再提交给后端保存
### 上传大小与格式限制
@@ -1536,6 +1536,14 @@ VN-10 实施收口记录(2026-05-07):
4. 运行时背景图与角色立绘改用 `ResolvedAssetImage`,私有 generated 路径通过 `/api/assets/read-url` 换签后渲染。
5. 本阶段未接入可选图片生成 action,保留既有 action 契约;未改 SpacetimeDB schema,未保存 Data URL、二进制对象或外部 R2 路径。
VN-10 音频生成补充(2026-05-08):
1. 场景编辑器在既有 `musicSrc` / `ambientSoundSrc` 槽位上接入音频生成,不新增视觉小说专属音频表或第二套资产系统。
2. 背景音乐走 VectorEngine Suno `POST /suno/submit/music``GET /suno/fetch/{task_id}`,完成后写入 `visual_novel_music` 平台资产并绑定到 `visual_novel_scene / music`
3. 场景音效走 VectorEngine Vidu `POST /ent/v2/text2audio``GET /ent/v2/tasks/{id}/creations`,完成后写入 `visual_novel_ambient_sound` 平台资产并绑定到 `visual_novel_scene / ambient_sound`
4. 前端只提交提示词、标题、标签、时长等生成参数;供应商 base URL 和 API key 只在 `api-server` 环境变量中读取。
5. 生成参数使用独立弹层承载,不在场景面板下方展开规则说明。
### VN-11:回放删除与外部平台功能负向扫描
负责范围:
@@ -37,7 +37,7 @@
## 5. 范围边界
1. 不增加跳过入口
1. 首屏右上角提供 `跳过` 入口,点击后写入首次访问标记并回到产品首页
2. 不定义额外功能说明文案。
3. 不扩展拼图为多关。
4. 不调整注册/登录后的去向,当前进入产品首页。
@@ -47,10 +47,12 @@
1. 未登录首次访问产品时,进入新手引导首屏。
2. 首屏展示确认文案、输入框和生成按钮。
3. 用户输入内容并点击生成后,系统生成 1 关拼图
4. 生成完成后,用户可以进入该拼图并完成第 1 关。
5. 第 1 关完成后,页面展示注册/登录引导文案和登录模块
6. 用户完成注册或登录后,进入产品首页
3. 首屏右上角展示跳过按钮;点击后本次和后续访问不再自动展示新手引导
4. 用户输入内容并点击生成后,系统生成 1 关拼图
5. 若临时生成接口返回 `404 / 资源不存在`,前端使用本地临时拼图兜底继续进入试玩,不把错误直接展示给用户
6. 生成完成后,用户可以进入该拼图并完成第 1 关
7. 第 1 关完成后,页面展示注册/登录引导文案和登录模块。
8. 用户完成注册或登录后,进入产品首页。
## 7. 落地接口与状态
@@ -59,3 +61,4 @@
3. 登录后保存入口为 `POST /api/runtime/puzzle/onboarding/save`,要求登录;服务端为当前用户创建拼图 agent session,并把临时 1 关拼图保存为当前用户作品草稿。
4. 新手引导游玩阶段复用现有本地拼图运行时,不新增 SpacetimeDB 表、reducer 或运行时真相。
5. 保存完成后清空新手引导临时态,刷新拼图作品架,并回到产品首页。
6. 跳过新手引导只更新本地首次访问标记和界面状态,不创建临时作品、不调用保存接口。
@@ -39,6 +39,22 @@ APIMART_BASE_URL=
APIMART_API_KEY=
APIMART_IMAGE_REQUEST_TIMEOUT_MS=180000
# VectorEngine / Suno / Vidu 音频生成网关
VECTOR_ENGINE_BASE_URL=
VECTOR_ENGINE_API_KEY=
VECTOR_ENGINE_AUDIO_REQUEST_TIMEOUT_MS=180000
# 火山引擎豆包语音 ASR / TTS
VOLCENGINE_SPEECH_API_KEY=
VOLCENGINE_SPEECH_APP_ID=
VOLCENGINE_SPEECH_ACCESS_KEY=
VOLCENGINE_SPEECH_ASR_RESOURCE_ID=volc.seedasr.sauc.concurrent
VOLCENGINE_SPEECH_TTS_RESOURCE_ID=seed-tts-2.0
VOLCENGINE_SPEECH_REQUEST_TIMEOUT_MS=180000
VOLCENGINE_SPEECH_ASR_WS_URL=wss://openspeech.bytedance.com/api/v3/sauc/bigmodel_async
VOLCENGINE_SPEECH_TTS_BIDIRECTION_WS_URL=wss://openspeech.bytedance.com/api/v3/tts/bidirection
VOLCENGINE_SPEECH_TTS_SSE_URL=https://openspeech.bytedance.com/api/v3/tts/unidirectional/sse
# DashScope 图片模型名
DASHSCOPE_SCENE_IMAGE_MODEL=
DASHSCOPE_REFERENCE_IMAGE_MODEL=
@@ -65,6 +81,9 @@ DASHSCOPE_COVER_IMAGE_MODEL / DASHSCOPE_IMAGE_MODEL
ARK_CHARACTER_VIDEO_BASE_URL / ARK_BASE_URL / GENARRATIVE_LLM_BASE_URL / LLM_BASE_URL
ARK_CHARACTER_VIDEO_API_KEY / ARK_API_KEY / GENARRATIVE_LLM_API_KEY / LLM_API_KEY
ARK_CHARACTER_VIDEO_MODEL / DASHSCOPE_CHARACTER_VIDEO_MODEL
VOLCENGINE_SPEECH_API_KEY / VOLCENGINE_API_KEY
VOLCENGINE_SPEECH_APP_ID / VOLCENGINE_ACCESS_KEY_ID
VOLCENGINE_SPEECH_ACCESS_KEY / VOLCENGINE_SECRET_ACCESS_KEY
```
## 运行时行为
@@ -74,6 +93,8 @@ ARK_CHARACTER_VIDEO_MODEL / DASHSCOPE_CHARACTER_VIDEO_MODEL
3. 文本 LLM provider 为 `ark` 且未配置 `GENARRATIVE_LLM_BASE_URL` 时,仍回退到 Ark 公开基础 URL。
4. 角色视频 provider 复用 Ark 且未配置 `ARK_CHARACTER_VIDEO_BASE_URL` 时,仍回退到 Ark 公开基础 URL。
5. 具体模型名缺失时不在配置层伪造默认模型,调用到对应能力时由下游配置校验返回缺配置错误。
6. VectorEngine 音频生成只读取 `VECTOR_ENGINE_BASE_URL` / `VECTOR_ENGINE_API_KEY`,不复用 `APIMART_*``GENARRATIVE_LLM_*` 或前端变量。
7. 火山引擎语音能力由 `platform-speech` 收口协议帧与上游鉴权,`api-server` 只暴露平台鉴权后的代理路由,不向前端返回任何密钥字段。
## 示例文件
@@ -135,4 +135,5 @@
2. 再请求当前 Rust API 目标,例如 `http://127.0.0.1:3100/api/auth/login-options``http://127.0.0.1:8082/api/auth/login-options`
3. 若直连 API 成功而 3000 返回 `500`,检查 `RUST_SERVER_TARGET``GENARRATIVE_API_TARGET``GENARRATIVE_RUNTIME_SERVER_TARGET` 是否指向仍在监听的 API 端口。
4. `npm run dev` / `npm run dev:rust` 完整栈默认由脚本计算 API 端口;加载 `.env.local` 给后端使用后,脚本必须重新固定 `RUST_SERVER_TARGET`,避免 `.env.local` 中的旧代理目标覆盖本次启动的实际 API 端口。
5. `npm run dev:web` 只启动前端,不会自动拉起 Rust API;如果单独使用它,必须同时确认其打印的 backend target 已有 `api-server` 正在监听
5. `npm run dev:web` 只启动前端,不会自动拉起 Rust API;如果单独使用它,脚本会先探测 `.env.local` / 当前环境里声明的目标,再回退到本机常见端口,最终只会接入一个真实可用的 `api-server`
6. 如果 `3000` 仍然返回 `500`,先确认浏览器是不是还开着旧的前端进程。当前脚本如果因为端口占用漂移到 `3001` / `3002`,应直接关掉旧进程后重启,而不是继续用旧的 3000 页面判断登录入口状态。
@@ -28,6 +28,7 @@
```text
POST https://api.apimart.ai/v1/responses
model: gpt-5
official_fallback: true
input[].content[].type: input_text | input_image
```
@@ -42,6 +43,7 @@ export interface CreativeAgentMultimodalInputPart {
export interface CreativeAgentGpt5Request {
model: 'gpt-5';
official_fallback: true;
input: Array<{
role: 'system' | 'user' | 'assistant';
content: CreativeAgentMultimodalInputPart[];
@@ -55,7 +57,7 @@ export interface CreativeAgentGpt5Request {
1. Agent 入口支持文本 + 图片多模态输入,首版至少支持 1 张图片,协议层预留多图。
2. 图片必须先进入资产系统,Agent 请求使用可访问的 `readUrl` 或受控 Data URISpacetimeDB 不保存大图 base64。
3. `platform-llm` 当前已有 Responses 协议骨架,但 Phase 1 需要把 content part 从纯文本扩展成 `input_text` / `input_image` 两类。
3. `platform-llm` 当前已有 Responses 协议骨架,但 Phase 1 需要把 content part 从纯文本扩展成 `input_text` / `input_image` 两类APIMart GPT-5 client 必须显式开启 `official_fallback = true`,该供应商字段不默认扩散到 Ark 等非 APIMart provider
4. `CREATION_TEMPLATE_LLM_MODEL` 等旧文本创作模型不能作为创意互动内容 Agent 的默认模型;本 Agent 必须显式使用 `gpt-5`
5. 如果 LangChain-Rust adapter 暂时无法直接表达多模态 Responses 请求,应在 `platform-agent` 内桥接到 `platform-llm` 的多模态 Responses client,不能退回纯文本摘要替代图片理解。
6. 模型工具调用可用 APIMart Responses 的 `tools` 能力承载;工具真正执行仍由 `platform-agent` 注册表和后端 typed Tool 控制。
@@ -108,6 +108,12 @@ Node 旧链路对上传封面有明确处理:
Rust 本批必须保持这组兼容约束。
2026-05-08 交互补充:
1. 前端裁剪面板不再展示 `缩放 / 左右位置 / 上下位置` 参数滑杆。
2. 作者直接在图片上拖拽裁剪框内部移动区域,或拖拽四边、四角调整裁剪范围。
3. 调整过程中前端持续锁定 `16:9`,确认时仍只提交后端兼容的 `cropRect`
## 4. 请求与响应 contract
### 4.1 `POST /api/custom-world/scene-image`
@@ -122,6 +122,12 @@
2. AI worker 绕过确认链路写出不完整记录
3. 把 OSS 响应中的派生 URL 当成对象真相
### 5.1 OSS V4 签名时间格式
`platform-oss` 的 OSS V4 签名时间必须显式格式化为 `YYYYMMDDTHHMMSSZ`,签名 scope 日期必须显式格式化为 `YYYYMMDD`。实现中不要依赖 `OffsetDateTime::time().to_string()``date().to_string()` 再替换字符,因为 UTC 小时、分钟、秒为个位数时可能不会保留前导零,导致 AI 生图已完成但上传 OSS 阶段报 `OSS V4 签名时间格式化失败`
拼图、视觉小说、自定义世界等所有服务端生成图片上传链路都复用同一套 `platform-oss` 签名 helper;新增签名逻辑时必须覆盖个位时间分量的测试样例,例如 `05:03:09` 应输出 `T050309Z`
## 6. 与 Web 端的边界
Web 端当前只允许:
@@ -9,7 +9,7 @@
1. `https://docs.apimart.ai/cn/api-reference/images/gpt-image-2/generation`
2. `https://docs.apimart.ai/cn/api-reference/images/gemini-3.1-flash/generation`
两条文档均指向 OpenAI 兼容风格的图片生成入口:`POST https://api.apimart.ai/v1/images/generations`,头部使用 `Authorization: Bearer {APIMART_API_KEY}`。请求体至少包含 `model``prompt``n``size`。返回体按 OpenAI images 兼容格式优先读取 `data[].url`,若供应商返回异步任务结构,则继续按 `task_id` / `tasks/{task_id}` 轮询并提取图片 URL。
两条文档均指向 OpenAI 兼容风格的图片生成入口:`POST https://api.apimart.ai/v1/images/generations`,头部使用 `Authorization: Bearer {APIMART_API_KEY}`。请求体至少包含 `model``prompt``n``official_fallback = true``size`。返回体按 OpenAI images 兼容格式优先读取 `data[].url`,若供应商返回异步任务结构,则继续按 `task_id` / `tasks/{task_id}` 轮询并提取图片 URL。
## 模型选项
@@ -43,7 +43,7 @@
- `gpt-image-2` 走 APIMart
- `gemini-3.1-flash-image-preview` 走 APIMart,前端显示名为 `nanobanana2`
4. APIMart 文生图和图生图共用 `POST /v1/images/generations`。有参考图时,后端将参考图 Data URL 作为 `image_urls` 数组传入;若上游不接受该字段,错误按上游失败返回,不在前端降级伪造结果。
5. APIMart 尺寸使用文档要求的比例写法 `1:1``gemini-3.1-flash-image-preview` 额外带 `resolution = "1K"`,对齐约 1024px 的拼图正方形素材。
5. APIMart 尺寸使用文档要求的比例写法 `1:1`,所有 APIMart 图片请求体固定携带 `official_fallback = true``gemini-3.1-flash-image-preview` 额外带 `resolution = "1K"`,对齐约 1024px 的拼图正方形素材。
6. APIMart 生成成功后仍下载远程图片,沿用现有 OSS 私有对象、`asset_object``asset_entity_binding` 写入流程。若图片已成功上传 OSS,但 Maincloud / SpacetimeDB 短暂返回 `503 Service Unavailable`,资产索引写入允许降级跳过,并返回本次生成图片;日志必须记录 `拼图图片资产索引写入因 SpacetimeDB 连接不可用而降级跳过`
7. `save_puzzle_generated_images` 写回草稿时若遇到 Maincloud 连接级 `503` 或断线,API 层基于本次生成结果合成 session 快照返回给前端,避免 APIMart 已成功出图却被后置持久化误报成服务不可用。余额不足、参数错误、上游生图失败仍按原错误返回,不做伪成功。
8. 结果页 `generate_puzzle_images` 会携带当前作品信息和 `levelsJson`。当 Maincloud / SpacetimeDB 在读取 session 阶段就返回连接级 `503` 或断线时,后端必须先用这份结果页快照构造最小内存 session,再继续调用 APIMart;外部图片已经生成后仍按第 6、7 条处理持久化降级。余额不足、参数错误、缺少草稿快照、关卡不存在等业务错误不走此降级。
@@ -51,6 +51,14 @@
10. APIMart 错误统一映射为 `502 UPSTREAM_ERROR``details.provider = "apimart"`,保留上游状态码、业务 message 和截断后的 raw excerpt。
11. 拼图首图生成 `compile_puzzle_draft` 与关卡图片生成 `generate_puzzle_images` 每次预扣 `2` 光点;余额不足仍返回 `409 CONFLICT`Maincloud 连接级 503 仍按既有降级策略处理。
## 关卡名多模态生成
1. 第一关和结果页关卡重新生图的最终关卡名统一由 APIMart Chat Completions `gpt-4o-mini` 生成。
2. 输入必须同时包含生成完成后的正式图片和当前关卡 `pictureDescription`;图片由 `api-server` 从生成结果字节压缩为最多 768 边长的 PNG Data URL 后,以 OpenAI 兼容 `messages[].content[]``image_url` 形式传入。
3. 文本模型仍只输出 `{"levelName":"..."}`,并继续复用现有 2 到 8 个中文字符、禁用“画面 / 拼图 / 作品”等泛词的解析与归一化规则。
4. `gpt-4o-mini` 调用失败、返回非法或 APIMart 文本配置缺失时,不阻断图片生成;后端保留图片生成前的文本关卡名或确定性兜底名。
5. 关卡名与候选图在同一次 `save_puzzle_generated_images` 中写回 `levels_json` 和正式候选图,避免图片与关卡名不同步。
## 环境变量
新增服务端环境变量:
@@ -69,7 +77,8 @@ APIMART_IMAGE_REQUEST_TIMEOUT_MS=180000
2. 点击“生成草稿”时,后端首图生成使用当前表单选择的模型。
3. 点击“生成画面 / 重新生成画面”时,后端当前关卡图片生成使用关卡详情选择的模型。
4. 历史 `original` 或空模型值不会再触发 DashScope,统一按 `gpt-image-2` 请求 APIMart。
5. 选择 APIMart 模型时,请求 `POST {APIMART_BASE_URL}/images/generations`,使用 `Authorization: Bearer {APIMART_API_KEY}``model` 等于请求值,`size = 1:1`
6. “生成草稿”和关卡详情生图按钮展示 `消耗2光点`;关卡详情确认后展示 30 秒预计剩余进度条
7. 不改 SpacetimeDB 表结构,因此无需更新 `migration.rs` 或重新生成 bindings
8. 后端改动后运行对应 Rust 测试,并按项目约束用 `npm run api-server` 重启验证
5. 选择 APIMart 模型时,请求 `POST {APIMART_BASE_URL}/images/generations`,使用 `Authorization: Bearer {APIMART_API_KEY}``model` 等于请求值,`official_fallback = true``size = 1:1`
6. 首图和结果页关卡重新生图成功后,Network 中应先完成图片生成,再调用 APIMart `POST {APIMART_BASE_URL}/chat/completions`,请求模型为 `gpt-4o-mini`,消息同时包含画面描述文本和正式图 `image_url` Data URL
7. “生成草稿”和关卡详情生图按钮展示 `消耗2光点`;关卡详情确认后展示 30 秒预计剩余进度条
8. 不改 SpacetimeDB 表结构,因此无需更新 `migration.rs` 或重新生成 bindings
9. 后端改动后运行对应 Rust 测试,并按项目约束用 `npm run api-server` 重启验证。
@@ -1,31 +1,40 @@
# 拼图生成图片资源代理修复
# 拼图生成图片读取链路修复
日期:`2026-04-27`
更新:`2026-05-08`
## 背景
拼图结果页的“生成或更换图片”会在 `api-server` 中调用 DashScope 生成图片,再把候选图上传到 OSS,最终以 `/generated-puzzle-assets/...` 旧兼容路径写回 `PuzzleGeneratedImageCandidate.image_src` 与草稿封面字段。
本次排查发现拼图图片写入路径已经进入 `platform-oss::LegacyAssetPrefix::PuzzleAssets`,但后端 Axum 旧资源代理和 Vite 本地代理没有挂载 `/generated-puzzle-assets`这会导致候选图或正式图无法读取;后续如果把已有候选图作为参考图继续更换图片,也会让参考图读取链路失效
历史排查发现拼图图片写入路径已经进入 `platform-oss::LegacyAssetPrefix::PuzzleAssets`,但后端 Axum 旧资源代理和 Vite 本地代理没有挂载 `/generated-puzzle-assets`当时的处理口径是补旧资源代理
当前 `WP-DEL` 后,旧 `/generated-*` 直读代理已经物理删除;`/generated-puzzle-assets/...` 只允许作为 `legacyPublicPath` / OSS object key 标识。浏览器不能再直接请求该路径,必须通过 `/api/assets/read-url?legacyPublicPath=...` 换取短期签名 URL 后预览。
## 修复口径
1. `server-rs/crates/api-server/src/legacy_generated_assets.rs` 增加 `proxy_generated_puzzle_assets(...)`,复用统一的 OSS 签名读取逻辑
2. `server-rs/crates/api-server/src/app.rs` 挂载 `/generated-puzzle-assets/{*path}`,与角色、大鱼、自定义世界图片资源前缀保持一致
3. `vite.config.ts` 增加 `/generated-puzzle-assets` dev proxy,保证本地网页端不会因为 Vite 代理缺口读不到后端资源
1. `platform-oss::LEGACY_PUBLIC_PREFIXES` 必须包含 `generated-puzzle-assets`,保持直传票据、服务端上传、read-url 支持列表和错误提示同一口径
2. `src/services/assetReadUrlService.ts``isGeneratedLegacyPath(...)` 需要同时识别 `/generated-puzzle-assets/...``generated-puzzle-assets/...`。后者可能来自 object key 形态的历史字段
3. `ResolvedAssetImage` / `useResolvedAssetReadUrl` 在签名 URL 返回前不能把裸 `/generated-*``generated-*` 写进 `<img src>`
4. `refreshKey` 只能用于跳过前端签名 URL 缓存并重新请求 `/api/assets/read-url`,不能再给 OSS V4 签名 URL 追加 `_v` 等额外 queryOSS 会把 query 纳入签名,额外参数会让新生成图变成 403/破图。
5. 历史素材被选为参考图后,参考图小预览也必须走 `ResolvedAssetImage`,不能使用裸 `<img src="/generated-*">`
6. 禁止恢复 `/generated-puzzle-assets/{*path}` Axum 路由或 Vite 直读代理;正式读取统一走 `/api/assets/read-url`
## 后续约束
1. 任何新增 `LegacyAssetPrefix` 都必须同时检查:
- `platform-oss` 前缀枚举
- `api-server` 旧资源代理路由
- Vite dev proxy
- `platform-oss::LEGACY_PUBLIC_PREFIXES`
- `/api/assets/read-url` 入参解析
- 前端 `isGeneratedLegacyPath(...)` 是否能识别
2. 拼图候选图 JSON 仍保持 SpacetimeDB 持久化结构 `PuzzleGeneratedImageCandidate` 的 snake_case 字段,不把 HTTP camelCase 响应结构写入 `draft_json`
3. 图片生成、OSS 读写和外部参考图解析继续留在 `api-server`,不能下沉到 SpacetimeDB reducer。
4. 如果图片组件需要刷新 generated 私有资源,优先让 `refreshKey` 触发重新换签;不要修改已返回的 `signedUrl`
## 验收
1. `npm run check:encoding`
2. `cargo check -p api-server --manifest-path server-rs/Cargo.toml`
3. `npm run api-server` 重启后,点击拼图结果页“生成或更换图片”,候选图应能写回并正常展示。
1. `npm run test -- src\services\assetReadUrlService.test.ts src\hooks\useResolvedAssetReadUrl.test.tsx src\components\puzzle-result\PuzzleResultView.test.tsx`
2. `cargo test -p platform-oss --manifest-path server-rs\Cargo.toml`
3. `npm run check:encoding`
4. `npm run api-server` 重启后检查 `/healthz`,再点击拼图结果页“生成或更换图片”,候选图应能写回并正常展示。
@@ -19,10 +19,18 @@
3. `puzzle-select-image` 展示为“写入正式草稿”:把首图设为第一关正式图,并同步到结果页草稿。
4. `ready` 文案提示进入结果页补作品信息;不得暗示作品名称、作品描述或作品标签已经完整生成。
### 2026-05-08 进度页预计等待与步骤动效补充
1. 拼图草稿生成进度页预计等待时间固定按 `60` 秒展示和倒计时,后端真实完成后立即进入结果页,不强制等满 60 秒。
2. 60 秒进度拆成三段:`compile` 约 12 秒,`puzzle-images` 约 42 秒,`puzzle-select-image` 约 6 秒。
3. 生成中即使后端 `compile_puzzle_draft` 仍是一次同步 action,前端也必须按本地计时推进总进度和当前步骤进度,避免页面停在静态等待态。
4. 每个步骤卡片都展示独立进度条;已完成步骤显示 100%,当前步骤按该段预计时长推进,后续步骤保留 0% 待处理状态。
5. 后端未返回前总进度最多推进到 98%,防止 UI 提前宣称生成完成;只有 action 成功并写回 `ready` 后才显示 100%。
## 草稿默认值
1. 后端先由 `module-puzzle` 生成可回滚的确定性草稿,再由 `api-server` 基于画面描述调用文本模型生成第一关关卡名;模型不可用或返回非法时才降级到确定性兜底名。
2. 第一关关卡名生成后,必须写回首关 `levelName`,并在入口直创默认场景下作为 `workTitle` 同步写入草稿和作品草稿卡。
1. 后端先由 `module-puzzle` 生成可回滚的确定性草稿,再由 `api-server` 生成第一关关卡名。图片生成前可先基于画面描述生成临时关卡名;正式图片生成完成后,必须使用 APIMart Chat Completions 的 `gpt-4o-mini`,把正式图片 data URL 与画面描述一起传入模型,生成最终关卡名。
2. 最终关卡名生成后,必须写回首关 `levelName`,并在入口直创默认场景下作为 `workTitle` 同步写入草稿和作品草稿卡;模型不可用、图片压缩失败或返回非法时,才保留前一步文本名或确定性兜底名
3. `workDescription` 默认保持空字符串,不再回退为画面描述。
4. `themeTags` 默认保持空数组,不再由入口画面描述自动推断为正式作品标签。
5. `formDraft` 只保留 `pictureDescription``workTitle``workDescription` 为空。
@@ -48,7 +48,7 @@
不能继续写到仓库本地 `public/generated-puzzle-covers/*`
这些路径只是前后端 DTO 里的兼容标识,不是浏览器可以直接裸读的公开资源地址。实际图片对象存放在私有 OSS 中,前端渲染前必须先通过 `/api/assets/read-url?legacyPublicPath=...` 换取签名读 URL;签名 URL 未返回或换签失败时,图片组件不能把 `/generated-puzzle-assets/*` 直接写入 `<img src>`,避免浏览器发起无签名、无鉴权请求。
这些路径只是前后端 DTO 里的兼容标识,不是浏览器可以直接裸读的公开资源地址。实际图片对象存放在私有 OSS 中,前端渲染前必须先通过 `/api/assets/read-url?legacyPublicPath=...` 换取签名读 URL;签名 URL 未返回或换签失败时,图片组件不能把 `/generated-puzzle-assets/*` 或无前导斜杠的 `generated-puzzle-assets/*` 直接写入 `<img src>`,避免浏览器发起无签名、无鉴权请求。
### 4.2 运行态边界
@@ -32,14 +32,18 @@
- 上传区自身就是图片卡片,不再额外封装为 `platform-subpanel` 模块壳。
- 亮色主题下上传卡片必须使用白色或暖浅色卡面,不得显示整块黑色底。
- 上传卡片固定为 1:1 正方形,避免拼图主画面在首屏出现非正方形预期。
- 上传卡片底部不再叠加文件名 bar;卡片下方只保留 `点击上传拼图图片` 纯文字入口
- 移动端表单主体不可依赖纵向拖动查看核心控件;玩法卡带、描述输入框和底部生成按钮占位固定后,上传卡片必须按剩余高度等比例缩放,仍保持 1:1
- 上传卡片底部不再叠加文件名 bar`点击上传拼图图片` 入口必须显示在拼图画面卡片内部。
- 上传卡片上方固定展示 `拼图画面` 标题。
- 叠在上传卡片上的 `AI重绘` 和图标必须和卡面保持足够对比,避免浅色主题重映射后不可读
3. 画面描述输入框高度约为旧版大输入框的 1/2,避免移动端把上传参考图和提交区挤出首屏
4. 输入区保留:
- 无图状态下,上传卡片内部、`点击上传拼图图片` 按钮上方展示 11px 级辅助提示 `若没有合适的图片可以通过填写画面描述生成画面`,提示用户可不上传图片、直接填写画面描述生成画面
- 上传成功后,`AI重绘` 开关显示在卡片左下角,右上角显示移除拼图图片图标按钮;移除必须先弹出二次确认
- 叠在上传卡片上的 `AI重绘`、移除图标和上传入口必须和卡面保持足够对比,避免浅色主题重映射后不可读。
3. 画面描述输入框高度固定,移动端保持约 `6rem`,不随剩余屏幕高度变大或变小,避免把上传参考图和提交区挤出首屏。
4. 创作 Tab 顶部玩法卡带的选中态只使用卡内暗色蒙版、细描边或内描边,不使用粉色外发光、外扩阴影或会从卡片边缘突出的高饱和边。
5. 输入区保留:
- 上传拼图图片按钮。
- 图片模型切换按钮。
5. 输入区不保留:
6. 输入区不保留:
- `try` 文本。
- 示例 prompt chip。
- 画面描述输入框默认提示词或占位示例。
@@ -73,8 +77,9 @@ Skill 封装仓库现有后端口径:
POST {APIMART_BASE_URL}/images/generations
Authorization: Bearer {APIMART_API_KEY}
model = gpt-image-2
size = 1:1
n = 1
official_fallback = true
size = 1:1
```
响应兼容:
@@ -87,7 +92,7 @@ n = 1
## 2026-05-07 AI 重绘与上传直用
拼图入口上传区右上角新增 `AI重绘` 开关,默认打开;未上传拼图图片前不显示开关,上传成功后才显示。
拼图入口上传区左下角展示 `AI重绘` 开关,默认打开;未上传拼图图片前不显示开关,上传成功后才显示。上传成功后右上角展示移除图标按钮,点击后必须二次确认。
1. `AI重绘=true`
- 上传区文案为 `点击上传拼图图片`,上传图作为生图参考图。
@@ -104,6 +109,7 @@ n = 1
3. 上传裁剪
- 前端读取上传图原始宽高。
- 非 1:1 图片必须先弹出正方形裁剪工具,裁剪完成后再进入表单状态和提交 payload。
- 裁剪工具必须在完整原图上展示正方形裁剪框,支持拖拽框内区域移动,以及拖拽四边或四角调整裁剪边界,不再展示 `缩放 / 横向 / 纵向` 参数滑杆。
- 裁剪输出仍按参考图体积预算压缩,避免上传图撑爆 JSON body。
契约字段同步:
@@ -117,13 +123,13 @@ Rust 共享契约使用 `ai_redraw: Option<bool>` 并按 camelCase 序列化为
## 验收
1. 点击拼图创作后,表单首屏呈现大参考图区和半高文本输入框
1. 点击拼图创作后,移动端表单无需纵向拖动即可看到大参考图区、固定高度文本输入框和 `生成拼图游戏草稿` 按钮
2. 输入框里没有 `try` 示例功能。
3. 图片模型切换仍可打开并选择 `gpt-image-2` / `nanobanana2`
4. 历史模板样例图文件可保留,但不出现在拼图入口表单。
5. 当前创作 Tab 顶部的拼图、方洞挑战、视觉小说和 AIRP 卡片能看到对应 `creation-type-references` 图片。
6. 默认 `AI重绘` 打开时,无图状态展示 `画面描述``消耗2光点`;上传图片后输入框标题改为 `画面AI重绘要求(提示词)`
7. 关闭 `AI重绘` 后隐藏画面描述输入框,生成按钮不展示 `消耗2光点`,后端直接应用上传图片为第一关图片。
8. 上传非 1:1 图片时必须先完成正方形裁剪。
8. 上传非 1:1 图片时必须先通过拖拽裁剪框完成正方形裁剪。
9. gpt-image-2 Skill 校验通过,且脚本 dry-run 能输出计划请求而不泄露密钥。
10. `npm run check:encoding` 通过。
+2
View File
@@ -5,6 +5,8 @@
## 文档列表
- [RUST_WORKSPACE_DEPENDENCY_CONSOLIDATION_2026-05-07.md](./RUST_WORKSPACE_DEPENDENCY_CONSOLIDATION_2026-05-07.md):记录 `server-rs` Cargo 依赖集中配置口径,第三方版本和 workspace 内部 crate path 统一维护在根 `server-rs/Cargo.toml`,成员 crate 只保留 feature/optional 差异。
- [VOLCENGINE_SPEECH_STREAMING_INTEGRATION_2026-05-08.md](./VOLCENGINE_SPEECH_STREAMING_INTEGRATION_2026-05-08.md):记录火山引擎大模型 ASR 双向流式、TTS WebSocket 双向流式和 TTS HTTP SSE 单向流式的后端代理、环境变量、协议帧和验收边界。
- [VECTOR_ENGINE_AUDIO_GENERATION_SUNO_VIDU_2026-05-08.md](./VECTOR_ENGINE_AUDIO_GENERATION_SUNO_VIDU_2026-05-08.md):记录视觉小说结果页接入 VectorEngine Suno 文生背景音乐与 Vidu 文生音效的接口、环境变量、后端路由、OSS 资产回写和前端弹层交互边界。
- [API_SERVER_EXTERNAL_SERVICE_ENV_CONFIG_2026-05-07.md](./API_SERVER_EXTERNAL_SERVICE_ENV_CONFIG_2026-05-07.md):冻结 api-server 外部服务配置边界,公共服务 URL 可保留代码默认值,非公共模型名和私有网关 URL 统一通过环境变量注入。
- [CREATIVE_INTERACTIVE_CONTENT_AGENT_TECHNICAL_SOLUTION_2026-05-05.md](./CREATIVE_INTERACTIVE_CONTENT_AGENT_TECHNICAL_SOLUTION_2026-05-05.md):冻结基于 LangChain-Rust 的创意互动内容生成 Agent 技术方案,明确首版只支持拼图模板、必须显式展示模板选择和积分范围,通过拼图模块 Tool/模板协议填充同一份草稿字段,支持单关卡与多关卡图片生成、立即试玩、表单化编辑和 Agent 自然语言修订草稿字段。
- [VISUAL_NOVEL_PROMPT_AND_LLM_TOOLS_VN03_2026-05-05.md](./VISUAL_NOVEL_PROMPT_AND_LLM_TOOLS_VN03_2026-05-05.md):记录视觉小说模板 `VN-03` Prompt / LLM 工具落地,包含创作底稿 Prompt、运行时 GM Prompt、repair Prompt、工具参数 schema、Responses 请求口径和定向验证结果。
@@ -36,8 +36,9 @@ model = gpt-image-2
1. `model`
2. `prompt`
3. `n`
4. `size`
5. 有参考图时增加 `image_urls`
4. `official_fallback = true`
5. `size`
6. 有参考图时增加 `image_urls`
尺寸归一规则:
@@ -67,8 +68,8 @@ APIMART_IMAGE_REQUEST_TIMEOUT_MS=180000
## 验收
1. 角色主图生成请求上游 `model``gpt-image-2`
2. 场景图生成请求上游 `model``gpt-image-2`
1. 角色主图生成请求上游 `model``gpt-image-2`,且携带 `official_fallback = true`
2. 场景图生成请求上游 `model``gpt-image-2`,且携带 `official_fallback = true`
3. 旧前端或历史草稿传 `wan2.7-image-pro` 时不会回退旧模型。
4. 场景参考图生成仍能把参考图 Data URL 放入 `image_urls`
5. 角色主图生成后仍执行原有 PNG 透明背景处理与 OSS 写入。
@@ -0,0 +1,156 @@
# VectorEngine 音频生成接入方案 2026-05-08
## 1. 范围
本方案用于把 VectorEngine / Apifox 文档中的 Suno 文生背景音乐与 Vidu 文生音效接入视觉小说结果页。
本次只接入 `visual-novel` 现有场景资产槽位,不新增独立音频系统、不新增 SpacetimeDB 表、不把供应商密钥下发到前端。
## 2. 参考接口
### 2.1 Suno 文生背景音乐
参考文档:
- `https://vectorengine.apifox.cn/api-349239190`
- `https://vectorengine.apifox.cn/api-349239199`
接口:
```text
POST /suno/submit/music
GET /suno/fetch/{task_id}
```
提交请求头:
```text
Content-Type: application/json
Accept: application/json
Authorization: Bearer {VECTOR_ENGINE_API_KEY}
```
自定义模式请求体:
```json
{
"prompt": "音乐描述或歌词",
"mv": "chirp-v4",
"title": "曲名",
"tags": "风格标签",
"continue_at": 120,
"continue_clip_id": "",
"task": ""
}
```
首版只使用 `prompt``mv``title``tags`。返回体按 `code = success``data` 为供应商任务 ID 处理。
### 2.2 Vidu 文生音效
参考文档:
- `https://vectorengine.apifox.cn/api-417728889`
- `https://vectorengine.apifox.cn/api-417728893`
接口:
```text
POST /ent/v2/text2audio
GET /ent/v2/tasks/{id}/creations
```
提交请求体:
```json
{
"model": "audio1.0",
"prompt": "雨滴落在窗户上的声音,伴随着轻柔的雷声",
"duration": 5,
"seed": 0
}
```
约束:
- `prompt` 最长 1500 字符。
- `duration` 范围为 2 到 10 秒,默认 5 秒。
- `model` 首版固定为 `audio1.0`
## 3. 环境变量
```text
VECTOR_ENGINE_BASE_URL=
VECTOR_ENGINE_API_KEY=
VECTOR_ENGINE_AUDIO_REQUEST_TIMEOUT_MS=180000
```
说明:
1. `VECTOR_ENGINE_BASE_URL` 只保存供应商代理 API 基础地址,不在代码中写死私有网关。
2. `VECTOR_ENGINE_API_KEY` 只能进入本地或生产私密环境文件,不提交到 Git。
3. 缺少任一必配项时,后端返回 `503 SERVICE_UNAVAILABLE`,前端沿用现有错误展示。
## 4. 后端路由
视觉小说创作链新增 4 个鉴权路由:
| 方法 | 路由 | 用途 |
| --- | --- | --- |
| `POST` | `/api/creation/visual-novel/audio/background-music` | 提交 Suno 背景音乐任务 |
| `POST` | `/api/creation/visual-novel/audio/background-music/{task_id}/asset` | 查询 Suno 任务,完成后下载并写入平台资产 |
| `POST` | `/api/creation/visual-novel/audio/sound-effect` | 提交 Vidu 音效任务 |
| `POST` | `/api/creation/visual-novel/audio/sound-effect/{task_id}/asset` | 查询 Vidu 任务,完成后下载并写入平台资产 |
生成资产回包写入既有视觉小说字段:
- Suno 背景音乐:`VisualNovelSceneDraft.musicSrc`
- Vidu 文生音效:`VisualNovelSceneDraft.ambientSoundSrc`
## 5. 资产落点
音频文件由后端下载后通过 `OssClient::put_object` 写入平台 OSS,并确认 `asset_object``asset_entity_binding`
对象规划:
| 类型 | `assetKind` | `entityKind` | `slot` | 旧路径前缀 |
| --- | --- | --- | --- | --- |
| 背景音乐 | `visual_novel_music` | `visual_novel_scene` | `music` | `generated-custom-world-scenes` |
| 场景音效 | `visual_novel_ambient_sound` | `visual_novel_scene` | `ambient_sound` | `generated-custom-world-scenes` |
确认后的 `audioSrc` 使用 OSS 返回的 legacy public path,继续由前端 `resolveAssetReadUrl` 换签播放。
## 6. 前端交互
视觉小说场景编辑弹层新增两类音频能力:
1. `音乐` 保留上传能力,并新增 Suno 生成按钮。
2. `音效` 使用 `ambientSoundSrc`,支持上传和 Vidu 生成。
交互要求:
1. 生成参数放在独立弹层中,不在当前场景面板下方展开。
2. 弹层只保留必要字段、提交、关闭和状态反馈,不展示供应商规则说明。
3. 任务提交成功后前端轮询资产接口;若供应商仍在处理,保持弹层状态。
4. 资产生成完成后自动写回当前场景字段。
## 7. 验收
建议执行:
```bash
npm run check:encoding
npm run typecheck
cd server-rs
cargo test -p shared-contracts visual_novel
cargo check -p api-server
```
涉及真实 API smoke 时:
1. 只在本地私密环境设置 `VECTOR_ENGINE_API_KEY`
2. 使用 `npm run api-server` 重启后端。
3. 确认 `/healthz`
4. 在视觉小说结果页提交背景音乐或音效生成,生成完成后确认场景音频槽位可播放。

Some files were not shown because too many files have changed in this diff Show More