This commit is contained in:
2026-05-10 22:20:54 +08:00
parent d6219f1a0c
commit 192accd796
92 changed files with 7045 additions and 1559 deletions

View File

@@ -105,6 +105,7 @@ HYPER3D_MODEL_REQUEST_TIMEOUT_MS / RODIN_MODEL_REQUEST_TIMEOUT_MS
7. 火山引擎语音能力由 `platform-speech` 收口协议帧与上游鉴权,`api-server` 只暴露平台鉴权后的代理路由,不向前端返回任何密钥字段。
8. Hyper3D Rodin Gen-2 使用公开默认 `https://api.hyper3d.com/api/v2`API Key 只读取 `HYPER3D_API_KEY` / `RODIN_API_KEY`,不复用文本 LLM、图片或音频网关密钥。
9. APIMart 当前只保留给创意 Agent 的 `gpt-5` Responses 文本/多模态理解链路GPT-image-2 图片生成不得再读取 APIMart 配置。
10. 本地 `npm run api-server``npm run dev:rust``npm run dev` 的环境文件优先级固定为外层 shell 变量最高,其后 `.env``.env.local``.env.secrets.local` 逐层覆盖;真实密钥建议放在 `.env.secrets.local`,防止 `.env` 中的空示例值覆盖私密配置。
## 示例文件

View File

@@ -44,7 +44,8 @@ RODIN_MODEL_REQUEST_TIMEOUT_MS
1. `HYPER3D_API_KEY` / `RODIN_API_KEY` 只允许写入本地或生产私密环境,不提交到 Git。
2. 缺少 API Key 时,后端返回 `503 SERVICE_UNAVAILABLE`
3. `HYPER3D_BASE_URL` 默认使用公开 API 基础地址;如果团队后续改用代理网关,可通过环境变量覆盖
3. 本地真实联调推荐把 key 放在 `.env.secrets.local``npm run api-server``npm run dev:rust``npm run dev` 均应保持“外层 shell 变量优先,随后 `.env``.env.local``.env.secrets.local` 逐层覆盖”的口径,避免 `.env` 里的空示例值压掉私密 key
4. `HYPER3D_BASE_URL` 默认使用公开 API 基础地址;如果团队后续改用代理网关,可通过环境变量覆盖。
## 4. 后端路由
@@ -91,6 +92,7 @@ RODIN_MODEL_REQUEST_TIMEOUT_MS
6. `meshMode` 限定为 `Quad/Raw`,默认 `Quad`
7. `addons` 首版只允许 `HighPack`
8. `bboxCondition` 必须为 3 个正数,按上游要求序列化为 JSON 字符串。
9. `subscriptionKey` 是 Hyper3D 返回的 opaque token状态查询只校验非空不在本地做 256 字符等固定长度限制,避免长 token 阻断抓大鹅草稿内联模型生成。
## 6. 返回语义

View File

@@ -0,0 +1,130 @@
# 抓大鹅草稿素材生成流水线 2026-05-10
## 1. 范围
本方案用于改造 `生成抓大鹅草稿` 的首版生成链路:点击按钮后先进入独立生成过程页,生成结束后自动进入抓大鹅草稿页,并在草稿页 `3D素材` Tab 预览本次生成的 3D 模型。
本次只把任意难度都收敛为 `3` 件物品。后续难度曲线恢复时,再把物品数、网格数和手动 3D 任务数量从配置中放开。
## 2. 前端流程
入口仍复用 `Match3DAgentWorkspace` 表单。点击 `生成抓大鹅草稿` 后:
1. 创建 Match3D session。
2. 进入 `match3d-generating` 生成过程页。
3. 过程页复用拼图生成页的 `CustomWorldGenerationView` 结构。
4. 生成成功后自动进入 `match3d-result`
5. 生成失败时停留在生成过程页,允许重新生成或返回创作中心。
生成页步骤固定为:
```text
生成物品名称 -> 生成素材图 -> 切割独立图片 -> 生成 3D 模型 -> 上传图片与模型资产 -> 写入草稿页
```
生成页只展示题材和物品数量,不展示玩法规则说明。
## 3. 后端编排边界
外部模型和 OSS 上传全部由 `api-server` 编排,不进入 SpacetimeDB reducer。SpacetimeDB 继续只负责 Match3D 会话、草稿和作品 profile 的确定性写入。
`match3d_compile_draft` action 的后端顺序为:
1. 读取 session config。
2. 将本次 MVP 的 `clearCount` 固定为 `3`,并同步用于草稿编译。
3. 调用文本模型生成 `3` 个题材下的短物品名称。
4. 调用项目当前图片链路 VectorEngine `gpt-image-2-all` 生成一张 `1:1` 素材图,提示词必须合入入口页选择的 `assetStylePrompt`。历史 `nanobanana2` 图片选项当前按项目统一决策回落到 VectorEngine不重新接入 APIMart 图片网关。
5. 将素材图按 `n*n` 网格切割成独立图片。当前 `3` 件物品使用 `2*2` 网格,取前 `3` 格。
6. 将素材图和每张独立图片上传到 OSS其中独立图片作为 Rodin 图生模型参考图。
7. 对每张独立图片调用 Hyper3D Rodin Gen-2 图生模型,等待任务完成,读取 GLB 下载文件并转存到 `generated-match3d-assets`
8. 调用现有 SpacetimeDB compile procedure 写入草稿,并把本次生成的独立物品图片与模型文件引用序列化写入 `match3d_work_profile.generated_item_assets_json`。这一步对标拼图的 `save_puzzle_generated_images`:生成资产不能只挂在本次 HTTP response 上,否则退出结果页后从草稿架读取 `getMatch3DWorkDetail` 会丢失素材列表。
9. 在 HTTP 返回的 draft/profile DTO 中附带本次生成的素材资产预览信息,模型生成成功时状态为 `model_ready`;后续重进草稿页时从 work profile 的持久化 `generatedItemAssets` 恢复同一批素材。
草稿生成阶段会调用 Hyper3D Rodin并等待 GLB 模型文件可下载;不得把上游下载 URL 直接写入 Match3D profile必须先转存 OSS再保存 `/generated-match3d-assets/...` 引用。结果页 `3D素材` Tab 的重新生成按钮仍可手动触发 Rodin 任务,但正式持久化仍以后端草稿生成链路写入的模型文件为准。
## 4. 图片提示词
素材图提示词必须显式包含:
```text
生成一张1:1图片
生成2*2网格素材图
整体画风遵循:...
只绘制这些物品:...
不要出现文字、水印、UI、边框
```
`包含若干个物品名称` 在落地中解释为“按生成出的物品名称绘制对应主体”,不要求图片上写出物品名称。这样可以避免文字渲染污染切图和后续手动 3D 模型参考。
入口页内置风格参考图通过同一 VectorEngine `gpt-image-2-all` 能力生成,保存路径固定为:
```text
public/match3d-style-references/clay-toy.png
public/match3d-style-references/low-poly.png
public/match3d-style-references/toy-plastic.png
public/match3d-style-references/wood-carved.png
public/match3d-style-references/voxel-block.png
public/match3d-style-references/metal-mecha.png
```
这些图片只作为入口页风格选择的视觉参考,不进入用户草稿资产,不替代生成时的物品素材图。
## 5. OSS 路径
新增 generated legacy prefix
```text
generated-match3d-assets
```
建议对象分组:
```text
generated-match3d-assets/{sessionId}/{profileId}/material-sheet/{taskId}/sheet.png
generated-match3d-assets/{sessionId}/{profileId}/items/{itemSlug}/image.png
generated-match3d-assets/{sessionId}/{profileId}/items/{itemSlug}/model/{taskUuid}/model.glb
```
`itemSlug` 必须带 `itemId` 前缀,例如 `match3d-item-1-item`。中文物品名清洗后可能都退回 `item`,不能只用物品名做路径,否则多张切割图会写到同一个 object key导致草稿页预览图全部一致。
HTTP DTO 同时返回 `imageSrc``imageObjectKey``modelSrc``modelObjectKey``modelFileName``taskUuid``subscriptionKey``status = model_ready`。前端模型预览不得直接请求裸 `/generated-match3d-assets/...` 路径;需要通过 `/api/assets/read-bytes` 读取模型字节,转成 Blob URL 后交给 Three.js GLTFLoader 加载,以绕开私有 bucket CORS。
## 6. 自动保存与草稿恢复
抓大鹅结果页的基础信息自动保存继续调用 `PUT /api/runtime/match3d/works/{profileId}` 更新名称、题材、描述、标签、封面、消除数和难度;该保存不得清空 `generated_item_assets_json`。SpacetimeDB `update_match3d_work` / `publish_match3d_work` 必须保留当前行的生成素材 JSON。
草稿架重进路径为:
```text
草稿 Tab -> getMatch3DWorkDetail(profileId) -> Match3DResultView(profile.generatedItemAssets)
```
因此 `map_match3d_work_summary_response` / `map_match3d_work_profile_response` 需要从 work profile snapshot 反序列化 `generated_item_assets_json` 并输出 `generatedItemAssets`。前端 `Match3DResultView` 仍保持现有优先级:本次生成流程内有 `draft.generatedItemAssets` 时用 draft从草稿架重进没有 draft 时,用 `profile.generatedItemAssets`;两者都没有才回退到默认 3D 素材占位。
`3D素材` 详情页只保留:
1. 模型预览区:优先加载 `modelSrc` 对应 GLB支持拖动旋转没有模型时展示空预览。
2. 素材名称输入。
3. `重新生成` 按钮。
详情页不再展示参考图、用途、提示词、文生/图生切换、状态查询、下载列表、taskUuid 或 subscriptionKey。
## 7. 验收
建议执行:
```powershell
npm run check:encoding
npm run test -- src\services\miniGameDraftGenerationProgress.test.ts
npm run test -- src\components\match3d-result\Match3DResultView.test.tsx
npm run typecheck
cargo test -p shared-contracts match3d --manifest-path server-rs\Cargo.toml
cargo test -p spacetime-client match3d --manifest-path server-rs\Cargo.toml
cargo test -p platform-oss --manifest-path server-rs\Cargo.toml
cargo test -p api-server match3d --manifest-path server-rs\Cargo.toml
cargo check -p api-server --manifest-path server-rs\Cargo.toml
cargo check -p spacetime-client --manifest-path server-rs\Cargo.toml
cargo check -p spacetime-module --manifest-path server-rs\Cargo.toml
```
真实草稿生成需要本地私密环境配置 `VECTOR_ENGINE_API_KEY``HYPER3D_API_KEY` 和 OSS 访问变量。后端改动后使用 `npm run api-server` 启动,并检查 `/healthz`

View File

@@ -1,6 +1,10 @@
# 抓大鹅 Match3D F1 创作入口与 Agent UI 落地记录 2026-04-30
> 2026-05-08 更新:抓大鹅创作端入口已重新开放,当前 `match3d.visible` 为 `true`。本文件记录 F1 接入能力,入口是否展示以 `NEW_WORK_ENTRY_CONFIG_2026-05-01.md` 和 `src/config/newWorkEntryConfig.ts` 为准。
>
> 2026-05-10 更新:抓大鹅入口页对齐拼图入口页,直接嵌入创作页模板 Tab。入口表单不再展示参考图、消除次数输入、难度数值滑杆和题材/物品/难度摘要框,仅保留题材主题大输入框和难度选项。难度选项负责派生 `clearCount` 与 `difficulty`,生成按钮必须展示 `消耗20光点`。
>
> 2026-05-10 补充:入口页新增 `3D素材风格` 横向滑动选择,首批风格参考图通过 VectorEngine `gpt-image-2-all` 生成并保存到 `public/match3d-style-references/`。最后一个选项为 `自定义`,点击后弹出独立面板填写画风描述。
## 1. 阶段边界
@@ -30,19 +34,38 @@ badge: 可创建
入口来源统一走 `getVisiblePlatformCreationTypes()`,因此创作首页首屏卡带与“选择创作类型”弹层会同时出现抓大鹅。
## 4. Agent 工作区
## 4. 入口表单工作区
新增 `Match3DAgentWorkspace`复用通用 `CreationAgentWorkspace`
新增 `Match3DAgentWorkspace`组件名继续兼容既有路由、草稿恢复和父层分流;当前主入口已从固定 Agent 追问收口为拼图式表单
Agent 只收集三类锚点:
创作页 `选择模板` Tab 中切换到 `抓大鹅` 时,直接渲染该表单,不创建会话,也不跳到独立工作台。点击生成后才创建 Match3D 会话并执行 `match3d_compile_draft`
1. 题材主题。
2. 需要消除次数。
3. 难度。
表单只展示三个输入块:
工作区支持参考图片上传入口。图片在 F1 中先以 Data URL 形式随消息 payload 带给 mock clientB5 接入后由后端 facade 替换为正式资产上传与引用
1. `想做一个什么题材的抓大鹅?`:大文本输入框,收集 `themeText`
2. `3D素材风格`:横向滑动风格卡,选择会写入 `assetStyleId``assetStyleLabel``assetStylePrompt`
3. `难度`:四个选项按钮,选项内部派生消除次数和难度数值。
UI 中不默认展示玩法规则长文,只展示进度、锚点、聊天内容和必要按钮。
当前难度映射固定为:
```text
轻松 -> clearCount 8, difficulty 2
标准 -> clearCount 12, difficulty 4
进阶 -> clearCount 16, difficulty 6
硬核 -> clearCount 20, difficulty 8
```
入口页不再上传参考图,提交 payload 中 `referenceImageSrc` 固定为 `null`。如果从旧会话或旧草稿恢复,前端只根据已有 `difficulty` 选择最接近的难度选项,并按当前选项重新派生 `clearCount``difficulty`
内置风格选项为:
```text
黏土手作 / 低多边形 / 玩具塑料 / 木质雕刻 / 体素积木 / 金属机甲 / 自定义
```
自定义风格必须在弹出面板中填写描述后才能应用。入口表单必须在移动端创作页可视区内完成题材、风格、难度和生成按钮的展示,页面自身不产生纵向滚动;风格卡只允许横向滑动。
生成按钮文案为 `生成抓大鹅草稿`,按钮内必须同时展示 `消耗20光点`。UI 中不默认展示玩法规则长文,也不展示隐藏派生数值的摘要框。
## 5. mock client
@@ -84,10 +107,13 @@ POST /api/creation/match3d/sessions/:sessionId/compile
## 8. 验收口径
1. 创作首页能看到“抓大鹅 / 经典消除玩法
2. 弹层选择“抓大鹅”能进入 Agent 工作区
3. 输入题材、消除次数、难度后进度到 `100%`
4. 点击“生成结果页”进入草稿承接页
5. 可从草稿承接页返回 Agent 修改
6. `npm run check:encoding` 通过
7. `npm run typecheck` 通过。
1. 创作`选择模板` Tab 能看到 `抓大鹅 / 经典消除玩法`
2. 切换到 `抓大鹅` Tab 后,页面内直接显示抓大鹅入口表单,不提前创建会话
3. 表单不展示参考图、`需要消除次数``难度数值``题材``物品``难度`摘要框
4. 输入题材、选择风格和难度后,提交 payload 包含派生后的 `clearCount``difficulty``referenceImageSrc``null`,并包含 `assetStyleId``assetStyleLabel``assetStylePrompt`
5. 生成按钮展示 `消耗20光点`
6. 点击 `自定义` 风格弹出独立面板,填写后应用到提交 payload未填写时不能应用空自定义风格
7. 移动端创作页内抓大鹅入口内容不产生纵向滚动,风格卡横向滑动。
8. 点击生成后创建会话并进入草稿生成/结果页链路。
9. `npm run check:encoding` 通过。
10. `npm run typecheck` 通过。

View File

@@ -0,0 +1,75 @@
# 抓大鹅 Rodin 3D 素材 Tab 接入方案 2026-05-10
## 1. 范围
本方案用于把抓大鹅结果页调整为多 Tab 工作台,并新增 `3D素材` Tab。该 Tab 专门服务抓大鹅玩法内物件、奖励、障碍等 3D 素材的 Rodin 生成试验。
本次复用已有 Hyper3D Rodin Gen-2 后端安全代理,不新增 SpacetimeDB 表;草稿生成链路会把 Rodin 模型转存到 OSS并通过 Match3D 作品 profile 的 `generatedItemAssets` 恢复。不得把 Hyper3D API Key、上游下载 URL 或本地 Data URL 写成正式资产真相。
## 2. 页面结构
抓大鹅结果页拆为三个 Tab
1. `作品信息`:承接封面、游戏名称、标签、简介。
2. `玩法配置`:承接题材、消除次数、难度、参考图与局内数量摘要。
3. `3D素材`:展示 Rodin 素材列表,点击列表项进入详情生成页。
`3D素材` Tab 的列表布局参考 RPG 结果页角色列表:移动端纵向卡片,桌面端多列紧凑卡片;卡片展示素材名和状态。详情页只保留模型预览、素材名称和 `重新生成` 按钮。
## 3. Rodin 任务边界
前端只维护当前页面内的临时重新生成任务状态:
1. 素材槽位名称。
2. 模型预览。草稿生成的 `/generated-match3d-assets/...` GLB 必须通过同源 `/api/assets/read-bytes` 由后端换签并读取字节,前端再转成 Blob URL 后交给 Three.js GLTFLoader避免浏览器直接 `fetch` OSS 签名 URL 时被 CORS 拦截。
3. 图生模型参考图只作为重新生成的隐藏输入来源,不在详情页展示。上传图片在前端直接读成 Data URL草稿生成的 `/generated-match3d-assets/...` 图片必须通过 `/api/assets/read-bytes` 转成 Data URL 后提交给 Hyper3D。
4. Hyper3D `taskUuid``subscriptionKey`
5. 查询到的状态、进度与下载文件列表。
正式资产链后续再接:
1. 下载文件转存 OSS。
2. `asset_object` 确认。
3. `asset_entity_binding` 绑定到 Match3D 作品或物件槽位。
首版不得把上游下载 URL 直接写入 Match3D profile也不得把 Data URL 持久化到 SpacetimeDB。
## 4. 接口复用
继续使用既有前端 client
```text
src/services/hyper3dModelGenerationService.ts
```
对应后端路由:
```text
POST /api/assets/hyper3d/text-to-model
POST /api/assets/hyper3d/image-to-model
POST /api/assets/hyper3d/status
POST /api/assets/hyper3d/download
```
请求参数默认使用:
```text
geometryFileFormat = glb
material = PBR
quality = medium
meshMode = Quad
previewRender = true
conditionMode = concat
```
## 5. 验收
建议执行:
```powershell
npm run check:encoding
npm run test -- src\components\match3d-result\Match3DResultView.test.tsx
npm run typecheck
```
真实 Rodin 生成会消耗 Hyper3D Credit只在本地私密环境配置 `HYPER3D_API_KEY``RODIN_API_KEY` 后手动验证。

View File

@@ -262,3 +262,14 @@ cannon-es
3. 可见 mesh 初始以较小比例显示,再用缓动动画放大到完整尺寸;视觉缩放不得反向修改 body shape、质量、边界半径或生成高度避让计算。
4. 入场动画继续服从第 18 节的创建限流和第 19 节的生成高度避让;不能为了动画效果把物体直接放进已有堆叠内部。
5. 动画结束后 mesh 缩放必须回到 `1`,避免影响后续点击可读性和托盘对应关系。
## 21. 高 DPR 移动端 WebGL 画布尺寸锁定
2026-05-10 针对移动端试玩入口中 3D 锅体和物体从中心区域向右下溢出的问题,补充 WebGL canvas 布局口径。
编码口径:
1. 中心 3D 棋盘和托盘 3D 预览都允许通过 `renderer.setPixelRatio(...)` 提升绘制清晰度,但 `WebGLRenderer.domElement` 的 CSS 显示尺寸必须单独锁定为 `position: absolute; inset: 0; width: 100%; height: 100%; display: block`
2. `renderer.setSize(width, height, false)` 只负责绘图缓冲区与当前容器尺寸同步,不能依赖 canvas 默认属性尺寸承载 CSS 布局;否则高 DPR 设备会把绘图缓冲区尺寸当成页面尺寸显示,导致棋盘内容放大并从容器右下溢出。
3. 移动端验收仍以 `390px` 竖屏为基准:顶部状态、圆形棋盘和 `7` 格备选栏都必须完整可见,锅体内容应落在屏幕中间的圆形区域内。
4. 该修复只影响 WebGL 画布 CSS 布局,不改变相机、物理世界、碰撞体、点击命中、后端权威快照或托盘规则。

View File

@@ -76,13 +76,14 @@
4. 首图文生图 prompt 由 api-server 拼接固定拼图约束后统一压缩到 `500` 字符以内,避免玩家长画面描述触发 DashScope 参数非法;进度页和结果页仍展示玩家原始画面描述,不展示压缩后的内部 prompt。
5. 图片生成仍在 api-server 内完成,遵守 SpacetimeDB reducer 不做网络 I/O 的约束。
6. 参考图以 Data URL 进入 `POST /api/runtime/puzzle/agent/sessions``POST /api/runtime/puzzle/agent/sessions/{sessionId}/actions`,这两条路由必须单独放宽 JSON body 上限;不要放大全局默认 body limit。
7. 前端仍应优先压缩参考图;后端 body 上限只用于容纳合理尺寸的单张参考图,超大原图不应直接落入 SpacetimeDB 或作为作品字段持久化。
8. 作品更新接口 `PUT /api/runtime/puzzle/works/{profileId}` 必须支持作品信息和关卡列表一起写入,前端自动保存不得只写旧单关字段
9. `StartPuzzleRunRequest` 新增可选 `levelId`。详情页或草稿结果页单独体验某关时传入目标关卡,后端从作品/草稿的 `levels` 中选取该关卡生成运行态
10. `ExecutePuzzleAgentActionRequest` 必须保留 `pictureDescription` 字段。表单直达生成时,`compile_puzzle_draft` 优先用 `pictureDescription` 作为首图 prompt再回退到旧 `promptText`;避免生成页展示的是玩家画面描述,但后端实际用作品名称或旧摘要出图
11. `compile_puzzle_draft` 中的图片上游失败不得映射成 `400 BAD_REQUEST`。DashScope 返回 `InvalidParameter` 或任务失败时api-server 统一按 `502 UPSTREAM_ERROR` 暴露,并在 `details.message` 中保留“拼图图片生成失败:...”的业务原因,避免生成页只显示“请求参数不合法”
12. `compile_puzzle_draft` 前置光点预扣失败不得映射成 `400 BAD_REQUEST`余额不足返回 `409 CONFLICT`SpacetimeDB procedure 不可用、绑定不匹配、钱包服务异常等统一按 `502 UPSTREAM_ERROR` 暴露,并在 `details.message` 中保留真实钱包错误
13. 生成拼图作品草稿动作涉及的表单 seed prompt 与首图 prompt 来源选择统一收口在 `server-rs/crates/api-server/src/prompt/puzzle/draft.rs``puzzle.rs` 只负责调用 SpacetimeDB、计费、图片服务和持久化不再直接拼草稿 prompt 文本
7. 前端仍应优先压缩参考图,入口上传图和裁剪图统一压到单边 1024 以内;后端 body 上限只用于容纳合理尺寸的单张参考图,超大原图不应直接落入 SpacetimeDB 或作为作品字段持久化,后端解析后会拒绝超过 8MB 字节的参考图
8. `aiRedraw = true` 且存在 `referenceImageSrc`api-server 必须走 VectorEngine `POST /v1/images/edits` multipart 图生图接口;无参考图或入口页 `aiRedraw = false` 时不走图生图,关闭 AI 重绘会直接应用上传图为首关正式图
9. 作品更新接口 `PUT /api/runtime/puzzle/works/{profileId}` 必须支持作品信息和关卡列表一起写入,前端自动保存不得只写旧单关字段
10. `StartPuzzleRunRequest` 新增可选 `levelId`。详情页或草稿结果页单独体验某关时传入目标关卡,后端从作品/草稿的 `levels` 中选取该关卡生成运行态
11. `ExecutePuzzleAgentActionRequest` 必须保留 `pictureDescription` 字段。表单直达生成时,`compile_puzzle_draft` 优先用 `pictureDescription` 作为首图 prompt再回退到旧 `promptText`;避免生成页展示的是玩家画面描述,但后端实际用作品名称或旧摘要出图
12. `compile_puzzle_draft` 中的图片上游失败不得映射成 `400 BAD_REQUEST`DashScope 返回 `InvalidParameter` 或任务失败时api-server 统一按 `502 UPSTREAM_ERROR` 暴露,并在 `details.message` 中保留“拼图图片生成失败:...”的业务原因,避免生成页只显示“请求参数不合法”
13. `compile_puzzle_draft` 前置光点预扣失败不得映射成 `400 BAD_REQUEST`。余额不足返回 `409 CONFLICT`SpacetimeDB procedure 不可用、绑定不匹配、钱包服务异常等统一按 `502 UPSTREAM_ERROR` 暴露,并在 `details.message` 中保留真实钱包错误
14. 生成拼图作品草稿动作涉及的表单 seed prompt 与首图 prompt 来源选择统一收口在 `server-rs/crates/api-server/src/prompt/puzzle/draft.rs``puzzle.rs` 只负责调用 SpacetimeDB、计费、图片服务和持久化不再直接拼草稿 prompt 文本。
## 结果页
@@ -104,10 +105,23 @@
4. 底部吸底操作区只承载动作按钮,不默认写玩法说明或规则解释,避免压缩移动端编辑空间。
5. 关卡详情面板内触发生成画面时,前端必须把当前编辑态完整 `levelsJson``generate_puzzle_images` action 一起提交。这样新建关卡在自动保存完成前立即生成,也能由后端写回目标关卡。
6. api-server 处理 `generate_puzzle_images` 时,若 action 带有 `levelsJson`,必须用这份关卡快照覆盖本次生成的草稿关卡视图后再定位 `levelId`。若请求明确传入 `levelId` 但关卡列表中不存在该关卡,必须返回错误,不得静默回退第一关。
7. 历史拼图素材入口只在已有正式图的 `画面图` 区域右下角展示,不再放在 `画面描述` 输入区;本地上传参考图入口仍保留在画面描述输入区右下角
7. 历史拼图素材入口和本地上传参考图入口统一收口到 `画面图` 图卡右下角,避免 `画面描述` 输入区同时承载文本编辑和素材入口;无正式图时也展示空图态图卡
8. 历史拼图素材列表必须由服务端按当前登录账号过滤,只返回 `asset_kind = puzzle_cover_image``owner_user_id = 当前账号` 的资产;不得依赖前端过滤,也不得展示其他账号素材。
9. `画面图` 图卡本身就是上传热区,详情页不再保留右下角独立“上传参考图”按钮;历史入口统一使用带 `History` 图标和 `历史` 小字的按钮。入口页空图态的“点击上传拼图图片”只作为图卡内轻量提示,不使用胶囊按钮、边框或背景样式。
画面描述区域不再展示候选图实际 prompt 或“请生成一张适合……”之类内部提示词模块。参考图入口保留在画面描述编辑区域内,便于重新生成时继续带入。结果页编辑关卡画面描述时只同步该关卡 `pictureDescription`;作品描述只在作品信息 Tab 编辑,作品详情页不得再回退使用画面描述。
### 2026-05-10 关卡生图交互补充
1. 关卡详情页的 `画面图``画面描述` 模块对齐入口页拼图表单:画面图使用稳定正方形图卡,画面描述使用固定高度输入区并保留图片模型选择。
2. 新建关卡或无正式图关卡也展示 `画面图` 图卡;空图态只保留图标化占位和生成中状态,不追加规则说明文案。
3. 关卡详情页删除手填 `参考图链接或资产ID` 输入框。参考图只能通过本地上传或历史拼图素材选择进入本次生成请求;字段 `levels[].pictureReference` 继续作为后端生成后的复用字段透传,不作为用户可手填表单项。
4. 单关生成等待估算从 `30` 秒调整为 `90` 秒;生成按钮内展示小字 `等待时间可以制作更多关卡哦~`,不得另起说明面板。
5. 触发某一关生成时,前端必须立即把该关 `generationStatus` 标为 `generating` 并随当前 `levelsJson` 写入草稿自动保存链路;后端生成完成后再写回 `ready`
6. `generationStatus = generating` 的关卡在详情弹窗关闭后仍保留进度展示,再次打开同一关详情能继续看到生成进度;关卡列表卡片也必须展示生成中的轻量状态。
7. 单关图片生成必须作为后台 action 执行,不占用拼图结果页全局 busy 状态;生成期间仍允许编辑作品信息、编辑关卡、新增关卡、删除其他关卡、关卡测试和继续触发其他可并行动作。
8. 发布是唯一必须等待全部图片生成完成的草稿结果页动作;发布检查需要把 `generationStatus = generating` 的关卡列为 blocker避免未完成资源进入广场。
9. 后台生图回包只合并对应关卡的图片候选、正式图、资产 ID 与生成状态,不得覆盖用户等待期间对关卡名、画面描述、作品信息或新增关卡所做的本地编辑。
画面描述区域不再展示候选图实际 prompt 或“请生成一张适合……”之类内部提示词模块。参考图入口统一放在画面图图卡内,便于重新生成时继续带入,同时保持画面描述输入区只负责文本编辑。结果页编辑关卡画面描述时只同步该关卡 `pictureDescription`;作品描述只在作品信息 Tab 编辑,作品详情页不得再回退使用画面描述。
## 验收

View File

@@ -10,9 +10,12 @@
- [VECTOR_ENGINE_GPT_IMAGE_2_GENERATION_2026-05-09.md](./VECTOR_ENGINE_GPT_IMAGE_2_GENERATION_2026-05-09.md):记录 GPT-image-2 图片生成从 APIMart 迁移到 VectorEngine `gpt-image-2-all` 的接口、环境变量、尺寸映射、错误口径和验收命令。
- [SPACETIMEDB_PUBLISH_SCCACHE_FALLBACK_2026-05-09.md](./SPACETIMEDB_PUBLISH_SCCACHE_FALLBACK_2026-05-09.md):记录本地 `spacetime publish` 被 sccache wrapper 通信异常阻断时的根因、debug 构建参数口径和手动排障命令。
- [AUTH_RESTORE_AND_RECOMMEND_LOADING_FIX_2026-05-09.md](./AUTH_RESTORE_AND_RECOMMEND_LOADING_FIX_2026-05-09.md):记录刷新网页后登录态失效和推荐页作品卡卡在加载中的联合修复,覆盖 `AuthGate` 本地 token 优先恢复、refresh 失败不清 token、推荐页启动请求版本保护和错误态收口。
- [USER_TAG_INVITE_AND_PUZZLE_LEADERBOARD_2026-05-10.md](./USER_TAG_INVITE_AND_PUZZLE_LEADERBOARD_2026-05-10.md):冻结用户标签字段、后台邀请码授予标签、用户填写邀请码后合并账号标签,以及拼图排行榜只展示白名单标签 `北科` 的落地口径。
- [RECOMMEND_RUNTIME_AUTH_FAILURE_ISOLATION_FIX_2026-05-09.md](./RECOMMEND_RUNTIME_AUTH_FAILURE_ISOLATION_FIX_2026-05-09.md):记录平台推荐页自动加载作品、公开拼图作品完整运行态、平台 bootstrap 私有投影刷新和展示层图片换签的局部请求 `401` 不应扩散成全局登出的修复,覆盖 `authImpact: local` 请求策略、推荐页 embedded 运行态启动、拼图开局/排行榜/下一关和回归测试。
- [AUTH_GATE_LOGIN_RACE_GUARD_FIX_2026-05-09.md](./AUTH_GATE_LOGIN_RACE_GUARD_FIX_2026-05-09.md):记录 `AuthGate` 登录成功后又被旧 hydrate 覆盖回未登录态的竞态根因、版本号保护修复与回归测试。
- [HYPER3D_RODIN_GEN2_MODEL_GENERATION_2026-05-08.md](./HYPER3D_RODIN_GEN2_MODEL_GENERATION_2026-05-08.md):记录 Hyper3D Rodin Gen-2 文生 3D 模型、图生 3D 模型、状态查询和下载列表的后端代理、环境变量、请求约束与验收边界。
- [MATCH3D_RODIN_ASSET_TAB_2026-05-10.md](./MATCH3D_RODIN_ASSET_TAB_2026-05-10.md):记录抓大鹅结果页多 Tab 改造与 Rodin 3D 素材列表/详情页的前端接入边界,明确首版只复用 Hyper3D 后端代理,不新增表或正式资产写入。
- [MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md](./MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md)冻结抓大鹅草稿生成过程页、3 件物品名称生成、VectorEngine 1:1 素材图、n*n 切图、并行 Rodin 图生 3D 与 OSS 回填草稿页的端到端边界。
- [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 资产回写和前端弹层交互边界。
- [PROFILE_FEEDBACK_BACKEND_INTEGRATION_2026-05-08.md](./PROFILE_FEEDBACK_BACKEND_INTEGRATION_2026-05-08.md):冻结“我的”页签帮助与反馈入口的后端接入方案,覆盖 `POST /api/profile/feedback``profile_feedback_submission`、凭证图片 Data URL 校验和前端预览/提交边界。
@@ -81,7 +84,7 @@
- [PROFILE_INVITE_CODE_REGISTRATION_AND_ADMIN_2026-04-30.md](./PROFILE_INVITE_CODE_REGISTRATION_AND_ADMIN_2026-04-30.md):冻结邀请码从“我的 Tab 填写”迁到注册环节的前后端边界、`profile_invite_code.metadata_json` 表结构扩展、管理员邀请码虚拟主体和奖励规则。
- [MATCH3D_CREATION_AND_RUNTIME_MINIMAL_IMPLEMENTATION_2026-04-30.md](./MATCH3D_CREATION_AND_RUNTIME_MINIMAL_IMPLEMENTATION_2026-04-30.md):冻结抓大鹅 Match3D 首版 demo 的独立玩法域、表与 procedure、HTTP facade、前端即时反馈/后端权威确认协议,以及可并行开发包。
- [MATCH3D_DOMAIN_AND_CONTRACTS_STAGE1_2026-04-30.md](./MATCH3D_DOMAIN_AND_CONTRACTS_STAGE1_2026-04-30.md):冻结抓大鹅 Match3D B1+B2 的纯领域规则 crate、Rust/TypeScript shared contracts以及 Stage1 不触碰 SpacetimeDB 表和 api-server 的边界。
- [MATCH3D_F1_CREATION_ENTRY_AND_AGENT_UI_2026-04-30.md](./MATCH3D_F1_CREATION_ENTRY_AND_AGENT_UI_2026-04-30.md):记录抓大鹅 F1 创作入口、Agent 工作区、参考图入口、本地 mock client 与后续 B5 HTTP facade 替换点
- [MATCH3D_F1_CREATION_ENTRY_AND_AGENT_UI_2026-04-30.md](./MATCH3D_F1_CREATION_ENTRY_AND_AGENT_UI_2026-04-30.md):记录抓大鹅 F1 创作入口与当前拼图式内嵌 Tab 表单,明确入口页仅保留题材大输入框和难度选项,由难度选项派生消除次数与难度数值
- [MATCH3D_F2_RESULT_AND_PUBLISH_2026-04-30.md](./MATCH3D_F2_RESULT_AND_PUBLISH_2026-04-30.md):冻结抓大鹅 F2 结果页、基础信息编辑、发布前试玩入口、发布门槛、自动保存和已发布作品二次编辑恢复口径。
- [MATCH3D_SPACETIME_CLIENT_AND_API_FACADE_2026-04-30.md](./MATCH3D_SPACETIME_CLIENT_AND_API_FACADE_2026-04-30.md):记录抓大鹅 B4+B5 已落地的 SpacetimeDB bindings、`spacetime-client` facade、`api-server` HTTP 路由、shared contract 对齐和验收命令。
- [MATCH3D_CREATION_ENTRY_COMING_SOON_2026-05-01.md](./MATCH3D_CREATION_ENTRY_COMING_SOON_2026-05-01.md):记录抓大鹅创作页入口重新开放、首屏与弹层分流一致,以及公开广场失败不污染创作错误态的边界。

View File

@@ -71,8 +71,9 @@ SELECT * FROM auth_store_snapshot WHERE snapshot_id = 'default';
### `user_account`
- 作用:用户账号主表,保存用户名、公开百梦号、手机号掩码、登录方式、密码登录开关token 版本。
- 结构:`user_id PK: String`, `public_user_code: String`, `username: String`, `display_name: String`, `avatar_url: Option<String>`, `phone_number_masked: Option<String>`, `phone_number_e164: Option<String>`, `login_method: String`, `binding_status: String`, `wechat_bound: bool`, `password_hash: Option<String>`, `password_login_enabled: bool`, `token_version: u64`
- 作用:用户账号主表,保存用户名、公开百梦号、手机号掩码、登录方式、密码登录开关token 版本和默认不前端展示的运营标签
- 结构:`user_id PK: String`, `public_user_code: String`, `username: String`, `display_name: String`, `avatar_url: Option<String>`, `phone_number_masked: Option<String>`, `phone_number_e164: Option<String>`, `login_method: String`, `binding_status: String`, `wechat_bound: bool`, `password_hash: String`, `password_login_enabled: bool`, `token_version: u64`, `user_tags: Vec<String>`
- 说明:`user_tags` 默认空数组,只允许后端白名单投影到特定业务接口;不得在登录态、个人资料等通用前端响应中直接暴露。
- 索引:`username`, `public_user_code`
```sql
@@ -255,10 +256,11 @@ SELECT * FROM profile_redeem_code_usage WHERE user_id = '<user_id>';
### `profile_invite_code`
- 作用:用户邀请中心的邀请码主表,也承载后台运营预置邀请码。
- 结构:`user_id PK: String`, `invite_code: String`, `metadata_json: String`, `created_at: Timestamp`, `updated_at: Timestamp`
- 作用:用户邀请中心的邀请码主表,也承载后台运营预置邀请码和使用后授予账号的运营标签
- 结构:`user_id PK: String`, `invite_code: String`, `metadata_json: String`, `created_at: Timestamp`, `updated_at: Timestamp`, `starts_at: Option<Timestamp>`, `expires_at: Option<Timestamp>`, `granted_user_tags: Vec<String>`
- 索引:主键 `user_id`,唯一索引 `invite_code`
- 后台读取:`GET /admin/api/profile/invite-codes` 只返回 `user_id``admin:` 开头的后台预置码;普通用户自己的邀请码不得进入后台运营列表。
- 说明:`granted_user_tags` 默认空数组;用户注册填写该邀请码后合并进 `user_account.user_tags`,不回改历史用户。
```sql
SELECT * FROM profile_invite_code WHERE user_id = '<user_id>';
@@ -661,8 +663,9 @@ SELECT * FROM match3d_agent_message WHERE session_id = '<session_id>' ORDER BY c
### `match3d_work_profile`
- 作用:抓大鹅 Match3D 作品主表,保存作品基础信息、配置、发布状态游玩次数。
- 结构:`profile_id PK: String`, `owner_user_id: String`, `source_session_id: String`, `author_display_name: String`, `game_name: String`, `theme_text: String`, `summary_text: String`, `tags_json: String`, `cover_image_src: String`, `cover_asset_id: String`, `clear_count: u32`, `difficulty: u32`, `config_json: String`, `publication_status: String`, `play_count: u32`, `updated_at: Timestamp`, `published_at: Option<Timestamp>`
- 作用:抓大鹅 Match3D 作品主表,保存作品基础信息、配置、发布状态游玩次数和草稿生成出的独立物品素材引用
- 结构:`profile_id PK: String`, `owner_user_id: String`, `source_session_id: String`, `author_display_name: String`, `game_name: String`, `theme_text: String`, `summary_text: String`, `tags_json: String`, `cover_image_src: String`, `cover_asset_id: String`, `clear_count: u32`, `difficulty: u32`, `config_json: String`, `publication_status: String`, `play_count: u32`, `updated_at: Timestamp`, `published_at: Option<Timestamp>`, `generated_item_assets_json: Option<String>`
- 说明:`generated_item_assets_json` 保存 `Match3DGeneratedItemAsset` 数组 JSON用于草稿页退出后从作品架重进时恢复 `3D素材` Tab 中的切割图片预览;基础信息自动保存和发布必须保留该字段。
- 索引:`owner_user_id`, `publication_status`
```sql

View File

@@ -0,0 +1,102 @@
# 用户标签、邀请码授予与拼图榜单展示方案
更新时间:`2026-05-10`
## 1. 目标
本次新增用户标签系统的最小闭环:
1. `user_account` 增加账号标签字段,默认空。
2. 后台预置邀请码可配置授予标签。
3. 用户填写带标签的邀请码后,把标签合并到自己的账号。
4. 标签默认不在前端资料页、邀请中心或通用接口展示。
5. 拼图排行榜仅对白名单标签做展示,首版只展示 `北科`
## 2. 数据字段
### `user_account.user_tags`
- 类型:`Vec<String>`
- 默认:空数组。
- 语义:账号级运营标签,属于后台与服务端投影数据,不作为普通前端个人资料字段。
- 写入:首版只由邀请码兑换链路合并写入。
- 迁移:旧迁移包和旧数据库按空数组兼容。
### `profile_invite_code.granted_user_tags`
- 类型:`Vec<String>`
- 默认:空数组。
- 语义:使用该邀请码后授予被邀请账号的标签列表。
- 范围:后台运营预置码和普通用户个人邀请码都可存字段,但后台表单首版只允许管理员配置预置码。
- 迁移:旧邀请码按空数组兼容。
## 3. 标签归一化
标签由 `module-runtime` 提供统一归一化:
1. trim 后为空的标签丢弃。
2. 同一邀请码或同一账号内标签去重,保留首次出现顺序。
3. 单个标签最长 `16` 字符,单次最多 `8` 个标签。
4. 当前不做中英文互译,不把中文标签改写为英文。
## 4. 邀请码授予流程
用户填写邀请码时,后端按原有校验顺序完成身份、绑定状态、邀请码存在、自邀请与时间窗校验。校验通过后:
1. 写入 `profile_referral_relation`
2. 发放原有双方奖励。
3. 读取 `profile_invite_code.granted_user_tags`
4. 将这些标签合并进 `user_account.user_tags`
管理员更新邀请码时,`grantedUserTags` 代表覆盖该邀请码之后授予的标签集合;空数组代表不授予标签。更新邀请码不会回溯修改已经使用过该邀请码的账号。
## 5. API 契约
后台邀请码 upsert 请求增加:
```json
{
"inviteCode": "BEIKE2026",
"grantedUserTags": ["北科"],
"metadata": {},
"startsAt": null,
"expiresAt": null
}
```
后台邀请码列表和 upsert 返回增加同名字段:
```json
{
"inviteCode": "BEIKE2026",
"grantedUserTags": ["北科"]
}
```
用户侧邀请中心、账号资料、登录返回和普通 profile 接口不返回 `userTags`
## 6. 拼图排行榜展示
拼图排行榜返回项增加 `visibleTags: string[]`
服务端构造排行榜时,从 `user_account.user_tags` 中仅投影允许公开展示的标签。首版公开展示白名单:
```text
北科
```
前端拼图通关弹窗按如下规则展示:
1. 昵称保持第一行。
2. `visibleTags` 非空时在昵称下方显示小标签。
3. 没有 `visibleTags` 时不占额外文案。
4. 本地兜底 run 的榜单 `visibleTags` 始终为空。
## 7. 验收
1. 新账号 `user_account.user_tags` 默认为空。
2. 后台创建邀请码时可填写 `北科`,列表和结果面板可回显。
3. 用户填写该邀请码后,账号表 `user_tags` 包含 `北科`
4. 不带标签的邀请码不改变账号标签。
5. 拼图排行榜中带 `北科` 的用户昵称下方显示 `北科`,其它标签不显示。
6. 执行 `npm run check:encoding``cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml`,并按影响范围执行后台 typecheck / 拼图前端测试。

View File

@@ -110,12 +110,13 @@ VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS=180000
1. 所有 GPT-image-2 生图请求都走 `POST {VECTOR_ENGINE_BASE_URL}/v1/images/generations`
2. 请求体 `model = gpt-image-2-all`,尺寸为 VectorEngine 支持的像素尺寸。
3. 请求体不再包含 `official_fallback`
4. 参考图字段使用 `image`,不再使用 APIMart 的 `image_urls`
4. 参考图使用 `POST {VECTOR_ENGINE_BASE_URL}/v1/images/generations`;有参考图且处于 AI 重绘时改走 `POST {VECTOR_ENGINE_BASE_URL}/v1/images/edits`;入口页关闭 AI 重绘时直接应用上传图,不调用图片生成
5. 拼图入口页上传图生成首图后,返回的首关 `pictureReference` 保留该 Data URL结果页重新生成在用户未重新上传参考图时会继续把 `pictureReference` 作为 `referenceImageSrc` 传给后端。
6. 缺少 `VECTOR_ENGINE_BASE_URL``VECTOR_ENGINE_API_KEY` 时返回 `503 SERVICE_UNAVAILABLE``details.provider = "vector-engine"`
7. 上游错误映射为 `502 UPSTREAM_ERROR`,保留 `upstreamStatus`、业务 message 和截断后的 raw excerpt
8. 运行 `npm run check:encoding``cargo test -p api-server openai_image --manifest-path server-rs/Cargo.toml``cargo test -p api-server puzzle --manifest-path server-rs/Cargo.toml``cargo test -p api-server custom_world_ai --manifest-path server-rs/Cargo.toml``cargo test -p api-server character_visual --manifest-path server-rs/Cargo.toml`
9. 后端改动后使用 `npm run api-server` 重启,并确认 `/healthz`
6. 拼图有参考图时,后端 prompt 会显式要求以参考图为第一优先级,保留主体、构图、视角、姿态、配色和光影氛围;入口页会把参考图压到单边 1024 以内,后端拒绝超过 8MB 的参考图字节
7. 缺少 `VECTOR_ENGINE_BASE_URL``VECTOR_ENGINE_API_KEY` 时返回 `503 SERVICE_UNAVAILABLE``details.provider = "vector-engine"`
8. 上游错误映射为 `502 UPSTREAM_ERROR`,保留 `upstreamStatus`、业务 message 和截断后的 raw excerpt
9. 运行 `npm run check:encoding``cargo test -p api-server openai_image --manifest-path server-rs/Cargo.toml``cargo test -p api-server puzzle --manifest-path server-rs/Cargo.toml``cargo test -p api-server custom_world_ai --manifest-path server-rs/Cargo.toml``cargo test -p api-server character_visual --manifest-path server-rs/Cargo.toml`
10. 后端改动后使用 `npm run api-server` 重启,并确认 `/healthz`
## 拼图链路排障日志