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

View File

@@ -37,3 +37,4 @@
4. 移动端卡片仍以紧凑横滑为主,参考图使用暗色遮罩承接标题,文本不得溢出卡片。
5. 当前创作 Tab 的可见玩法卡必须真实渲染 `img`,不能只在隐藏弹窗或旧入口中配置图片。
6. 参考图卡片上的标题和副标题必须显式使用白色文字,并配合底部加深渐变与文字阴影;禁止依赖 `text-inherit`,避免黑字叠在暗蒙版上。
7. 当前创作 Tab 顶部不再保留“10分钟创作一个精品互动玩法”标题玩法参考图卡带直接作为首屏入口移动端卡带必须支持横向拖动滑动。

View File

@@ -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 未登录三栏补充

View File

@@ -45,6 +45,7 @@
1. 玩家需要优先依赖画面主体、构图和色块识别位置。
2. 编号覆盖会削弱“完整图片被逐步复原”的视觉奖励。
3. 合成后的拼图块只保留原图切片、外轮廓描边和必要的拖拽层级,不叠加额外的色块、暗层或蒙版,避免破坏原图识别。
### 3. 设置能力

View File

@@ -118,6 +118,11 @@
- 早期方案曾在 `AuthGate` 层提供右上角全局账号信息条,并在 `GameShellRuntime` 中临时隐藏。
- 2026-04-20 起,这个全局悬浮入口已整体下线,不再区分“平台显示 / 冒险隐藏”。
- 原因是右上角高频观察区不适合承载账号入口,且平台内已经有更明确的页面内入口。
## 9. 2026-05-08 创作首页通知入口下线
- `CreativeAgentHome` 顶栏右上角不再展示“通知与账户”按钮,避免创作首页把通知入口放在首屏高频区域。
- 账号入口仍保留在侧边栏底部,创作首页顶栏维持左侧菜单、居中品牌的轻量结构。
- 当前账号相关入口统一保留在平台首页受保护动作、个人页、存档页与账号弹窗,不再占用全局悬浮层。
---

View File

@@ -468,8 +468,9 @@ interface CustomWorldCoverProfile {
1. 上传后先进入独立裁剪面板
2. 裁剪框比例固定为 `16:9`
3. 作者只能平移和缩放,不允许自由改比例
4. 裁剪完成后,再提交给后端保存
3. 作者直接在图片上拖拽裁剪框内部移动区域,拖拽四边或四角调整裁剪范围,不再通过参数滑杆调整
4. 裁剪框调整过程中必须持续锁定 `16:9`,不允许自由改比例
5. 裁剪完成后,再提交给后端保存
### 上传大小与格式限制

View File

@@ -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回放删除与外部平台功能负向扫描
负责范围:

View File

@@ -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. 跳过新手引导只更新本地首次访问标记和界面状态,不创建临时作品、不调用保存接口。

View File

@@ -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` 只暴露平台鉴权后的代理路由,不向前端返回任何密钥字段。
## 示例文件

View File

@@ -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 页面判断登录入口状态。

View File

@@ -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 控制。

View File

@@ -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`

View File

@@ -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 端当前只允许:

View File

@@ -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` 重启验证。

View File

@@ -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`,再点击拼图结果页“生成或更换图片”,候选图应能写回并正常展示。

View File

@@ -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` 为空。

View File

@@ -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 运行态边界

View File

@@ -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` 通过。

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 请求口径和定向验证结果。

View File

@@ -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 写入。

View File

@@ -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. 在视觉小说结果页提交背景音乐或音效生成,生成完成后确认场景音频槽位可播放。

View File

@@ -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 requestmessage type `0b0001`JSON 序列化gzip 压缩。
3. audio only requestmessage type `0b0010`raw payloadgzip 压缩。
4. 最后一包音频使用 flags `0b0010`
5. full server responsemessage type `0b1001`payload 为 gzip JSON。
6. error responsemessage 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 ASRTTS 默认使用 24k mp3运行时可按玩法需要改为 pcm。
3. 火山返回的 `X-Tt-Logid` 是排障关键信息,应记录 logid但不能记录密钥。
4. 语音流式能力是平台副作用,不涉及 SpacetimeDB 表结构变更,本次无需修改 `migration.rs`