3D 产物 content type 只认字节或槽位白名单,去掉兜底
- 删除 fallback_extension 与「未知声明原样放行 + 扩展名兜底」的归一逻辑 - 槽位白名单收敛为唯一映射表(content type ↔ 对象键扩展名),跨槽位类型返回 None - resolve_artifact_content_type 改为返回 Result:字节认不出且声明不在白名单时按 502 失败, 不再把 text/plain 之类的声明连同拼出来的 .glb 扩展名落进 OSS 与 asset_object - 错误体只回显截断到 64 字符的声明值,避免把任意长度的 provider 文本带进响应 - 重写 storage 测试:新增白名单外声明、跨槽位类型与字节认不出三类用例 - 同步技术方案:说明白名单范围与「不做任何兜底」的口径
This commit is contained in:
@@ -14,7 +14,7 @@ import {
|
||||
MODEL3D_GENERATION_FAILURE_MESSAGE,
|
||||
MODEL3D_GENERATION_TIMEOUT_MESSAGE,
|
||||
MODEL3D_IMAGE_SOURCE_REQUIRED_MESSAGE,
|
||||
MODEL3D_PROJECT_REQUIRED_MESSAGE,
|
||||
MODEL3D_TARGET_REQUIRED_MESSAGE,
|
||||
MODEL3D_TEXT_PROMPT_REQUIRED_MESSAGE,
|
||||
resolveModel3dGenerationErrorMessage,
|
||||
resolveModel3dImageSource,
|
||||
@@ -175,6 +175,26 @@ describe('buildModel3dSubmissionPlan', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('没有画布工程时只给素材夹:不带 projectId,也不带 canvasCompletion', () => {
|
||||
const result = buildModel3dSubmissionPlan({
|
||||
dialog: createDialog('model3d-text-to-model', { prompt: '木椅' }),
|
||||
projectId: ' ',
|
||||
canvasCompletion: CANVAS_COMPLETION,
|
||||
assetFolderId: 'project',
|
||||
pricing: PRICING,
|
||||
});
|
||||
expect(result.ok).toBe(true);
|
||||
if (!result.ok) {
|
||||
return;
|
||||
}
|
||||
expect(result.plan.body).not.toHaveProperty('projectId');
|
||||
expect(result.plan.body).not.toHaveProperty('canvasCompletion');
|
||||
expect(result.plan.body).toMatchObject({
|
||||
assetFolderId: 'project',
|
||||
assetLabel: '木椅',
|
||||
});
|
||||
});
|
||||
|
||||
it('素材夹落点只 trim,素材名缺省回落本次结果标题', () => {
|
||||
const result = buildModel3dSubmissionPlan({
|
||||
dialog: createDialog('model3d-text-to-model', { prompt: '木椅' }),
|
||||
@@ -227,15 +247,16 @@ describe('buildModel3dSubmissionPlan', () => {
|
||||
).not.toHaveProperty('textureQuality');
|
||||
});
|
||||
|
||||
it('缺工程、缺输入、缺定价都不组装请求', () => {
|
||||
it('缺落点、缺输入、缺定价都不组装请求', () => {
|
||||
const textDialog = createDialog('model3d-text-to-model');
|
||||
expect(
|
||||
buildModel3dSubmissionPlan({
|
||||
dialog: { ...textDialog, prompt: '木椅' },
|
||||
projectId: ' ',
|
||||
assetFolderId: ' ',
|
||||
pricing: PRICING,
|
||||
}),
|
||||
).toEqual({ ok: false, message: MODEL3D_PROJECT_REQUIRED_MESSAGE });
|
||||
).toEqual({ ok: false, message: MODEL3D_TARGET_REQUIRED_MESSAGE });
|
||||
expect(
|
||||
buildModel3dSubmissionPlan({
|
||||
dialog: textDialog,
|
||||
|
||||
@@ -25,8 +25,8 @@ import {
|
||||
export const MODEL3D_TEXT_PROMPT_REQUIRED_MESSAGE = '请填写 3D 模型描述';
|
||||
export const MODEL3D_IMAGE_SOURCE_REQUIRED_MESSAGE =
|
||||
'请先选择一张画布图片或素材库图片';
|
||||
export const MODEL3D_PROJECT_REQUIRED_MESSAGE =
|
||||
'请先保存当前画布工程,再生成 3D 模型';
|
||||
export const MODEL3D_TARGET_REQUIRED_MESSAGE =
|
||||
'请先保存当前画布工程或打开素材库,再生成 3D 模型';
|
||||
export const MODEL3D_GENERATION_FAILURE_MESSAGE =
|
||||
'生成 3D 模型失败,请稍后重试。';
|
||||
export const MODEL3D_GENERATION_TIMEOUT_MESSAGE =
|
||||
@@ -92,7 +92,7 @@ export function buildModel3dSubmissionPlan({
|
||||
canvasCompletion?: EditorCanvasGenerationCompletionInput | null;
|
||||
/**
|
||||
* 素材库落点:与其它画布生成工具一样,画布链路会把当前素材夹一起发出去,
|
||||
* 结果因此同时落画布与素材库。缺省或空串表示本次不落素材库。
|
||||
* 结果因此同时落画布与素材库;没有画布工程时它是唯一落点。缺省或空串表示本次不落素材库。
|
||||
*/
|
||||
assetFolderId?: string | null;
|
||||
/** 素材库里的展示名;缺省用本次结果的标题。 */
|
||||
@@ -102,9 +102,12 @@ export function buildModel3dSubmissionPlan({
|
||||
if (!isModel3dGenerationMode(dialog.mode)) {
|
||||
return { ok: false, message: MODEL3D_MODE_UNSUPPORTED_REASON };
|
||||
}
|
||||
// 与其它画布生成工具同一条门禁:落点至少给一个(画布工程或素材夹)。
|
||||
// 没有画布工程时仍然可以生成,结果只落素材库 —— 3D 不再要求「必须先保存工程」。
|
||||
const trimmedProjectId = projectId?.trim();
|
||||
if (!trimmedProjectId) {
|
||||
return { ok: false, message: MODEL3D_PROJECT_REQUIRED_MESSAGE };
|
||||
const trimmedAssetFolderId = assetFolderId?.trim();
|
||||
if (!trimmedProjectId && !trimmedAssetFolderId) {
|
||||
return { ok: false, message: MODEL3D_TARGET_REQUIRED_MESSAGE };
|
||||
}
|
||||
const quote = resolveModel3dGenerationQuote(dialog, pricing);
|
||||
if (!quote.ok) {
|
||||
@@ -138,16 +141,16 @@ export function buildModel3dSubmissionPlan({
|
||||
// 服务端按「至少一个落点」校验,项目资源与素材库可以同时给 —— 其它画布生成工具
|
||||
// 就是「projectId + assetFolderId + assetLabel」一起发,结果两边都落;3D 跟随同一口径,
|
||||
// 否则同一张画布生成出来的东西,只有 3D 不会出现在素材库。
|
||||
const trimmedAssetFolderId = assetFolderId?.trim();
|
||||
const placement = {
|
||||
projectId: trimmedProjectId,
|
||||
...(trimmedProjectId ? { projectId: trimmedProjectId } : {}),
|
||||
...(trimmedAssetFolderId
|
||||
? {
|
||||
assetFolderId: trimmedAssetFolderId,
|
||||
assetLabel: assetLabel?.trim() || resolveModel3dResultTitle(dialog),
|
||||
}
|
||||
: {}),
|
||||
...(canvasCompletion ? { canvasCompletion } : {}),
|
||||
// 画布占位框是项目资源落点的回填载荷:没有工程就没有画布可以回填。
|
||||
...(trimmedProjectId && canvasCompletion ? { canvasCompletion } : {}),
|
||||
};
|
||||
const finish = (
|
||||
body: Model3dTextToModelRequest | Model3dImageToModelRequest,
|
||||
|
||||
@@ -284,7 +284,7 @@ describe('useModel3dGenerationTask', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('画布工程还没保存时不发请求', async () => {
|
||||
it('没有任何落点(既没有工程也没有素材夹)时不发请求', async () => {
|
||||
const { result } = renderTask({ dialog: createDialog(), projectId: null });
|
||||
|
||||
await act(async () => {
|
||||
@@ -296,6 +296,31 @@ describe('useModel3dGenerationTask', () => {
|
||||
expect(result.current.dialog.errorMessage).toContain('保存当前画布工程');
|
||||
});
|
||||
|
||||
it('没有画布工程时照样提交:只发素材夹,不发 projectId 与 canvasCompletion', async () => {
|
||||
const submitResponse: Model3dGenerationSubmissionResponse = {
|
||||
operationId: 'task-2',
|
||||
jobKind: 'model3d_text_to_model',
|
||||
status: QUEUED_STATUS,
|
||||
statusUrl: '/api/runtime/external-generation/jobs/task-2',
|
||||
};
|
||||
submitModel3dGenerationRequestMock.mockResolvedValue(submitResponse);
|
||||
const { result } = renderTask({
|
||||
dialog: createDialog(),
|
||||
projectId: null,
|
||||
assetFolderId: 'project',
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await result.current.submitModel3dGeneration(createDialog());
|
||||
});
|
||||
|
||||
expect(submitModel3dGenerationRequestMock).toHaveBeenCalledTimes(1);
|
||||
const request = submitModel3dGenerationRequestMock.mock.calls[0][0];
|
||||
expect(request.body).not.toHaveProperty('projectId');
|
||||
expect(request.body).not.toHaveProperty('canvasCompletion');
|
||||
expect(request.body).toMatchObject({ assetFolderId: 'project' });
|
||||
});
|
||||
|
||||
it('非 3D 对话框不做任何事', async () => {
|
||||
const { result } = renderTask({
|
||||
dialog: createDialog({ mode: 'generate' }),
|
||||
|
||||
Reference in New Issue
Block a user