3D 允许 projectId 为空的文档与共享记忆同步
- 实施计划(API 契约):交付验收补上「只给 assetFolderId 合法」这一条。 - 里程碑(API 契约):落点口径写成「两个都给合法、只给 assetFolderId(无画布工程)同样合法」。 - 实施计划(前端入口):发送门禁改成「两个落点至少给一个」,projectId 可空、缺失时不发该字段,没有工程时结果只落素材库。 - 共享记忆 decision-log:在 G5 条目的「代价与取舍」里把「3D 仍要求 projectId」改成「同日按用户决定改为与其它工具一致」,并追加一条收尾说明(门禁常量改名、canvasCompletion 只随 projectId 下发、无工程时的读回边界)。 - 验证:node scripts/check-doc-index.mjs(221 份 Markdown 通过)、npm run check:encoding、git diff --check 通过。
This commit is contained in:
@@ -13,7 +13,7 @@ Milestone Spec: `docs/project-memory/plans/【里程碑】Tripo生成API契约
|
||||
- `common/` 新增平台字段类型:`Model3dGenerationSource`(resource / asset 二选一)、结果落点引用(`Model3dGenerationTargetRef`,平坦可选的 `resourceId` / `assetId`,与请求落点同形)与产物元数据类型;请求侧落点与其它生成接口同形,用平坦的可选 `projectId` / `assetFolderId` / `assetLabel` / `canvasCompletion`,不引入 tagged enum。
|
||||
- 图片输入不属于 provider 参数:客户端只能给 `source`,`input` 由服务端解析后注入 provider 调用,避免绕过归属校验传任意 URL。
|
||||
- 新增按端点的严格结果 payload 类型(模型 artifact + 预览 artifact),二者都只携带正式资源引用与对象元数据。
|
||||
- 交付:类型定义、serde 组合测试(`projectId` 与 `assetFolderId` 都不给拒绝、两个都给合法、`canvasCompletion` 与“没有 `projectId`”同现拒绝、`source` 两个 ID 同现拒绝)。
|
||||
- 交付:类型定义、serde 组合测试(`projectId` 与 `assetFolderId` 都不给拒绝、两个都给合法、只给 `assetFolderId` 合法、`canvasCompletion` 与“没有 `projectId`”同现拒绝、`source` 两个 ID 同现拒绝)。
|
||||
- 验收:`cargo test --locked -p shared-contracts --manifest-path server-rs/Cargo.toml` 通过。
|
||||
|
||||
2. **ts-rs 目录绑定**
|
||||
|
||||
@@ -16,7 +16,7 @@ Parent Spec: `docs/technical/【技术方案】Tripo 3D生成API集成-2026-09-2
|
||||
2. 入口形态照 `music` / `spec`:工具栏一项入口 → 浮动子选项 → 各自一个提交面板。子项文案沿用后端 `phaseLabel`:「文生 3D 模型」「图生 3D 模型」。
|
||||
3. 价格**只**来自 `GET /api/editor/generation-pricing` 的 `model3d` 段(前端已有 `loadEditorGenerationPricing` 与运行时缓存)。段缺失即两个子面板都不可提交且不发请求;前端不写任何 3D 兜底数值,也不把缺价当 0。
|
||||
4. 参数永远全量非空提交:`model`、`texture`、`pbr`、`geometryQuality`、`quad`、`smartLowPoly`、`generateParts` 一律显式给出,不依赖后端默认值(provider 隐式默认会改变价格)。唯一例外是 `textureQuality`:后端 `api-server::tripo3d::validation` 显式拒绝 `texture=false` 时出现贴图档位,因此纯几何档位必须**不带**该字段,而不是给 `null`。
|
||||
5. 落点照现有画布生成工具的做法:**同时**发 `projectId`(发送门禁为 `projectId`)与当前素材夹 `assetFolderId` + `assetLabel`,画布占位框 `canvasCompletion: { dialogId, title, placeholder }` 在 `projectId && canvasCompletionPlaceholder` 时带上;结果因此同时落画布与素材库。
|
||||
5. 落点照现有画布生成工具的做法:**同时**发 `projectId`(可空,缺失时不发这个字段)与当前素材夹 `assetFolderId` + `assetLabel`,画布占位框 `canvasCompletion: { dialogId, title, placeholder }` 在 `projectId && canvasCompletionPlaceholder` 时带上;发送门禁是「两个落点至少给一个」,不再要求先保存工程。结果因此同时落画布与素材库,没有工程时只落素材库。
|
||||
6. 图生输入照现有参考图来源菜单:从画布中选择 / 上传图片 / 从项目素材中选择,最终收敛成契约允许的 `{ kind: "resource", resourceId }` 或 `{ kind: "asset", assetId }`;只允许一张,不接 multiview。
|
||||
7. `Idempotency-Key` 由前端铸造并绑定当次请求,用户主动重试铸造新键、不复用旧键。落地形态是「尝试代次 + 请求内容指纹」(`Model3dGenerationSubmission.resolveModel3dRequestKey`):同一份内容重复提交(双击)仍命中同一个 operation,内容一变(含换参考图、换上传图、换素材库图片)自动换键;重试换代次即换键。之所以把内容指纹并进键里,是因为会改内容的入口散落在 `ImageCanvasGenerationDialogModel` / `ImageCanvasUploadModel` 的通用函数中,靠「每处都记得重铸键」迟早漏一处。
|
||||
8. 失败与退款沿用既有链路:文案由后端归一为「3D 模型生成失败,请稍后重试。」,侧栏与面板展示失败原因与已退还泥点。
|
||||
|
||||
@@ -29,7 +29,7 @@ Implementation Plan: `docs/project-memory/plans/【实施计划】Tripo生成API
|
||||
## 验收标准
|
||||
|
||||
1. API 请求与结果契约可编译,并通过 ts-rs 生成到 `packages/shared/src/contracts/model3d/` 的对应目录;生成产物经 prettier 后提交。
|
||||
2. 请求落点用平坦可选字段:`projectId` 与 `assetFolderId` 至少给一个,两个都给合法(与其它画布生成工具一致);`canvasCompletion` 与“没有 `projectId`”同现失败。
|
||||
2. 请求落点用平坦可选字段:`projectId` 与 `assetFolderId` 至少给一个,两个都给合法,只给 `assetFolderId`(无画布工程)同样合法(与其它画布生成工具一致);`canvasCompletion` 与“没有 `projectId`”同现失败。
|
||||
3. `source` 同时给出两个 ID、或使用裸字符串时被拒绝。
|
||||
4. `external_generation_job` 新字段追加在结构体末尾且带显式默认值;`migration.rs`、表目录与生成绑定同步,`npm run check:spacetime-schema` 通过。
|
||||
5. checkpoint procedure 只在租约有效且 lease token 匹配时写入;过期或错误 token 被拒。
|
||||
|
||||
@@ -9420,7 +9420,8 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
|
||||
- 背景:G5 复核(对照图片 / 图标 / 音效 / 角色动作等所有画布生成工具的前后端)发现 3D 仍有三处自成一派:① 服务端要求「`projectId` 与 `assetFolderId` 恰好一个」,而其它工具两个都能给,且前端画布链路**始终**同时发 `projectId` + `assetFolderId`(当前素材夹) + `assetLabel`,落库时项目资源行与素材行两条都写;② 3D 前端提交只发 `projectId`,于是 3D 结果永远不进素材库,同一张画布上的其它生成工具却都会进;③ 结果里的落点引用还是 tagged enum(`{ kind: "projectResource" | "assetLibrary" }`),与请求侧的平坦落点形状不一致;素材库行也没有 `model3d` 分支,一旦真落了素材行,缩略图会拿 `objectKey`(模型 `.glb`)去换签名地址。
|
||||
- 决策:① 服务端校验放宽为「至少一个落点」:两个都不给 400 点名 `projectId`,`canvasCompletion` 与「没有 `projectId`」同现 400 点名 `canvasCompletion`,两个都给合法;预检对给出的每个落点逐个定点查,出错时字段名只在「只给一个」时点名该字段,两个都给无法归因就回落到 `target`。② `Model3dGenerationTargetRef` 从 tagged enum 改成与请求落点同形的平坦可选字段 `resourceId?` / `assetId?`:哪个落点写了行就带哪个 ID,两个都写就都带。③ 前端 `buildModel3dSubmissionPlan` 接收当前素材夹与素材名,与其它工具一样同时发 `projectId` + `assetFolderId` + `assetLabel`(缺省名回落本次结果标题),`useModel3dGenerationTask` 从画布生成链路拿到 `assetFolderId`;结果消费沿用既有画布路径(`applyGeneratedProjectSnapshot` 顺带刷新素材库),不再需要 3D 专用刷新。④ 素材库行加 `model3d` 分支:缩略图按预览图(`asset.src`)渲染,`objectKey`(模型本体)不参与图片签名,与画布 3D 卡片的 `objectKey={null}` 口径一致。
|
||||
- 原因:**同一件事在各工具间必须同形**——落点是「结果放哪儿」的同一语义,3D 没有理由单独要求二选一;其它工具「画布 + 素材库同时落」既是现状也是用户预期(同一画布上的产出应当都能在素材库里找到)。结果引用跟着请求形状走,消费方不需要为同一个概念准备两套类型。素材库行的隐患属于「不接就永远发现不了」的类型:后端投影已经把 `imageSrc` 指向预览、`objectKey` 指向模型本体(`project_editor_client_media_src`),前端再拿 `objectKey` 签名就会去加载 `.glb`。
|
||||
- 代价与取舍:3D 现在会像其它画布工具一样,每次都往素材库写一条素材行(落点是当前素材夹,默认 `project` → owner 默认素材夹),素材库体积与写入量随 3D 使用增加,这是与其它工具一致的既有代价。素材库行只做预览图渲染与拖入画布,行内不开 3D 查看器,要看模型仍需拖到画布后点「3D 预览」。3D **仍要求 `projectId`**(画布链路必须已保存工程,否则提示「请先保存当前画布工程」),这一点与「`projectId` 可空、只落素材库」的其它工具仍有差异,属于有意保留:3D 入口只在有画布的编辑器里,没有画布就没有 3D 查看入口。本分支未合并,无外部调用方,结果引用形状变化不需要兼容层。
|
||||
- 代价与取舍:3D 现在会像其它画布工具一样,每次都往素材库写一条素材行(落点是当前素材夹,默认 `project` → owner 默认素材夹),素材库体积与写入量随 3D 使用增加,这是与其它工具一致的既有代价。素材库行只做预览图渲染与拖入画布,行内不开 3D 查看器,要看模型仍需拖到画布后点「3D 预览」。3D **起初仍要求 `projectId`**(初稿理由是「3D 入口只在有画布的编辑器里」);同日按用户决定改为与其它工具完全一致——`projectId` 可空、只给 `assetFolderId` 时结果只落素材库,前端门禁由「必须有工程」改成「两个落点至少给一个」。本分支未合并,无外部调用方,结果引用形状变化不需要兼容层。
|
||||
- 2026-09-23 追加(同一需求收尾):`projectId` 也改成可空。前端 `buildModel3dSubmissionPlan` 的发送门禁从「必须有画布工程」(`MODEL3D_PROJECT_REQUIRED_MESSAGE`,已删除)改成「两个落点至少给一个」(`MODEL3D_TARGET_REQUIRED_MESSAGE`),placement 只带实际存在的字段、`canvasCompletion` 只随 `projectId` 下发;Rust 请求结构体的文档注释同步去掉「二选一」旧表述并重新生成绑定。没有工程时结果只落素材库(与其它画布工具在无工程时一致:不套用项目快照、也没有本地图层回流)。
|
||||
- 影响面:`server-rs/crates/shared-contracts/src/model3d/common/generation_target_ref.rs`(tagged enum → 平坦结构,`packages/shared/src/contracts/model3d/common/Model3dGenerationTargetRef.ts` 随之变宽)、`server-rs/crates/api-server/src/tripo3d/{validation.rs,target.rs,job.rs,worker.rs}`、`server-rs/crates/shared-contracts/tests/model3d_api_request_contract.rs`、`src/components/image-editor/model3d-generation/{Model3dGenerationSubmission.ts,useModel3dGenerationTask.ts}`、`src/components/image-editor/useImageCanvasGenerationSubmissionWorkflow.ts`、`src/components/image-editor/ImageCanvasAssetRowView.tsx`、技术方案 / 里程碑 / 实施计划三份 3D 文档与共享记忆。
|
||||
- 验证方式:`cargo test -p api-server tripo3d::`(56 passed,含 `target_accepts_both_locators_but_requires_at_least_one`、`flat_locator_maps_to_point_lookup_without_rewriting_ids`、`unavailable_field_names_the_given_locators`、`completed_result_is_strict_per_endpoint_and_free_of_provider_facts`)、`cargo test -p shared-contracts`、`npm run contracts:model3d:generate`、`npx vitest run src/components/image-editor/model3d-generation src/components/image-editor/ImageCanvasAssetRowView.test.tsx`(67 passed)、`npm run typecheck`、`npm run check:encoding`、`npm run check:rustfmt`、`node scripts/check-doc-index.mjs`、`git diff --check`。
|
||||
- 关联文档:[技术方案 Tripo 3D生成API集成](../../technical/【技术方案】Tripo 3D生成API集成-2026-09-21.md)、[里程碑 Tripo生成API契约与数据模型](../plans/【里程碑】Tripo生成API契约与数据模型-2026-09-21.md)、[实施计划 Tripo生成前端入口](../plans/【实施计划】Tripo生成前端入口-2026-09-21.md)、[实施计划 Tripo生成结果前端预览接入](../plans/【实施计划】Tripo生成结果前端预览接入-2026-09-21.md)。
|
||||
|
||||
Reference in New Issue
Block a user