3D 生成面板开放模型版本选择,参数按模型能力门禁
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m35s
Project CI / Backend tests (pull_request) Failing after 14s
Project CI / AI game creator shell Rust smoke (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled

- Model3dGenerationValidation.ts(新):前端唯一一份参数校验,逐条复刻 platform-tripo 的 validate_generation_options 与 api-server 的 PricingParamView::validate(模型族能力、分件三个前置条件、face_limit_bounds、平台必显式项),并给出按契约联合类型做键的 MODEL3D_MODEL_TABLE 与禁用原因文案
- Model3dGenerationFormModel:模型版本改为用户可选,可选集只取当前端点有底价的版本;报价与请求体读同一个 resolveModel3dModelVersion;换版本或切档位时按新能力收敛档位与开关并开新一代次;v2.5 的贴图档位不带 textureQuality
- Model3dGenerationForm:新增模型版本选择器(照图片面板的浮层选项形态),档位与开关按模型能力禁用并给出 title 原因
- Model3dGenerationModal:定价只读一次,模型选项与报价共用同一份快照
- Model3dGenerationSubmission / useModel3dGenerationTask:提交计划改成 endpoint 判别联合,两个端点各一个收口函数
- editorProjectClient:删掉「endpoint + 联合提交体」的单函数,改为 submitModel3dTextToModelRequest / submitModel3dImageToModelRequest 一个端点一个函数;basePrices 两层键改成契约枚举
- ImageCanvasOptionChoice.tsx(新):把图片面板的浮层选项行抽成共享组件,3D 面板复用
- 测试:新增 Model3dGenerationValidation.test.ts(12 例),FormModel / Submission / Modal / useModel3dGenerationTask 补组合表与菜单用例,共 75 例通过
- 文档:实施计划与里程碑追加本轮口径与证据,decision-log 与 pitfalls 各记两条
This commit is contained in:
2026-09-24 14:55:06 +08:00
parent cc095a7620
commit 42c3186e13
19 changed files with 1619 additions and 148 deletions
@@ -1,5 +1,27 @@
# 决策记录
## 2026-09-24 3D 参数面板开放模型选择:版本取「本端点有底价」的键,能力规则抄 provider,端点与提交体由类型绑死
- 背景:3D 面板此前把请求体与报价里的 `model` 一起写死成 `DEFAULT_MODEL3D_MODEL_VERSION`(`v3.1-20260211`),而公开读模型的定价段是按 `endpoint × modelVersion` 展开的;后端加载即校验「每个契约版本 × 两个端点都必须有底价」,于是定价表里已配好的 `v3.0` / `v2.5` / `P1` / `P2` 四个版本在前端永远不可达,用户也看不到任何模型选择入口。同一轮还暴露两个类型层面的口子:提交函数签名是「endpoint 联合 + 提交体联合」两个彼此独立的字段,把图生请求体发到文生地址照样编译通过;`Model3dPricingConfig.basePrices` 内层键写成 `string`,写错的版本号在编译期没有任何反馈。
- 决策:
1. `GenerateDialogState.model3dModel` 成为用户可选参数,缺省 `v3.1-20260211`;面板选项只列**当前端点有底价**的版本(`resolveModel3dModelOptions`),报价与请求体的 `generation.model` 读同一个 `resolveModel3dModelVersion(dialog.model3dModel)`。
2. 换版本 / 切档位时按新版本能力收敛参数(高清贴图 → 标准贴图、关掉该版本不支持的开关)并开新一代次,面板上不留已经失效的勾选。
3. 前端保留一份与 provider 对齐的参数校验 `model3d-generation/Model3dGenerationValidation.ts`,逐条复刻 `platform-tripo::common::validation::validate_generation_options` 与 `api-server::tripo3d::validation::PricingParamView::validate`:模型族能力、分件的三个前置条件、`face_limit_bounds`、平台必显式项。抄的是规则不是数值,两端顺序一致(平台口径在前),冲突以 provider 为准。
4. 提交端点与提交体由类型绑死:`submitModel3dTextToModelRequest` / `submitModel3dImageToModelRequest` 一个端点一个函数,`Model3dSubmissionPlan` 做成 `endpoint` 判别联合;`Model3dPricingConfig.basePrices` 的两层键改成契约枚举(`Partial<Record<Model3dPricingEndpoint, Partial<Record<Model3dModelVersion, Model3dBasePrice>>>>`)。
- 原因:价格与参数是同一件事的两面——定价按版本分档,把版本写死等于让其余四个版本的定价成为死配置;面板不给版本选择,用户既看不到这些版本,也不可能用上已经付费配置的档位。校验必须抄一份到前端,是因为契约与公开读模型只暴露「价格」,不暴露「哪个版本支持哪些参数」,不抄就只能等用户点提交后由 provider 的字段级报错来教育。类型收口(判别联合 + 契约枚举做键)让同类端点 / 版本错配在 `tsc` 阶段暴露,而不是等远端按字段报错。
- 代价与取舍:前端多了一份 provider 规则镜像,provider 改规则时两边要一起改;用 `Model3dGenerationValidation.test.ts`(12 例,含五个版本的能力与面数边界)与 `Model3dGenerationFormModel.test.ts` 的组合表把两边钉住,冲突时以 provider 为准(失败关闭)。运行期的「契约版本列表」仍只能靠前端常量表 `MODEL3D_MODEL_TABLE`:ts-rs 只导出联合**类型**,导不出运行期数组,所以表写成 `Record<Model3dModelVersion, …>`,契约新增版本后 `npm run contracts:model3d:generate` 会让这里直接编译失败。更干净的做法是后端公开读模型补一份 `modelCapabilities`(版本 → 支持参数 → 加价项),前端就不用镜像规则,本轮未做。
- 影响面:`src/components/image-editor/model3d-generation/*`、`src/components/image-editor/ImageCanvasOptionChoice.tsx`(新,选项行抽共享)、`src/components/image-editor/ImageCanvasGenerationImageOptionsView.tsx`、`src/components/image-editor/ImageCanvasEditorTypes.ts`、`src/services/image-editor/editorProjectClient.ts`、[实施计划](../plans/【实施计划】Tripo生成前端入口-2026-09-21.md)、[里程碑](../plans/【里程碑】Tripo生成前端入口-2026-09-21.md)。
- 验证方式:`npx vitest run src/components/image-editor/model3d-generation`(75 passed)、`npx vitest run src/components/image-editor/ImageCanvasGenerationComposerView.test.tsx`、`npm run typecheck`、`npx eslint`(改动文件无告警)。
## 2026-09-24 画布底部工具栏改用换行兜溢出,不再靠横向滚动
- 背景:13 个工具 + 3 个分隔符放进宿主限宽的 `.image-canvas-editor__bottom-toolbar`(`max-width: min(calc(100% - 6.6rem), 34rem)`,约 614px)后必然溢出,共享的 `.genarrative-image-canvas__toolbar` 用 `overflow-x: auto` + `scrollbar-width: thin` 兜住——细到几乎不可见的滚动条让尾部工具(3D 入口等)在桌面端直接「消失」,用户只会看到工具栏被截断。
- 决策:共享工具栏改 `flex-wrap: wrap`(只换行、不再滚动),宿主上限 34rem → 42rem 并 `justify-content: center`;AGC 资源画布的宿主覆盖删掉与共享默认重复的 `overflow` 覆盖,只保留限宽。
- 原因:工具条是「入口清单」,被截断等于功能不存在;换行是唯一在任意宽度下都能保证每个入口可见且可点的方案,横向滚动在隐藏滚动条的容器里连「还能滚」这件事都传达不到。
- 代价与取舍:工具变多时工具栏会占两行、抬高画布底部,视觉上不如一行整齐;换来的是入口不再被容器宽度吃掉。判据固定为 `scrollWidth === clientWidth` 且最后一个按钮完整可见。
- 影响面:`packages/image-canvas-react/src/{CanvasChrome.tsx,styles.css}`、`src/index.css`、`apps/ai-game-creator-shell/src/features/resource-canvas/resourceCanvasChrome.css`。
- 验证方式:`npx vitest run src/components/image-editor/ImageCanvasBottomToolbarView.test.tsx`、`npx vitest run packages/image-canvas-react`;1280px 视口实测不再有横向溢出。
## 2026-09-24 Tripo 配置改为启动期失败关闭:网关无内置默认值,缺配置的进程直接起不来
- 背景:`TRIPO_BASE_URL` / `TRIPO_API_KEY` 原来只有请求期一道判断,而请求期根本挡不住部署错误:HTTP 角色收 3D 单时不碰 provider,worker 只在自己真的跑到那个 job 时才构造 `TripoSettings`,于是缺配置的部署形态是「进程起来了、`/healthz` 全绿、用户拿到 202」,失败只在任务记录里现身。同时 `AppConfig::default()` 带着内置网关 `https://openapi.tripo3d.com/v3`,`tripo_settings()` 又把超时钳到 `.max(1)`、重试钳到 `.min(10)`,一个写错的配置会被翻译成「能跑但行为诡异」。
@@ -1,5 +1,37 @@
# 踩坑与排障记录
## 隐藏滚动条的横向滚动容器不等于「放得下」
- **现象**:桌面端画布底部工具栏尾部几个工具(含「生成 3D 模型」入口)看不到,用户反馈成「工具栏变成横向可滚动的了」「是不是限宽了」。
- **原因**:共享 `.genarrative-image-canvas__toolbar` 用 `overflow-x: auto` + `scrollbar-width: thin` 处理溢出,宿主 `.image-canvas-editor__bottom-toolbar` 又把宽度限到约 614px;13 个工具必然溢出,但细滚动条在桌面端几乎不可见,也没有「还有更多」的任何提示,尾部工具等同于消失。
- **处理(现行口径)**:工具栏 `flex-wrap: wrap`,只换行不滚动;宿主上限放宽并居中,AGC 宿主不再重复覆盖。验收判据固定为 `scrollWidth === clientWidth` 且最后一个按钮完整可见,不能只看「滚动条能不能拖」。
- **验证**:`npx vitest run src/components/image-editor/ImageCanvasBottomToolbarView.test.tsx` 与 `packages/image-canvas-react` 用例;1280px 视口实测工具栏单行、尾部工具可见。
- **关联**:`packages/image-canvas-react/src/CanvasChrome.tsx`、`packages/image-canvas-react/src/styles.css`、`src/index.css`。
## 定价按模型版本分档时,前端不能把版本写死成常量
- **现象**:3D 生成面板没有任何模型选择入口,四个已经在定价表里配好底价的版本(`v3.0` / `v2.5` / `P1` / `P2`)在前端完全不可达;提交的 `generation.model` 永远是 `v3.1-20260211`。
- **原因**:请求体与报价都把 `model` 写死成 `DEFAULT_MODEL3D_MODEL_VERSION`,而公开读模型的 `basePrices` 是按 `endpoint × modelVersion` 展开的;面板不做版本选择,也就没人去读那些键,定价配置里多数行成了死数据。
- **处理(现行口径)**:版本由用户选,选项取「当前端点有底价」的版本;报价与请求体读同一个 `resolveModel3dModelVersion(dialog.model3dModel)`;换版本时按新版本能力收敛档位与开关并开新一代次。
- **验证**:`Model3dGenerationFormModel.test.ts`(`resolveModel3dModelOptions` 两个端点各一例、缺价版本不可提交)、`Model3dGenerationModal.test.tsx`(选项不含无价版本、切版本后报价跟着换)。
- **关联**:`src/components/image-editor/model3d-generation/Model3dGenerationFormModel.ts`、`Model3dGenerationSubmission.ts`、`src/services/image-editor/editorProjectClient.ts`。
## 「endpoint 联合 + 提交体联合」是两个独立的联合,配错端点编译能过
- **现象**:3D 生成提交函数的签名是 `{ endpoint: 'text-to-model' | 'image-to-model'; body: TextRequest | ImageRequest }`,把图生请求体发到文生地址没有任何类型错误,只能等远端按字段报错;同类口子还有定价类型的 `basePrices` 内层键写成 `string`,写错的模型版本号在编译期无反馈。
- **原因**:两个联合类型彼此独立,类型系统不要求它们同进同退;只要两边各自合法,任意组合都成立。写死的常量(默认模型版本)也属于同一类问题:类型正确、语义错位。
- **处理(现行口径)**:提交函数按端点一分为二(端点字面量与提交体类型在同一个签名里绑死),提交计划改成 `endpoint` 判别联合;`MODEL3D_MODEL_TABLE` 用 `Record<Model3dModelVersion, …>`,定价键改成契约枚举。契约新增版本 / 端点时先编译失败,而不是先上生产。
- **验证**:`useModel3dGenerationTask.test.tsx`(图生只走图生函数、文生函数不被调用)、`npm run typecheck`;`Model3dGenerationValidation.test.ts` 覆盖契约声明的 5 个版本。
- **关联**:`src/services/image-editor/editorProjectClient.ts`、`src/components/image-editor/model3d-generation/Model3dGenerationSubmission.ts`。
## 面板禁用态不能只靠「后端会拒」兜底
- **现象**:用户能选到必然被 provider 拒的组合(v2.5 + 贴图档位、P2 + 高清贴图、分件 + 贴图档位),提交后拿到字段级错误,白等一次往返,且错误文案对用户没有指导意义。
- **原因**:契约与公开读模型只暴露价格,不暴露「哪个版本支持哪些参数」;前端此前的组合判定只覆盖分件与贴图档位,其余全交给 provider。
- **处理(现行口径)**:前端保留 `model3d-generation/Model3dGenerationValidation.ts` 镜像 provider 与平台侧规则(抄规则不抄数值,冲突以 provider 为准),面板的禁用态与禁用原因读同一份常量;长期正解是后端读模型补 `modelCapabilities`,让前端不再维护第二份规则。
- **验证**:`Model3dGenerationValidation.test.ts`(12 例,含五个版本的能力与面数边界)、`Model3dGenerationFormModel.test.ts` 的档位 × 开关组合表。
- **关联**:`src/components/image-editor/model3d-generation/Model3dGenerationValidation.ts`、`server-rs/crates/platform-tripo/src/common/validation.rs`、`server-rs/crates/api-server/src/tripo3d/validation.rs`。
## Direct 宿主继续请求不能重发原始用户条目
原始 `direct_user_item` 同时参与历史持久化和模型输入转换;验收或错误反馈更新了 prompt 后,如果发送层仍优先转换原始条目,模型会收到重复的用户输入,而本地历史按 itemId 去重后只显示一次。首次请求与宿主继续必须显式区分:首次保留结构化输入,继续发送当次反馈,原始条目只保留历史与事件关联职责。GUI、CLI 的两条循环都要覆盖;只改反馈文本或清空原始条目不完整。见 [Direct 宿主继续请求输入修复](../../technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md#2026-09-23-direct-宿主继续请求输入修复)。