收敛资源工作台错误正文
统一资源编辑、上传、预览和恢复队列的诊断脱敏 保留错误状态与正文并补充当前错误处理文档
This commit is contained in:
@@ -281,6 +281,7 @@ import {
|
||||
} from '../../features/resource-canvas/resourceVersionReplacementTransport';
|
||||
import { useResourceCanvasGenerationPlaceholders } from '../../features/resource-canvas/useResourceCanvasGenerationPlaceholders';
|
||||
import { createUiDesignDocFromImages } from '../../features/ui-editor/uiDesignResourceBridge';
|
||||
import { normalizeDiagnosticText } from '../../services/errorReporting';
|
||||
import {
|
||||
currentPlatformSessionGeneration,
|
||||
requestPlatformSessionRefresh,
|
||||
@@ -516,6 +517,18 @@ function resolveResourceCardDragMoves({
|
||||
*/
|
||||
const EMPTY_RESOURCE_CANVAS_HISTORY = createResourceCanvasHistory();
|
||||
|
||||
function projectDevelopmentErrorMessage(
|
||||
error: unknown,
|
||||
fallback = '未提供具体错误正文',
|
||||
) {
|
||||
const raw = error instanceof Error ? error.message : String(error ?? '');
|
||||
const safe = normalizeDiagnosticText(raw.replace(/\s+/gu, ' ').trim()).slice(
|
||||
0,
|
||||
400,
|
||||
);
|
||||
return safe || fallback;
|
||||
}
|
||||
|
||||
function isPlatformAuthenticationRequired(error: unknown) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
return (
|
||||
@@ -2795,9 +2808,7 @@ export default function ProjectDevelopmentView({
|
||||
}
|
||||
onNotice({
|
||||
tone: 'error',
|
||||
message: `在浏览器打开失败:${
|
||||
error instanceof Error ? error.message : String(error)
|
||||
}`,
|
||||
message: `在浏览器打开失败:${projectDevelopmentErrorMessage(error)}`,
|
||||
});
|
||||
}
|
||||
}, [embeddedPreviewUrl, onNotice]);
|
||||
@@ -4038,13 +4049,10 @@ export default function ProjectDevelopmentView({
|
||||
commitId,
|
||||
});
|
||||
} catch (error) {
|
||||
setResourceWorkbenchNotice(
|
||||
options.readBackFailureNotice
|
||||
? options.readBackFailureNotice(error)
|
||||
: error instanceof Error
|
||||
? error.message
|
||||
: String(error),
|
||||
);
|
||||
const notice = options.readBackFailureNotice
|
||||
? options.readBackFailureNotice(error)
|
||||
: projectDevelopmentErrorMessage(error);
|
||||
setResourceWorkbenchNotice(projectDevelopmentErrorMessage(notice));
|
||||
}
|
||||
},
|
||||
[manifest.projectId, onManifestChange, projectPath],
|
||||
@@ -4129,9 +4137,7 @@ export default function ProjectDevelopmentView({
|
||||
// 原生已经写入成功,只是整份 manifest 没读回来:说清"已保存、刷新失败",
|
||||
// 不给用户"保存失败、其实已落盘"的错觉。
|
||||
readBackFailureNotice: (error) =>
|
||||
`标签已保存,但刷新失败:${
|
||||
error instanceof Error ? error.message : String(error)
|
||||
}`,
|
||||
`标签已保存,但刷新失败:${projectDevelopmentErrorMessage(error)}`,
|
||||
},
|
||||
);
|
||||
},
|
||||
@@ -4702,8 +4708,7 @@ export default function ProjectDevelopmentView({
|
||||
}
|
||||
const viewport = resolveViewport(event);
|
||||
const category = viewport?.dataset.resourceSectionScroll as
|
||||
| ResourceCategory
|
||||
| undefined;
|
||||
ResourceCategory | undefined;
|
||||
if (!viewport || !category) {
|
||||
return;
|
||||
}
|
||||
@@ -4723,8 +4728,7 @@ export default function ProjectDevelopmentView({
|
||||
const event = rawEvent as WebKitGestureEvent;
|
||||
const viewport = resolveViewport(event);
|
||||
const category = viewport?.dataset.resourceSectionScroll as
|
||||
| ResourceCategory
|
||||
| undefined;
|
||||
ResourceCategory | undefined;
|
||||
if (!viewport || !category) {
|
||||
return;
|
||||
}
|
||||
@@ -4883,8 +4887,7 @@ export default function ProjectDevelopmentView({
|
||||
.querySelectorAll<HTMLElement>('[data-resource-section-scroll]')
|
||||
.forEach((viewport) => {
|
||||
const category = viewport.dataset.resourceSectionScroll as
|
||||
| ResourceCategory
|
||||
| undefined;
|
||||
ResourceCategory | undefined;
|
||||
if (!category) {
|
||||
return;
|
||||
}
|
||||
@@ -4904,8 +4907,7 @@ export default function ProjectDevelopmentView({
|
||||
?.querySelectorAll<HTMLElement>('[data-resource-section-scroll]')
|
||||
.forEach((viewport) => {
|
||||
const category = viewport.dataset.resourceSectionScroll as
|
||||
| ResourceCategory
|
||||
| undefined;
|
||||
ResourceCategory | undefined;
|
||||
if (!category) {
|
||||
return;
|
||||
}
|
||||
@@ -5080,7 +5082,7 @@ export default function ProjectDevelopmentView({
|
||||
) {
|
||||
return { status: 'stale' as const };
|
||||
}
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
const message = projectDevelopmentErrorMessage(error);
|
||||
setPendingResourceEditsLoadState('failed');
|
||||
setPendingResourceEditsError(message);
|
||||
return { status: 'failed' as const, error: message };
|
||||
@@ -5431,7 +5433,7 @@ export default function ProjectDevelopmentView({
|
||||
savedCount += 1;
|
||||
onNotice(`已保存 ${result.destinationPath}`);
|
||||
} catch (error) {
|
||||
onNotice(error instanceof Error ? error.message : String(error));
|
||||
onNotice(projectDevelopmentErrorMessage(error, '保存素材失败'));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -5496,7 +5498,7 @@ export default function ProjectDevelopmentView({
|
||||
}
|
||||
} catch (error) {
|
||||
setResourcePanelNotice(
|
||||
error instanceof Error ? error.message : String(error),
|
||||
projectDevelopmentErrorMessage(error, '上传素材失败'),
|
||||
);
|
||||
} finally {
|
||||
setResourcePanelUploading(false);
|
||||
@@ -6502,7 +6504,7 @@ export default function ProjectDevelopmentView({
|
||||
} catch (error) {
|
||||
if (canvasOpenEpochRef.current === openEpoch) {
|
||||
setResourceWorkbenchNotice(
|
||||
error instanceof Error ? error.message : String(error),
|
||||
projectDevelopmentErrorMessage(error, '打开 UI 编辑器失败'),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -6731,9 +6733,11 @@ export default function ProjectDevelopmentView({
|
||||
}
|
||||
} catch (error) {
|
||||
if (!isCurrentRecoveryProject(actionProject)) return;
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
const rawMessage =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
const message = projectDevelopmentErrorMessage(error);
|
||||
let displayMessage = message;
|
||||
if (message.includes('service-identity-confirmation-required')) {
|
||||
if (rawMessage.includes('service-identity-confirmation-required')) {
|
||||
try {
|
||||
const confirmation =
|
||||
await invoke<ResourceEditServiceIdentityConfirmation>(
|
||||
@@ -6764,10 +6768,7 @@ export default function ProjectDevelopmentView({
|
||||
displayMessage = `请确认“${confirmation.serviceOrigin}”仍是该 operation 的原服务后再继续`;
|
||||
} catch (confirmationError) {
|
||||
if (!isCurrentRecoveryProject(actionProject)) return;
|
||||
displayMessage =
|
||||
confirmationError instanceof Error
|
||||
? confirmationError.message
|
||||
: String(confirmationError);
|
||||
displayMessage = projectDevelopmentErrorMessage(confirmationError);
|
||||
}
|
||||
}
|
||||
setPendingResourceEditActionErrors((current) => {
|
||||
@@ -6843,7 +6844,7 @@ export default function ProjectDevelopmentView({
|
||||
shouldResume = true;
|
||||
} catch (error) {
|
||||
if (!isCurrentRecoveryProject(actionProject)) return;
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
const message = projectDevelopmentErrorMessage(error);
|
||||
setResourceEditServiceIdentityConfirmations((current) => {
|
||||
if (!current.has(pending.operationId)) return current;
|
||||
const next = new Map(current);
|
||||
@@ -6924,7 +6925,7 @@ export default function ProjectDevelopmentView({
|
||||
}
|
||||
} catch (error) {
|
||||
if (!isCurrentRecoveryProject(actionProject)) return;
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
const message = projectDevelopmentErrorMessage(error);
|
||||
setPendingResourceEditActionErrors((current) => {
|
||||
const next = new Map(current);
|
||||
next.set(pending.operationId, message);
|
||||
@@ -6979,16 +6980,16 @@ export default function ProjectDevelopmentView({
|
||||
dependencyLayoutSettled: dependencyLayout.settled,
|
||||
dependencyPositioned: Boolean(
|
||||
intent.resourceId &&
|
||||
dependencyLayout.layout.positions.some(
|
||||
(position) => position.resourceId === intent.resourceId,
|
||||
),
|
||||
dependencyLayout.layout.positions.some(
|
||||
(position) => position.resourceId === intent.resourceId,
|
||||
),
|
||||
),
|
||||
typeLayoutSettled: typeLayout.settled,
|
||||
typePositioned: Boolean(
|
||||
intent.resourceId &&
|
||||
typeLayout.layout.positions.some(
|
||||
(position) => position.resourceId === intent.resourceId,
|
||||
),
|
||||
typeLayout.layout.positions.some(
|
||||
(position) => position.resourceId === intent.resourceId,
|
||||
),
|
||||
),
|
||||
visible: targetVisible,
|
||||
domRendered: Boolean(card),
|
||||
@@ -8071,8 +8072,7 @@ export default function ProjectDevelopmentView({
|
||||
? {
|
||||
...current,
|
||||
status: 'failed',
|
||||
errorMessage:
|
||||
error instanceof Error ? error.message : String(error),
|
||||
errorMessage: projectDevelopmentErrorMessage(error),
|
||||
}
|
||||
: current,
|
||||
);
|
||||
@@ -8199,7 +8199,7 @@ export default function ProjectDevelopmentView({
|
||||
resourceQuickEditRequestRef.current = null;
|
||||
setResourceWorkbenchNotice('快速编辑已产出新素材,正在同步资源与布局…');
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
const message = projectDevelopmentErrorMessage(error);
|
||||
settleResourceEditSubmission(request.operationId, {
|
||||
status: 'failed',
|
||||
error: message,
|
||||
@@ -8343,8 +8343,7 @@ export default function ProjectDevelopmentView({
|
||||
? {
|
||||
...current,
|
||||
status: 'failed',
|
||||
errorMessage:
|
||||
error instanceof Error ? error.message : String(error),
|
||||
errorMessage: projectDevelopmentErrorMessage(error),
|
||||
}
|
||||
: current,
|
||||
);
|
||||
@@ -8480,7 +8479,7 @@ export default function ProjectDevelopmentView({
|
||||
});
|
||||
setResourceWorkbenchNotice('生成动画已产出新素材,正在同步资源与布局…');
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
const message = projectDevelopmentErrorMessage(error);
|
||||
settleResourceEditSubmission(request.operationId, {
|
||||
status: 'failed',
|
||||
error: message,
|
||||
@@ -8798,7 +8797,7 @@ export default function ProjectDevelopmentView({
|
||||
});
|
||||
} catch (error) {
|
||||
setResourceWorkbenchNotice(
|
||||
error instanceof Error ? error.message : String(error),
|
||||
projectDevelopmentErrorMessage(error, '同步生成结果失败'),
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -9174,7 +9173,7 @@ export default function ProjectDevelopmentView({
|
||||
}
|
||||
} catch (error) {
|
||||
setResourceWorkbenchNotice(
|
||||
error instanceof Error ? error.message : String(error),
|
||||
projectDevelopmentErrorMessage(error, '上传素材失败'),
|
||||
);
|
||||
} finally {
|
||||
setResourceBottomToolbarUploading(false);
|
||||
@@ -10110,7 +10109,7 @@ export default function ProjectDevelopmentView({
|
||||
Boolean(selectedResource?.manifestAssetId) ||
|
||||
Boolean(
|
||||
selectedResource &&
|
||||
isResourceDocumentPreviewable(selectedResource),
|
||||
isResourceDocumentPreviewable(selectedResource),
|
||||
) ||
|
||||
selectedResourceOpensUiEditor ||
|
||||
/*
|
||||
|
||||
@@ -110,6 +110,7 @@ DirectProject 已经解决过同一类问题([`【ADR】DirectProject命令接
|
||||
- HTTP 409 只有明确包含泥点不足事实时才映射为 `paidCreditsInsufficient`;Claude Code 的普通 409 冲突保留为 `upstreamFailed`。
|
||||
- `hostDropped` 携带可选的脱敏 `detail`:panic hook 能取得的负载和位置随原回合占用进入失败事件;普通 Drop 仅记录“退出时未写终态、未观察到 panic”,不推断为网络中断。旧版本无 `detail` 的事件继续可读,界面明确标注旧事件未记录原因。
|
||||
- DirectProject 控制器的取消待发、终止、附件上传、项目历史读取和发送前置异常也复用同一份前端精确脱敏出口;这些非回合错误不得直接把 `Error.message` 原文写入状态栏、composer 或历史错误行。
|
||||
- AGC 项目资源编辑、上传、预览、恢复队列和生成结果回读的面板错误统一经过 `normalizeDiagnosticText`,保留状态码与安全正文,替换凭据、URL、路径和长标识;资源业务提示不再直接展示 `Error.message`。
|
||||
|
||||
## 后果与边界
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ Parent Milestone: `【里程碑】AGC错误具体文本展示-2026-10-04.md`
|
||||
5. 更新 ADR 的当前行为口径。
|
||||
6. HostDropped 的可选 detail 由 Rust 单点脱敏;panic hook 把可读负载和位置关联到原回合占用,Drop 与旧无字段事件分别验证,不更改回合排队、重试或计费行为。
|
||||
7. DirectProject 控制器的取消、上传、历史读取和前置异常统一复用前端精确脱敏出口,保留 IPC/OS 正文并替换凭据值、URL 与路径。
|
||||
8. 项目资源编辑/上传/预览/恢复队列/生成结果回读的 UI 错误统一使用 `normalizeDiagnosticText`,不再直出 `Error.message`。
|
||||
|
||||
## 实现顺序
|
||||
|
||||
@@ -35,5 +36,6 @@ Parent Milestone: `【里程碑】AGC错误具体文本展示-2026-10-04.md`
|
||||
- Rust 应用日志/启动诊断精确脱敏回归:通过。
|
||||
- Rust HostDropped panic detail + 旧无字段载荷兼容回归:定向通过。
|
||||
- DirectProject 控制器错误正文:新增脱敏与空错误专用提示回归,前端定向测试通过。
|
||||
- 资源工作台错误正文:项目开发页所有用户可见资源错误路径完成统一脱敏收口,TypeScript 与编码检查覆盖。
|
||||
- 真实 Provider、IPC 断链和内存压力尚未执行。
|
||||
- 2026-10-05 当前代码真实 `npm run agc` 构建启动通过;客户端与 Runner 已启动,前端/后台/API/worker/数据库健康检查通过,停止本次自有客户端后保留原有后端。
|
||||
|
||||
Reference in New Issue
Block a user