1
This commit is contained in:
@@ -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. 设置能力
|
||||
|
||||
|
||||
@@ -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 URI;SpacetimeDB 不保存大图 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` 等额外 query;OSS 会把 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` 通过。
|
||||
|
||||
@@ -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. 在视觉小说结果页提交背景音乐或音效生成,生成完成后确认场景音频槽位可播放。
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
# 火山引擎大模型语音流式接入 2026-05-08
|
||||
|
||||
## 背景
|
||||
|
||||
本次接入火山引擎豆包语音能力,覆盖两类运行态语音链路:
|
||||
|
||||
1. 大模型流式语音识别 ASR,使用 WebSocket 双向流式优化模式。
|
||||
2. 大模型语音合成 TTS,使用实时交互场景的 WebSocket 双向流式接口,并提供一次性文本输入的 HTTP SSE 单向流式接口。
|
||||
|
||||
语音能力属于外部副作用,按 server-rs DDD 分层落在 `platform-speech`;`api-server` 只负责平台账号鉴权、环境配置校验、协议代理和错误映射。前端不得直接持有火山引擎密钥。
|
||||
|
||||
## 官方文档依据
|
||||
|
||||
- ASR:`https://www.volcengine.com/docs/6561/1354869?lang=zh`
|
||||
- TTS WebSocket 双向流式:`https://www.volcengine.com/docs/6561/1329505?lang=zh`
|
||||
- TTS WebSocket 单向流式:`https://www.volcengine.com/docs/6561/1719100?lang=zh`
|
||||
- TTS HTTP Chunked / SSE 单向流式:`https://www.volcengine.com/docs/6561/1598757?lang=zh`
|
||||
|
||||
## 环境变量
|
||||
|
||||
真实值只能放在本地未提交的 `.env.local` / `.env.secrets.local` 或生产服务器环境文件,禁止提交到仓库。
|
||||
|
||||
```text
|
||||
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
|
||||
```
|
||||
|
||||
配置规则:
|
||||
|
||||
1. 优先使用新版控制台 `VOLCENGINE_SPEECH_API_KEY`,上游请求头写 `X-Api-Key`。
|
||||
2. 若只配置旧版控制台信息,则使用 `VOLCENGINE_SPEECH_APP_ID` 和 `VOLCENGINE_SPEECH_ACCESS_KEY`。
|
||||
3. ASR 默认资源 ID 选 ASR 2.0 并发版;如账号是小时版,部署时改成 `volc.seedasr.sauc.duration`。
|
||||
4. TTS 默认资源 ID 选 `seed-tts-2.0`;旧音色或 1.0 计费资源由部署环境覆盖。
|
||||
|
||||
## ASR 协议边界
|
||||
|
||||
客户端连接:
|
||||
|
||||
```text
|
||||
GET /api/speech/volcengine/asr/stream
|
||||
Authorization: Bearer <Genarrative JWT>
|
||||
```
|
||||
|
||||
浏览器与 `api-server` 使用 WebSocket 二进制帧透传:
|
||||
|
||||
1. 首包必须是 JSON 文本,表示 ASR full client request 的业务参数。
|
||||
2. 后续二进制帧是音频分片。
|
||||
3. 浏览器发送文本帧 `{"type":"finish"}` 时,后端把最后一个空音频包按负包发送给火山。
|
||||
4. 后端把火山 full server response 解析成 JSON 文本帧发回浏览器。
|
||||
|
||||
ASR 上游连接:
|
||||
|
||||
```text
|
||||
wss://openspeech.bytedance.com/api/v3/sauc/bigmodel_async
|
||||
X-Api-Key: <VOLCENGINE_SPEECH_API_KEY>
|
||||
X-Api-Resource-Id: <VOLCENGINE_SPEECH_ASR_RESOURCE_ID>
|
||||
X-Api-Request-Id: <uuid>
|
||||
X-Api-Sequence: -1
|
||||
```
|
||||
|
||||
ASR 二进制协议:
|
||||
|
||||
1. 4 字节 header,大端整数。
|
||||
2. full client request:message type `0b0001`,JSON 序列化,gzip 压缩。
|
||||
3. audio only request:message type `0b0010`,raw payload,gzip 压缩。
|
||||
4. 最后一包音频使用 flags `0b0010`。
|
||||
5. full server response:message type `0b1001`,payload 为 gzip JSON。
|
||||
6. error response:message type `0b1111`,payload 为错误 JSON 或 UTF-8 文本。
|
||||
|
||||
首包参数由前端传入,但后端会兜底:
|
||||
|
||||
```json
|
||||
{
|
||||
"user": { "uid": "current-user-id" },
|
||||
"audio": {
|
||||
"format": "pcm",
|
||||
"codec": "raw",
|
||||
"rate": 16000,
|
||||
"bits": 16,
|
||||
"channel": 1
|
||||
},
|
||||
"request": {
|
||||
"model_name": "bigmodel",
|
||||
"enable_itn": true,
|
||||
"enable_punc": true,
|
||||
"show_utterances": true,
|
||||
"result_type": "full"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## TTS 协议边界
|
||||
|
||||
### WebSocket 双向流式
|
||||
|
||||
客户端连接:
|
||||
|
||||
```text
|
||||
GET /api/speech/volcengine/tts/bidirection
|
||||
Authorization: Bearer <Genarrative JWT>
|
||||
```
|
||||
|
||||
浏览器向后端发送 JSON 文本帧:
|
||||
|
||||
```json
|
||||
{ "type": "start_connection" }
|
||||
{ "type": "start_session", "sessionId": "...", "payload": { "user": {}, "req_params": {} } }
|
||||
{ "type": "task_request", "sessionId": "...", "payload": { "req_params": { "text": "..." } } }
|
||||
{ "type": "finish_session", "sessionId": "..." }
|
||||
{ "type": "finish_connection" }
|
||||
```
|
||||
|
||||
后端转成火山 WebSocket V3 二进制帧,并把上游返回帧统一解析成 JSON 文本或音频二进制帧回传浏览器。
|
||||
|
||||
TTS 双向上游连接:
|
||||
|
||||
```text
|
||||
wss://openspeech.bytedance.com/api/v3/tts/bidirection
|
||||
X-Api-Key: <VOLCENGINE_SPEECH_API_KEY>
|
||||
X-Api-Resource-Id: <VOLCENGINE_SPEECH_TTS_RESOURCE_ID>
|
||||
X-Api-Connect-Id: <uuid>
|
||||
```
|
||||
|
||||
V3 事件帧:
|
||||
|
||||
1. Full-client request + event number 用于 `StartConnection`、`StartSession`、`TaskRequest`、`FinishSession`、`FinishConnection`。
|
||||
2. Full-server response + event number 用于 `ConnectionStarted`、`SessionStarted`、`SessionFinished` 等状态事件。
|
||||
3. Audio-only response + event number 用于返回音频二进制。
|
||||
4. 错误帧必须转成结构化 JSON 错误,不把上游密钥或完整请求头写入日志。
|
||||
|
||||
### HTTP SSE 单向流式
|
||||
|
||||
客户端请求:
|
||||
|
||||
```text
|
||||
POST /api/speech/volcengine/tts/sse
|
||||
Authorization: Bearer <Genarrative JWT>
|
||||
Content-Type: application/json
|
||||
Accept: text/event-stream
|
||||
```
|
||||
|
||||
请求体:
|
||||
|
||||
```json
|
||||
{
|
||||
"text": "你好,欢迎来到百梦。",
|
||||
"speaker": "zh_female_cancan_mars_bigtts",
|
||||
"audioParams": {
|
||||
"format": "mp3",
|
||||
"sampleRate": 24000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
后端转换为火山 HTTP SSE 请求体:
|
||||
|
||||
```json
|
||||
{
|
||||
"user": { "uid": "current-user-id" },
|
||||
"req_params": {
|
||||
"text": "...",
|
||||
"speaker": "...",
|
||||
"audio_params": {
|
||||
"format": "mp3",
|
||||
"sample_rate": 24000
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
上游 SSE 的常见事件:
|
||||
|
||||
1. `352`:TTSResponse,`data` 为 base64 音频片段。
|
||||
2. `351`:TTSSentenceEnd,`sentence` 为字幕或时间戳数据。
|
||||
3. `152`:SessionFinish,合成完成,可含 `usage.text_words`。
|
||||
4. `153`:SessionFailed,合成失败。
|
||||
|
||||
后端保持 SSE 形态透传,但会补齐平台 `requestId` 与上游 `X-Tt-Logid` 作为排障信息。
|
||||
|
||||
## api-server 路由
|
||||
|
||||
| 方法 | 路由 | 说明 |
|
||||
|---|---|---|
|
||||
| `GET` | `/api/speech/volcengine/config` | 返回前端可见的默认资源和推荐音频参数,不返回密钥 |
|
||||
| `GET` | `/api/speech/volcengine/asr/stream` | ASR WebSocket 双向流式代理 |
|
||||
| `GET` | `/api/speech/volcengine/tts/bidirection` | TTS WebSocket 双向流式代理 |
|
||||
| `POST` | `/api/speech/volcengine/tts/sse` | TTS HTTP SSE 单向流式代理 |
|
||||
|
||||
所有路由必须走 `require_bearer_auth`。
|
||||
|
||||
## 验收
|
||||
|
||||
代码级验收:
|
||||
|
||||
```bash
|
||||
cargo fmt --manifest-path server-rs/Cargo.toml --all --check
|
||||
cargo test --manifest-path server-rs/Cargo.toml -p platform-speech
|
||||
cargo test --manifest-path server-rs/Cargo.toml -p api-server volcengine_speech
|
||||
cargo check --manifest-path server-rs/Cargo.toml -p api-server
|
||||
npm run check:encoding
|
||||
```
|
||||
|
||||
联调验收:
|
||||
|
||||
1. 启动 `npm run api-server`。
|
||||
2. 检查 `/healthz` 返回 200。
|
||||
3. 未登录访问语音路由返回 401。
|
||||
4. 已登录后 `/api/speech/volcengine/config` 不返回任何密钥字段。
|
||||
5. ASR WebSocket 发送首包和 200ms PCM 分片后能收到识别 JSON。
|
||||
6. TTS SSE 能收到 `352` 音频事件与最终 `152` 完成事件。
|
||||
7. TTS 双向 WebSocket 能复用连接完成至少一个 session。
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 不把 `VOLCENGINE_ACCESS_KEY_ID`、`VOLCENGINE_SECRET_ACCESS_KEY`、API Key、Access Token 或完整 Authorization 写入文档、日志、测试快照或前端状态。
|
||||
2. 中文语音默认使用 16k 单声道 PCM ASR;TTS 默认使用 24k mp3,运行时可按玩法需要改为 pcm。
|
||||
3. 火山返回的 `X-Tt-Logid` 是排障关键信息,应记录 logid,但不能记录密钥。
|
||||
4. 语音流式能力是平台副作用,不涉及 SpacetimeDB 表结构变更,本次无需修改 `migration.rs`。
|
||||
Reference in New Issue
Block a user