From 7e24d9511fc9d3f5df34c4a085e6aba2c2bd6e29 Mon Sep 17 00:00:00 2001 From: kdletters <61648117+kdletters@users.noreply.github.com> Date: Mon, 5 Oct 2026 18:58:40 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BB=9F=E4=B8=80=E5=89=8D=E7=AB=AF=E7=BB=93?= =?UTF-8?q?=E6=9E=84=E5=8C=96=E9=94=99=E8=AF=AF=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 避免资源和工作台错误退化为 object Object 统一 Direct UI 和资源面板的正文脱敏出口 通过错误报告和 Direct 展示回归测试 --- apps/ai-game-creator-shell/src/App.tsx | 2 +- .../features/app-shell/useRecentProjects.ts | 2 +- .../resourceCanvasAssetGenerationQueue.ts | 14 ++----------- .../resourceVersionReplacementModel.ts | 10 +++++----- .../src/services/platformSessionRetry.ts | 3 ++- .../useDirectProjectChatController.ts | 11 ++++++---- .../useDirectThreadChatSubscription.ts | 6 +++++- .../src/view/project-development/index.tsx | 5 ++--- .../projectAssetCommandErrorMessage.ts | 15 ++------------ .../useProjectResourceCanvasLayout.ts | 16 +++++---------- .../useProjectResourceCardPreviews.ts | 5 ++--- .../src/view/ui-editor/useUiEditorPage.ts | 20 +++++++++---------- 12 files changed, 43 insertions(+), 66 deletions(-) diff --git a/apps/ai-game-creator-shell/src/App.tsx b/apps/ai-game-creator-shell/src/App.tsx index 0b6661de5..20dec971a 100644 --- a/apps/ai-game-creator-shell/src/App.tsx +++ b/apps/ai-game-creator-shell/src/App.tsx @@ -567,7 +567,7 @@ export function App({ if (!isCurrentDesignTurn(nextProjectPath, clientTurnId)) { return; } - const rawMessage = error instanceof Error ? error.message : String(error); + const rawMessage = visibleClientErrorMessage(error); const message = visibleClientErrorMessage(error); if (isRuntimeConfigMissingError(rawMessage)) { requestRuntimeConfigOpen(); diff --git a/apps/ai-game-creator-shell/src/features/app-shell/useRecentProjects.ts b/apps/ai-game-creator-shell/src/features/app-shell/useRecentProjects.ts index d2dfb6e6a..f27558fb8 100644 --- a/apps/ai-game-creator-shell/src/features/app-shell/useRecentProjects.ts +++ b/apps/ai-game-creator-shell/src/features/app-shell/useRecentProjects.ts @@ -78,7 +78,7 @@ async function inspectRecentWorkspace( ]); return { path: workspace, status, retryable: true }; } catch (error) { - const message = error instanceof Error ? error.message : String(error); + const message = visibleClientErrorMessage(error); return { path: workspace, status: null, diff --git a/apps/ai-game-creator-shell/src/features/resource-canvas/resourceCanvasAssetGenerationQueue.ts b/apps/ai-game-creator-shell/src/features/resource-canvas/resourceCanvasAssetGenerationQueue.ts index 6d9b66c0d..48c13571e 100644 --- a/apps/ai-game-creator-shell/src/features/resource-canvas/resourceCanvasAssetGenerationQueue.ts +++ b/apps/ai-game-creator-shell/src/features/resource-canvas/resourceCanvasAssetGenerationQueue.ts @@ -1,4 +1,4 @@ -import { normalizeDiagnosticText } from '../../services/errorReporting'; +import { visibleClientErrorMessage } from '../../services/errorReporting'; import { applyLocalProjectAssetGenerationRecords, type LocalProjectAssetGenerationTaskRecord, @@ -90,17 +90,7 @@ export type ResourceCanvasAssetGenerationQueue = { }; function errorMessage(error: unknown) { - const raw = - typeof error === 'string' - ? error - : error instanceof Error - ? error.message - : String(error ?? ''); - const safe = normalizeDiagnosticText(raw.replace(/\s+/gu, ' ').trim()).slice( - 0, - 400, - ); - return safe || '生成素材失败'; + return visibleClientErrorMessage(error, '生成素材失败').slice(0, 400); } function safeGenerationRecord( diff --git a/apps/ai-game-creator-shell/src/features/resource-canvas/resourceVersionReplacementModel.ts b/apps/ai-game-creator-shell/src/features/resource-canvas/resourceVersionReplacementModel.ts index fb39281c9..598a63944 100644 --- a/apps/ai-game-creator-shell/src/features/resource-canvas/resourceVersionReplacementModel.ts +++ b/apps/ai-game-creator-shell/src/features/resource-canvas/resourceVersionReplacementModel.ts @@ -1,5 +1,5 @@ import type { EditorAsset } from '../../../../../src/components/image-editor/ImageCanvasEditorTypes'; -import { normalizeDiagnosticText } from '../../services/errorReporting'; +import { visibleClientErrorMessage } from '../../services/errorReporting'; import type { ProjectResource } from '../../view/project-development/resourceProjectionModel'; import { resourceCanvasAssetKind, @@ -257,10 +257,10 @@ export function resourceReplacementPickerAssets( * 不认识的错误原样透出:宁可显示后端原文,也不把原因吞掉换成"替换失败"这种无信息文案。 */ export function resourceVersionReplacementErrorMessage(error: unknown): string { - const message = normalizeDiagnosticText( - error instanceof Error ? error.message : String(error ?? '').trim(), - ).slice(0, 400); - if (!message) return '替换素材失败'; + const message = visibleClientErrorMessage(error, '替换素材失败').slice( + 0, + 400, + ); if (message.includes('project-revision-conflict')) { return '项目已被其它操作改动,请重试替换'; } diff --git a/apps/ai-game-creator-shell/src/services/platformSessionRetry.ts b/apps/ai-game-creator-shell/src/services/platformSessionRetry.ts index baf76765d..c2bb57489 100644 --- a/apps/ai-game-creator-shell/src/services/platformSessionRetry.ts +++ b/apps/ai-game-creator-shell/src/services/platformSessionRetry.ts @@ -1,3 +1,4 @@ +import { visibleClientErrorMessage } from './errorReporting'; import { currentPlatformSessionGeneration, requestPlatformSessionRefresh, @@ -11,7 +12,7 @@ import { * 续期后重试一次,网络抖动、5xx 和业务拒绝必须原样抛出。 */ export function isPlatformAuthenticationRequiredError(error: unknown) { - const message = error instanceof Error ? error.message : String(error); + const message = visibleClientErrorMessage(error); return ( message.includes('authentication-required') || message.includes('codex-app-server-error:unauthorized') || diff --git a/apps/ai-game-creator-shell/src/view/project-development/chat/controller/useDirectProjectChatController.ts b/apps/ai-game-creator-shell/src/view/project-development/chat/controller/useDirectProjectChatController.ts index d97a6945a..8f341d7e2 100644 --- a/apps/ai-game-creator-shell/src/view/project-development/chat/controller/useDirectProjectChatController.ts +++ b/apps/ai-game-creator-shell/src/view/project-development/chat/controller/useDirectProjectChatController.ts @@ -16,7 +16,10 @@ import { hasMeaningfulDirectCodexContent, userItemFromContent, } from '../../../../features/project-workspace/resourceReferences'; -import { captureAgentRuntimeError } from '../../../../services/errorReporting'; +import { + captureAgentRuntimeError, + visibleClientErrorMessage, +} from '../../../../services/errorReporting'; import { createDirectProjectTurnId, DIRECT_CODEX_AGENT_ID, @@ -495,7 +498,7 @@ export function useDirectProjectChatController({ if (projectPathRef.current === nextProjectPath) { onRuntimeError( projectRuntimeVisibleError( - error instanceof Error ? error.message : String(error), + visibleClientErrorMessage(error), '陶泥儿智能创作', true, ), @@ -565,7 +568,7 @@ export function useDirectProjectChatController({ if (!systemNotice) { onRuntimeError( projectRuntimeVisibleError( - error instanceof Error ? error.message : String(error), + visibleClientErrorMessage(error), '陶泥儿智能创作', true, ), @@ -741,7 +744,7 @@ export function useDirectProjectChatController({ // 与加载成功路径同样按世代守卫:切项目后在飞的读取失败不能算到新项目头上。 if (isStaleLoad()) return; const detail = visibleDirectFailureDetail( - error instanceof Error ? error.message : String(error), + visibleClientErrorMessage(error), ); const message = detail ? `读取更早的对话历史失败:${detail}` diff --git a/apps/ai-game-creator-shell/src/view/project-development/chat/controller/useDirectThreadChatSubscription.ts b/apps/ai-game-creator-shell/src/view/project-development/chat/controller/useDirectThreadChatSubscription.ts index 1385de7d4..4306544d4 100644 --- a/apps/ai-game-creator-shell/src/view/project-development/chat/controller/useDirectThreadChatSubscription.ts +++ b/apps/ai-game-creator-shell/src/view/project-development/chat/controller/useDirectThreadChatSubscription.ts @@ -7,6 +7,7 @@ import { } from 'react'; import { resolveTauriInvoke } from '../../../../app/tauri'; +import { visibleClientErrorMessage } from '../../../../services/errorReporting'; import { canSubscribeTauriEvents, subscribeTauriEvent, @@ -128,7 +129,10 @@ export function useDirectThreadChatSubscription({ ); } while (consumeAgain && !disposed); } catch (error) { - if (!disposed && String(error).includes('SUBSCRIPTION_EXPIRED')) { + if ( + !disposed && + visibleClientErrorMessage(error).includes('SUBSCRIPTION_EXPIRED') + ) { subscriptionId = null; try { await bootstrap(); diff --git a/apps/ai-game-creator-shell/src/view/project-development/index.tsx b/apps/ai-game-creator-shell/src/view/project-development/index.tsx index 3f99aa3b6..29c33bf23 100644 --- a/apps/ai-game-creator-shell/src/view/project-development/index.tsx +++ b/apps/ai-game-creator-shell/src/view/project-development/index.tsx @@ -525,7 +525,7 @@ function projectDevelopmentErrorMessage( } function isPlatformAuthenticationRequired(error: unknown) { - const message = error instanceof Error ? error.message : String(error); + const message = visibleClientErrorMessage(error); return ( message.includes('authentication-required') || message.includes('登录已失效') @@ -6728,8 +6728,7 @@ export default function ProjectDevelopmentView({ } } catch (error) { if (!isCurrentRecoveryProject(actionProject)) return; - const rawMessage = - error instanceof Error ? error.message : String(error); + const rawMessage = visibleClientErrorMessage(error); const message = projectDevelopmentErrorMessage(error); let displayMessage = message; if (rawMessage.includes('service-identity-confirmation-required')) { diff --git a/apps/ai-game-creator-shell/src/view/project-development/projectAssetCommandErrorMessage.ts b/apps/ai-game-creator-shell/src/view/project-development/projectAssetCommandErrorMessage.ts index 2c48a18af..0119f4c3e 100644 --- a/apps/ai-game-creator-shell/src/view/project-development/projectAssetCommandErrorMessage.ts +++ b/apps/ai-game-creator-shell/src/view/project-development/projectAssetCommandErrorMessage.ts @@ -8,24 +8,13 @@ * 口径与 `resourceVersionReplacementErrorMessage` 里同样的两条映射逐字一致, * 三条链路给用户的说法必须一样。 */ -import { normalizeDiagnosticText } from '../../services/errorReporting'; +import { visibleClientErrorMessage } from '../../services/errorReporting'; export function projectAssetCommandErrorMessage( error: unknown, fallback: string, ): string { - let message = ''; - if (typeof error === 'string') { - message = error.trim(); - } else if (error instanceof Error) { - message = error.message.trim(); - } else if (error !== null && error !== undefined) { - message = String(error).trim(); - } - message = normalizeDiagnosticText(message).slice(0, 400); - if (!message) { - return fallback; - } + const message = visibleClientErrorMessage(error, fallback); if (message.includes('project-revision-conflict')) { return '项目已被其它操作改动,请刷新后重试'; } diff --git a/apps/ai-game-creator-shell/src/view/project-development/useProjectResourceCanvasLayout.ts b/apps/ai-game-creator-shell/src/view/project-development/useProjectResourceCanvasLayout.ts index e028ca312..949847eb6 100644 --- a/apps/ai-game-creator-shell/src/view/project-development/useProjectResourceCanvasLayout.ts +++ b/apps/ai-game-creator-shell/src/view/project-development/useProjectResourceCanvasLayout.ts @@ -10,7 +10,7 @@ import { isSafeProjectResourceCanvasCoordinate, isSafeProjectResourceCanvasLayoutRevision, } from '../../../../../packages/shared/src/contracts/gameCreationApp'; -import { normalizeDiagnosticText } from '../../services/errorReporting'; +import { visibleClientErrorMessage } from '../../services/errorReporting'; import { applyResourceCanvasPositionWrites, createEmptyResourceCanvasLayout, @@ -26,16 +26,12 @@ import { normalizeResourceCanvasPosition } from './resourceCanvasSectionMapping' * * 这两个 `.catch` 此前写成 `() => { ... }` —— 没有 message 参数,失败原因被整个吞掉: * 用户只看到"布局保存失败",排障端也拿不到为什么(revision 越界?IPC 拒绝?权限?)。 - * 这里把 `error.message` 压成一行并复用既有的 `normalizeDiagnosticText`(路径 / token / - * URL / 长 id 一律打码),再截断,避免多行堆栈糊进日志;日志由客户端已安装的 + * 这里复用既有的 `visibleClientErrorMessage`(结构化对象也保留正文,路径 / token / URL / + * 长 id 一律打码),再截断,避免多行堆栈糊进日志;日志由客户端已安装的 * webview→application.log 桥带进诊断日志,遇到"某个窗口保存一直失败"能直接查。 */ function describeResourceCanvasLayoutFailure(error: unknown) { - const message = error instanceof Error ? error.message : String(error); - return normalizeDiagnosticText(message.replace(/\s+/gu, ' ').trim()).slice( - 0, - 400, - ); + return visibleClientErrorMessage(error).slice(0, 400); } type LayoutNotice = @@ -439,9 +435,7 @@ function reconcileLayout( * 做,磁盘上仍是旧栏目取值的坐标不会因为 `section` 字面量不同而漏出整理范围。 */ organizeSections: - | readonly ProjectResourceCanvasCategory[] - | null - | undefined = undefined, + readonly ProjectResourceCanvasCategory[] | null | undefined = undefined, ) { // `undefined` = 不是整理;`null` = 整理全部栏目;否则按集合判定。 const organizedSections = diff --git a/apps/ai-game-creator-shell/src/view/project-development/useProjectResourceCardPreviews.ts b/apps/ai-game-creator-shell/src/view/project-development/useProjectResourceCardPreviews.ts index 6b255b682..6522d7581 100644 --- a/apps/ai-game-creator-shell/src/view/project-development/useProjectResourceCardPreviews.ts +++ b/apps/ai-game-creator-shell/src/view/project-development/useProjectResourceCardPreviews.ts @@ -8,7 +8,7 @@ import { } from 'react'; import type { ProjectResourceCanvasLayoutMode } from '../../../../../packages/shared/src/contracts/gameCreationApp'; -import { normalizeDiagnosticText } from '../../services/errorReporting'; +import { visibleClientErrorMessage } from '../../services/errorReporting'; import { cancelLocalProjectResourcePreviewScope, createProjectResourcePreviewRequestId, @@ -109,8 +109,7 @@ function previewReadErrorMessage( kind: ProjectResourceCardPreviewKind, error: unknown, ): { error: string; retryable: boolean } { - const rawMessage = error instanceof Error ? error.message : String(error); - const message = normalizeDiagnosticText(rawMessage).slice(0, 400); + const message = visibleClientErrorMessage(error).slice(0, 400); const kindLabel = resourceReadKindLabel(kind); if (message.includes('项目权限策略要求用户确认')) { return { diff --git a/apps/ai-game-creator-shell/src/view/ui-editor/useUiEditorPage.ts b/apps/ai-game-creator-shell/src/view/ui-editor/useUiEditorPage.ts index 88de5c4b1..f5b3a78d2 100644 --- a/apps/ai-game-creator-shell/src/view/ui-editor/useUiEditorPage.ts +++ b/apps/ai-game-creator-shell/src/view/ui-editor/useUiEditorPage.ts @@ -342,8 +342,7 @@ export function useUiEditorSession( }) .catch((cause: unknown) => { if (!cancelled) { - const message = - cause instanceof Error ? cause.message : String(cause); + const message = visibleClientErrorMessage(cause); setStatus(message); setLoadError(message); } @@ -606,7 +605,7 @@ export function useUiEditorSession( })); setStatus(`已加入 ${prepared.length} 项独立素材。`); } catch (cause) { - setStatus(cause instanceof Error ? cause.message : String(cause)); + setStatus(visibleClientErrorMessage(cause)); } } @@ -970,7 +969,7 @@ export function useUiEditorSession( reportWorkflowCompletion( 'structure-recognition', 'failure', - cause instanceof Error ? cause.message : String(cause), + visibleClientErrorMessage(cause), setRecognitionStatus, ); } finally { @@ -1187,7 +1186,7 @@ export function useUiEditorSession( reportWorkflowCompletion( 'asset-separation', 'failure', - cause instanceof Error ? cause.message : String(cause), + visibleClientErrorMessage(cause), setSeparationStatus, ); } finally { @@ -1231,7 +1230,7 @@ export function useUiEditorSession( reportWorkflowCompletion( 'asset-separation', 'failure', - cause instanceof Error ? cause.message : String(cause), + visibleClientErrorMessage(cause), setSeparationStatus, ); } @@ -1266,7 +1265,7 @@ export function useUiEditorSession( reportWorkflowCompletion( 'asset-separation', 'failure', - cause instanceof Error ? cause.message : String(cause), + visibleClientErrorMessage(cause), setSeparationStatus, ); } @@ -1298,7 +1297,7 @@ export function useUiEditorSession( reportWorkflowCompletion( 'asset-separation', 'failure', - cause instanceof Error ? cause.message : String(cause), + visibleClientErrorMessage(cause), setSeparationStatus, ); } @@ -1424,8 +1423,7 @@ export function useUiEditorSession( analytics?.record(saved.status === 'saved'); return { status: 'generated' as const, result: generated }; } catch (cause) { - const message = - cause instanceof Error ? cause.message : String(cause); + const message = visibleClientErrorMessage(cause); return { status: 'failed' as const, phase: 'generate' as const, @@ -1434,7 +1432,7 @@ export function useUiEditorSession( } }); } catch (cause) { - const message = cause instanceof Error ? cause.message : String(cause); + const message = visibleClientErrorMessage(cause); setSaveError(message); return { status: 'failed' as const, phase: 'save' as const, message }; } finally {