统一前端结构化错误展示
避免资源和工作台错误退化为 object Object 统一 Direct UI 和资源面板的正文脱敏出口 通过错误报告和 Direct 展示回归测试
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
+2
-12
@@ -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(
|
||||
|
||||
+5
-5
@@ -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 '项目已被其它操作改动,请重试替换';
|
||||
}
|
||||
|
||||
@@ -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') ||
|
||||
|
||||
+7
-4
@@ -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}`
|
||||
|
||||
+5
-1
@@ -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();
|
||||
|
||||
@@ -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')) {
|
||||
|
||||
+2
-13
@@ -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 '项目已被其它操作改动,请刷新后重试';
|
||||
}
|
||||
|
||||
+5
-11
@@ -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 =
|
||||
|
||||
+2
-3
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user