3D 生成前端提交体跟随平坦落点契约

- buildModel3dSubmissionPlan 不再拼 tagged target,把 projectId 与 canvasCompletion 直接摊进请求体;画布链路仍只落项目资源。
- submitModel3dGenerationRequest 的提交体注释同步落点字段改名。
- 请求体、幂等键与排队用例改为断言扁平落点字段。
- 定向验证:npx vitest run src/components/image-editor/model3d-generation src/components/image-editor/model3d-preview(63 passed)、npm run typecheck 通过。
This commit is contained in:
2026-09-23 14:02:57 +08:00
parent fd48829c24
commit f659ddfcf0
4 changed files with 13 additions and 16 deletions
@@ -138,11 +138,8 @@ describe('buildModel3dSubmissionPlan', () => {
smartLowPoly: false,
generateParts: false,
},
target: {
kind: 'projectResource',
projectId: 'project-1',
canvasCompletion: CANVAS_COMPLETION,
},
projectId: 'project-1',
canvasCompletion: CANVAS_COMPLETION,
});
});
@@ -172,7 +169,7 @@ describe('buildModel3dSubmissionPlan', () => {
smartLowPoly: false,
generateParts: true,
},
target: { kind: 'projectResource', projectId: 'project-1' },
projectId: 'project-1',
});
expect(result.plan.body).not.toHaveProperty('prompt');
// 后端显式拒绝 texture=false + textureQuality 的组合,缺位是契约要求。
@@ -322,7 +319,7 @@ describe('Idempotency-Key', () => {
endpoint: 'text-to-model',
body: {
generation: { model: 'v3.1-20260211', prompt: '木椅' },
target: { kind: 'projectResource', projectId: 'project-1' },
projectId: 'project-1',
},
}),
).toMatch(/^[\x21-\x7e]{1,128}$/);
@@ -334,7 +331,7 @@ describe('Idempotency-Key', () => {
endpoint: 'text-to-model',
body: {
generation: { texture: true, model: 'v3.1-20260211', prompt: '木椅' },
target: { kind: 'projectResource', projectId: 'project-1' },
projectId: 'project-1',
},
});
const right = resolveModel3dRequestKey({
@@ -342,7 +339,7 @@ describe('Idempotency-Key', () => {
endpoint: 'text-to-model',
body: {
generation: { model: 'v3.1-20260211', texture: true, prompt: '木椅' },
target: { kind: 'projectResource', projectId: 'project-1' },
projectId: 'project-1',
},
});
expect(right).toBe(left);
@@ -1,7 +1,6 @@
import type { ExternalGenerationJobStatusRecord } from '../../../../packages/shared/src/contracts/externalGeneration';
import type {
Model3dGenerationSource,
Model3dGenerationTarget,
Model3dImageToModelRequest,
Model3dTextToModelRequest,
} from '../../../../packages/shared/src/contracts/model3d';
@@ -126,8 +125,9 @@ export function buildModel3dSubmissionPlan({
smartLowPoly: params.smartLowPoly,
generateParts: params.generateParts,
};
const target: Model3dGenerationTarget = {
kind: 'projectResource',
// 落点与其它生成接口同形:平坦的可选字段,不再包一层 tagged enum。
// 服务端按「二选一」校验:这里只给 projectId(画布链路没有落素材库的入口)。
const placement = {
projectId: trimmedProjectId,
...(canvasCompletion ? { canvasCompletion } : {}),
};
@@ -153,14 +153,14 @@ export function buildModel3dSubmissionPlan({
if (!source) {
return { ok: false, message: MODEL3D_IMAGE_SOURCE_REQUIRED_MESSAGE };
}
return finish({ source, generation, target });
return finish({ source, generation, ...placement });
}
const prompt = dialog.prompt.trim();
if (!prompt) {
return { ok: false, message: MODEL3D_TEXT_PROMPT_REQUIRED_MESSAGE };
}
return finish({ generation: { ...generation, prompt }, target });
return finish({ generation: { ...generation, prompt }, ...placement });
}
/**
@@ -166,7 +166,7 @@ describe('useModel3dGenerationTask', () => {
smartLowPoly: false,
generateParts: false,
},
target: { kind: 'projectResource', projectId: 'project-1' },
projectId: 'project-1',
});
expect(applyQueuedGeneration).toHaveBeenCalledTimes(1);
@@ -1746,7 +1746,7 @@ export async function submitModel3dGenerationRequest({
idempotencyKey,
}: {
endpoint: Model3dPricingEndpoint;
// 提交体直接用端点契约:形状不符(缺 target、generation 结构错)在编译期就会暴露。
// 提交体直接用端点契约:形状不符(落点缺失、generation 结构错)在编译期就会暴露。
body: Model3dTextToModelRequest | Model3dImageToModelRequest;
idempotencyKey: string;
}) {