From 62f1ca45376df2dba6fcce464ec21fa2531622aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Tue, 22 Sep 2026 16:57:55 +0800 Subject: [PATCH] =?UTF-8?q?3D=20=E6=8F=90=E4=BA=A4=E5=A4=B1=E8=B4=A5?= =?UTF-8?q?=E6=8F=90=E7=A4=BA=E4=B8=8D=E5=86=8D=E7=9B=B4=E6=8E=A5=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E4=BC=A0=E8=BE=93=E5=B1=82=E8=8B=B1=E6=96=87=E6=96=87?= =?UTF-8?q?=E6=A1=88=20-=20resolveModel3dGenerationErrorMessage=20?= =?UTF-8?q?=E5=AF=B9=E8=B6=85=E6=97=B6=E5=8D=95=E7=8B=AC=E7=BB=99=E5=87=BA?= =?UTF-8?q?=E4=B8=AD=E6=96=87=E5=8F=AF=E9=87=8D=E8=AF=95=E6=8F=90=E7=A4=BA?= =?UTF-8?q?=20-=20fetch=20=E5=A4=B1=E8=B4=A5=E4=B8=8E=E4=B8=AD=E6=96=AD?= =?UTF-8?q?=E6=94=B6=E6=95=9B=E5=88=B0=E4=B8=AD=E6=96=87=E5=85=9C=E5=BA=95?= =?UTF-8?q?=EF=BC=8C=E6=9C=8D=E5=8A=A1=E7=AB=AF=E6=96=87=E6=A1=88=E4=BB=8D?= =?UTF-8?q?=E5=8E=9F=E6=A0=B7=E9=80=8F=E5=87=BA=20-=20=E8=A1=A5=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E7=AB=AF=E6=96=87=E6=A1=88=E9=80=8F=E5=87=BA=E3=80=81?= =?UTF-8?q?=E4=BC=A0=E8=BE=93=E5=B1=82=E5=85=9C=E5=BA=95=E4=B8=8E=E8=B6=85?= =?UTF-8?q?=E6=97=B6=E6=8F=90=E7=A4=BA=E7=9A=84=E7=94=A8=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Model3dGenerationSubmission.test.ts | 39 +++++++++++++++++++ .../Model3dGenerationSubmission.ts | 22 ++++++++++- 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/src/components/image-editor/model3d-generation/Model3dGenerationSubmission.test.ts b/src/components/image-editor/model3d-generation/Model3dGenerationSubmission.test.ts index 3fc42c3e6..7f63af339 100644 --- a/src/components/image-editor/model3d-generation/Model3dGenerationSubmission.test.ts +++ b/src/components/image-editor/model3d-generation/Model3dGenerationSubmission.test.ts @@ -11,9 +11,12 @@ import { } from './Model3dGenerationFormModel'; import { buildModel3dSubmissionPlan, + MODEL3D_GENERATION_FAILURE_MESSAGE, + MODEL3D_GENERATION_TIMEOUT_MESSAGE, MODEL3D_IMAGE_SOURCE_REQUIRED_MESSAGE, MODEL3D_PROJECT_REQUIRED_MESSAGE, MODEL3D_TEXT_PROMPT_REQUIRED_MESSAGE, + resolveModel3dGenerationErrorMessage, resolveModel3dImageSource, resolveModel3dRequestKey, resolveModel3dResultTitle, @@ -336,3 +339,39 @@ describe('Idempotency-Key', () => { expect(right).toBe(left); }); }); + +describe('resolveModel3dGenerationErrorMessage', () => { + it('服务端给的文案原样透出', () => { + expect( + resolveModel3dGenerationErrorMessage( + new Error('3D 生成服务当前限流,请稍后重试。'), + ), + ).toBe('3D 生成服务当前限流,请稍后重试。'); + expect(resolveModel3dGenerationErrorMessage('落点不可用')).toBe( + '落点不可用', + ); + }); + + it('传输层英文技术文案换成中文兜底', () => { + expect( + resolveModel3dGenerationErrorMessage(new TypeError('Failed to fetch')), + ).toBe(MODEL3D_GENERATION_FAILURE_MESSAGE); + expect( + resolveModel3dGenerationErrorMessage( + new DOMException('The operation was aborted.', 'AbortError'), + ), + ).toBe(MODEL3D_GENERATION_FAILURE_MESSAGE); + expect(resolveModel3dGenerationErrorMessage(null)).toBe( + MODEL3D_GENERATION_FAILURE_MESSAGE, + ); + }); + + it('超时单独给出可重试的提示', () => { + const timeout = new Error('请求超时:60000ms'); + timeout.name = 'TimeoutError'; + + expect(resolveModel3dGenerationErrorMessage(timeout)).toBe( + MODEL3D_GENERATION_TIMEOUT_MESSAGE, + ); + }); +}); diff --git a/src/components/image-editor/model3d-generation/Model3dGenerationSubmission.ts b/src/components/image-editor/model3d-generation/Model3dGenerationSubmission.ts index 85d218eb1..01f579827 100644 --- a/src/components/image-editor/model3d-generation/Model3dGenerationSubmission.ts +++ b/src/components/image-editor/model3d-generation/Model3dGenerationSubmission.ts @@ -1,5 +1,6 @@ import type { ExternalGenerationJobStatusRecord } from '../../../../packages/shared/src/contracts/externalGeneration'; import type { Model3dGenerationSource } from '../../../../packages/shared/src/contracts/model3d'; +import { isAbortError, isTimeoutError } from '../../../services/apiClient'; import type { EditorCanvasGenerationCompletionInput, Model3dGenerationSubmissionResponse, @@ -22,6 +23,10 @@ export const MODEL3D_IMAGE_SOURCE_REQUIRED_MESSAGE = '请先选择一张画布图片或素材库图片'; export const MODEL3D_PROJECT_REQUIRED_MESSAGE = '请先保存当前画布工程,再生成 3D 模型'; +export const MODEL3D_GENERATION_FAILURE_MESSAGE = + '生成 3D 模型失败,请稍后重试。'; +export const MODEL3D_GENERATION_TIMEOUT_MESSAGE = + '3D 生成请求超时,请稍后重试。'; export type Model3dSubmissionPlan = { endpoint: Model3dPricingEndpoint; @@ -213,11 +218,26 @@ export function model3dQueueStateFromResponse( } export function resolveModel3dGenerationErrorMessage(error: unknown): string { + if (isTimeoutError(error)) { + return MODEL3D_GENERATION_TIMEOUT_MESSAGE; + } + // 传输层失败的 message 是英文技术文案(`Failed to fetch` / `The operation was + // aborted.`),不能直接当用户提示;只有服务端给出的 message 才是面向用户的。 + if (isAbortError(error) || isFetchFailureError(error)) { + return MODEL3D_GENERATION_FAILURE_MESSAGE; + } if (error instanceof Error && error.message.trim()) { return error.message.trim(); } if (typeof error === 'string' && error.trim()) { return error.trim(); } - return '生成 3D 模型失败,请稍后重试。'; + return MODEL3D_GENERATION_FAILURE_MESSAGE; +} + +/** fetch 的网络层失败统一是 `TypeError: Failed to fetch`。 */ +function isFetchFailureError(error: unknown) { + return ( + error instanceof TypeError && /failed to fetch|network/i.test(error.message) + ); }