diff --git a/src/components/image-editor/model3d-generation/Model3dGenerationSubmission.ts b/src/components/image-editor/model3d-generation/Model3dGenerationSubmission.ts index 21f2c59c3..e2ee10ed7 100644 --- a/src/components/image-editor/model3d-generation/Model3dGenerationSubmission.ts +++ b/src/components/image-editor/model3d-generation/Model3dGenerationSubmission.ts @@ -27,6 +27,8 @@ export const MODEL3D_GENERATION_FAILURE_MESSAGE = '生成 3D 模型失败,请稍后重试。'; export const MODEL3D_GENERATION_TIMEOUT_MESSAGE = '3D 生成请求超时,请稍后重试。'; +export const MODEL3D_SUBMISSION_MISSING_QUEUE_STATE_MESSAGE = + '3D 生成任务已提交,但服务端没有返回任务状态,请稍后重试。'; export type Model3dSubmissionPlan = { endpoint: Model3dPricingEndpoint; diff --git a/src/components/image-editor/model3d-generation/useModel3dGenerationTask.test.tsx b/src/components/image-editor/model3d-generation/useModel3dGenerationTask.test.tsx index a67b977f9..b10b6b038 100644 --- a/src/components/image-editor/model3d-generation/useModel3dGenerationTask.test.tsx +++ b/src/components/image-editor/model3d-generation/useModel3dGenerationTask.test.tsx @@ -22,6 +22,7 @@ import { createModel3dGenerationDraftFields, MODEL3D_PRICING_UNAVAILABLE_REASON, } from './Model3dGenerationFormModel'; +import { MODEL3D_SUBMISSION_MISSING_QUEUE_STATE_MESSAGE } from './Model3dGenerationSubmission'; import { useModel3dGenerationTask } from './useModel3dGenerationTask'; const submitModel3dGenerationRequestMock = vi.hoisted(() => vi.fn()); @@ -218,6 +219,26 @@ describe('useModel3dGenerationTask', () => { expect(result.current.dialog.composerOpen).toBe(true); }); + it('2xx 但没有任务状态时给出失败提示,而不是静默复位', async () => { + // requestJson 在空 body 时返回 null,这里覆盖「成功但没有 status」的形态。 + submitModel3dGenerationRequestMock.mockResolvedValue(null); + const applyQueuedGeneration = vi.fn(async () => true); + const { result } = renderTask({ + dialog: createDialog(), + applyQueuedGeneration, + }); + + await act(async () => { + await result.current.submitModel3dGeneration(createDialog()); + }); + + expect(applyQueuedGeneration).not.toHaveBeenCalled(); + expect(result.current.dialog.status).toBe('failed'); + expect(result.current.dialog.errorMessage).toBe( + MODEL3D_SUBMISSION_MISSING_QUEUE_STATE_MESSAGE, + ); + }); + it('请求失败时留在面板上展示失败原因', async () => { submitModel3dGenerationRequestMock.mockRejectedValue( new Error('3D 模型生成失败,请稍后重试。'), diff --git a/src/components/image-editor/model3d-generation/useModel3dGenerationTask.ts b/src/components/image-editor/model3d-generation/useModel3dGenerationTask.ts index d19517121..666d00be8 100644 --- a/src/components/image-editor/model3d-generation/useModel3dGenerationTask.ts +++ b/src/components/image-editor/model3d-generation/useModel3dGenerationTask.ts @@ -14,6 +14,7 @@ import { readEditorModel3dPricingConfig } from '../ImageCanvasGenerationModel'; import { isModel3dGenerationMode } from './Model3dGenerationFormModel'; import { buildModel3dSubmissionPlan, + MODEL3D_SUBMISSION_MISSING_QUEUE_STATE_MESSAGE, model3dQueueStateFromResponse, resolveModel3dGenerationErrorMessage, resolveModel3dResultTitle, @@ -106,9 +107,19 @@ export function useModel3dGenerationTask({ idempotencyKey: planResult.plan.requestKey, }); const queueState = model3dQueueStateFromResponse(response); + if (!queueState) { + // 2xx 但没有任务状态(空 body 或缺 status 的 payload)不能当成功静默复位: + // 用户既看不到任务也没有任何提示,服务端细节被丢掉。 + updateCanvasGenerationDialogById(dialogId, (currentDialog) => ({ + ...currentDialog, + status: 'failed', + composerOpen: true, + errorMessage: MODEL3D_SUBMISSION_MISSING_QUEUE_STATE_MESSAGE, + })); + return; + } if ( - queueState && - (await applyQueuedGeneration( + await applyQueuedGeneration( { queueState }, projectId, applyProjectSnapshot, @@ -116,7 +127,7 @@ export function useModel3dGenerationTask({ onWalletBalanceMayHaveChanged, onGenerationWarning, dialogId, - )) + ) ) { return; }