From 2350c56f2f9c86f01dc1335caaa7839d9db39887 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Thu, 24 Sep 2026 17:30:01 +0800 Subject: [PATCH] =?UTF-8?q?3D=20=E5=A4=B1=E8=B4=A5=E6=8F=90=E7=A4=BA?= =?UTF-8?q?=E5=8F=AA=E9=80=8F=E5=87=BA=E6=9C=8D=E5=8A=A1=E7=AB=AF=E7=BB=99?= =?UTF-8?q?=E7=9A=84=E6=96=87=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - src/components/image-editor/model3d-generation/Model3dGenerationSubmission.ts:resolveModel3dGenerationErrorMessage 只对 ApiClientError 原样透出 message;原生 SyntaxError(2xx 但 body 不是 JSON)与其它运行时 TypeError 的英文技术文案不再直接显示给用户 - src/components/image-editor/model3d-generation/Model3dGenerationSubmission.test.ts、useModel3dGenerationTask.test.tsx:用例改用 ApiClientError 构造服务端文案,并补非服务端错误退回兜底文案的断言 --- .../Model3dGenerationSubmission.test.ts | 21 ++++++++++++++++++- .../Model3dGenerationSubmission.ts | 10 +++++++-- .../useModel3dGenerationTask.test.tsx | 7 ++++++- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/components/image-editor/model3d-generation/Model3dGenerationSubmission.test.ts b/src/components/image-editor/model3d-generation/Model3dGenerationSubmission.test.ts index 04e1960b6..1fb0dd227 100644 --- a/src/components/image-editor/model3d-generation/Model3dGenerationSubmission.test.ts +++ b/src/components/image-editor/model3d-generation/Model3dGenerationSubmission.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from 'vitest'; +import { ApiClientError } from '../../../services/apiClient'; import type { Model3dPricingConfig } from '../../../services/image-editor/editorProjectClient'; import type { CharacterReferenceImage, @@ -478,7 +479,11 @@ describe('resolveModel3dGenerationErrorMessage', () => { it('服务端给的文案原样透出', () => { expect( resolveModel3dGenerationErrorMessage( - new Error('3D 生成服务当前限流,请稍后重试。'), + new ApiClientError({ + message: '3D 生成服务当前限流,请稍后重试。', + status: 429, + code: 'tripo-api-rate-limited', + }), ), ).toBe('3D 生成服务当前限流,请稍后重试。'); expect(resolveModel3dGenerationErrorMessage('落点不可用')).toBe( @@ -486,6 +491,20 @@ describe('resolveModel3dGenerationErrorMessage', () => { ); }); + it('非服务端来源的错误不把技术文案透给用户', () => { + // 2xx 但 body 不是 JSON 时 requestJson 抛的是原生 SyntaxError。 + expect( + resolveModel3dGenerationErrorMessage( + new SyntaxError('Unexpected token < in JSON at position 0'), + ), + ).toBe(MODEL3D_GENERATION_FAILURE_MESSAGE); + expect( + resolveModel3dGenerationErrorMessage( + new TypeError("Cannot read properties of undefined (reading 'ok')"), + ), + ).toBe(MODEL3D_GENERATION_FAILURE_MESSAGE); + }); + it('传输层英文技术文案换成中文兜底', () => { expect( resolveModel3dGenerationErrorMessage(new TypeError('Failed to fetch')), diff --git a/src/components/image-editor/model3d-generation/Model3dGenerationSubmission.ts b/src/components/image-editor/model3d-generation/Model3dGenerationSubmission.ts index 4a79d1940..669d8e48d 100644 --- a/src/components/image-editor/model3d-generation/Model3dGenerationSubmission.ts +++ b/src/components/image-editor/model3d-generation/Model3dGenerationSubmission.ts @@ -4,7 +4,11 @@ import type { Model3dImageToModelRequest, Model3dTextToModelRequest, } from '../../../../packages/shared/src/contracts/model3d'; -import { isAbortError, isTimeoutError } from '../../../services/apiClient'; +import { + ApiClientError, + isAbortError, + isTimeoutError, +} from '../../../services/apiClient'; import type { EditorCanvasGenerationCompletionInput, Model3dGenerationSubmissionResponse, @@ -307,7 +311,9 @@ export function resolveModel3dGenerationErrorMessage(error: unknown): string { if (isAbortError(error) || isFetchFailureError(error)) { return MODEL3D_GENERATION_FAILURE_MESSAGE; } - if (error instanceof Error && error.message.trim()) { + // 只有 ApiClientError 的 message 是按服务端响应拼出来的;`JSON.parse` 失败的 SyntaxError + // 或其它运行时 TypeError 的 message 是英文技术文案,同样不能当用户提示。 + if (error instanceof ApiClientError && error.message.trim()) { return error.message.trim(); } if (typeof error === 'string' && error.trim()) { diff --git a/src/components/image-editor/model3d-generation/useModel3dGenerationTask.test.tsx b/src/components/image-editor/model3d-generation/useModel3dGenerationTask.test.tsx index 59ddc6ac3..b2398bb0e 100644 --- a/src/components/image-editor/model3d-generation/useModel3dGenerationTask.test.tsx +++ b/src/components/image-editor/model3d-generation/useModel3dGenerationTask.test.tsx @@ -5,6 +5,7 @@ import { useState } from 'react'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import type { ExternalGenerationJobStatusRecord } from '../../../../packages/shared/src/contracts/externalGeneration'; +import { ApiClientError } from '../../../services/apiClient'; import type { EditorCanvasGenerationCompletionInput, Model3dGenerationSubmissionResponse, @@ -256,7 +257,11 @@ describe('useModel3dGenerationTask', () => { it('请求失败时留在面板上展示失败原因', async () => { submitModel3dTextToModelRequestMock.mockRejectedValue( - new Error('3D 模型生成失败,请稍后重试。'), + new ApiClientError({ + message: '3D 模型生成失败,请稍后重试。', + status: 502, + code: 'tripo-upstream-error', + }), ); const { result } = renderTask({ dialog: createDialog() });