diff --git a/apps/ai-game-creator-shell/src/view/project-development/chat/DirectProjectChatView.tsx b/apps/ai-game-creator-shell/src/view/project-development/chat/DirectProjectChatView.tsx index a429ada6a..dd128bf37 100644 --- a/apps/ai-game-creator-shell/src/view/project-development/chat/DirectProjectChatView.tsx +++ b/apps/ai-game-creator-shell/src/view/project-development/chat/DirectProjectChatView.tsx @@ -106,8 +106,13 @@ export function DirectProjectChatView({ ensureConversationWriteAllowed, ref, }: DirectProjectChatViewProps) { - const { assets, projectId, refresh, versions } = - useDirectProjectManifest(projectPath); + const { + assets, + projectId, + error: manifestError, + refresh, + versions, + } = useDirectProjectManifest(projectPath); const composerHandleRef = useRef(null); const [runtimeNotice, setRuntimeNotice] = useState(''); const [settingsOpen, setSettingsOpen] = useState(false); @@ -181,6 +186,7 @@ export function DirectProjectChatView({ const statusText = runtimeNotice || statusNotice || + manifestError || (projectPath ? `已打开:${projectNameFromPath(projectPath)}` : '请选择工作区'); diff --git a/apps/ai-game-creator-shell/src/view/project-development/chat/controller/useDirectProjectManifest.ts b/apps/ai-game-creator-shell/src/view/project-development/chat/controller/useDirectProjectManifest.ts index 79ffbc562..734c1ac69 100644 --- a/apps/ai-game-creator-shell/src/view/project-development/chat/controller/useDirectProjectManifest.ts +++ b/apps/ai-game-creator-shell/src/view/project-development/chat/controller/useDirectProjectManifest.ts @@ -2,6 +2,7 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import { resolveTauriInvoke } from '../../../../app/tauri'; import { projectPathsMatchForInvalidation } from '../../../../features/project-summary/projectPath'; +import { visibleClientErrorMessage } from '../../../../services/errorReporting'; import { canSubscribeTauriEvents, subscribeTauriEvent, @@ -40,6 +41,7 @@ export function useDirectProjectManifest(projectPath: string | null) { const [manifest, setManifest] = useState( null, ); + const [error, setError] = useState(null); const projectPathRef = useRef(projectPath); projectPathRef.current = projectPath; const mountedRef = useRef(true); @@ -70,8 +72,12 @@ export function useDirectProjectManifest(projectPath: string | null) { return; } setManifest(nextManifest); - } catch { - // 清单读不到不能阻塞聊天:`@` 引用退化为空,回合仍以项目对话为准。 + setError(null); + } catch (cause) { + // 清单读不到不能阻塞聊天:`@` 引用退化为空,但必须把正文交给状态条。 + if (mountedRef.current && projectPathRef.current === nextProjectPath) { + setError(`项目清单读取失败:${visibleClientErrorMessage(cause)}`); + } } finally { inFlight.delete(nextProjectPath); } @@ -81,6 +87,7 @@ export function useDirectProjectManifest(projectPath: string | null) { }, []); useEffect(() => { + setError(null); if (!projectPath) { setManifest(null); return; @@ -115,8 +122,11 @@ export function useDirectProjectManifest(projectPath: string | null) { } unsubscribe = release; }) - .catch(() => { - // 拿不到订阅通道时清单只在项目打开和回合结束时刷新。 + .catch((cause) => { + // 拿不到订阅通道时清单只在项目打开和回合结束时刷新,但不能静默隐藏 IPC 正文。 + if (!disposed) { + setError(`项目清单事件订阅失败:${visibleClientErrorMessage(cause)}`); + } }); return () => { disposed = true; @@ -127,6 +137,7 @@ export function useDirectProjectManifest(projectPath: string | null) { return { assets: directProjectChatAssets(manifest), projectId: manifest?.projectId ?? null, + error, refresh, versions: manifest?.versions ?? NO_VERSIONS, }; diff --git a/docs/adr/【ADR】AGC命令错误结构化与错误报告口径-2026-10-01.md b/docs/adr/【ADR】AGC命令错误结构化与错误报告口径-2026-10-01.md index d04e1b4a6..7b6ce3388 100644 --- a/docs/adr/【ADR】AGC命令错误结构化与错误报告口径-2026-10-01.md +++ b/docs/adr/【ADR】AGC命令错误结构化与错误报告口径-2026-10-01.md @@ -142,6 +142,7 @@ DirectProject 已经解决过同一类问题([`【ADR】DirectProject命令接 - 资源画布历史任务账本读取属于用户可见的恢复路径:异常正文必须进入提示条;只有响应形状错误这类没有底层异常的情况才使用明确的契约提示。 - 异步业务 facade 不得把底层异常折成“空结果”来逃避错误处理;只有真实的空回包、未配置或浏览器桥不可用等无正文事实允许返回空值,Tauri/网络/IPC 异常必须上抛到统一脱敏出口。 - 可选偏好可以安全回退默认值,但回退不能隐藏错误:偏好读写 IPC 失败必须同时保留安全正文、记录诊断并在当前输入上下文给出提示。 +- Direct 项目清单读取失败可以保留聊天可用性,但不得把素材/版本候选置空后静默;清单读取和事件订阅正文必须进入项目状态条。 - Direct 活动回合事件桥注册失败即使仍有一次性快照,也必须标明事件更新通道已失联并展示安全正文,不能只显示旧快照而让用户误以为活动状态持续可靠。 - 首页富文本剪贴板读取的“无内容”与“原生 IPC 失败”必须区分;后者展示安全正文,不能静默当作空剪贴板。 - 账号邀请码复制与充值状态确认的失败提示也必须附安全正文;操作指引不能替换底层剪贴板、网络或支付查询原因。 diff --git a/docs/project-memory/plans/【里程碑】AGC错误具体文本展示-2026-10-04.md b/docs/project-memory/plans/【里程碑】AGC错误具体文本展示-2026-10-04.md index 89bf188a7..999dc6f6c 100644 --- a/docs/project-memory/plans/【里程碑】AGC错误具体文本展示-2026-10-04.md +++ b/docs/project-memory/plans/【里程碑】AGC错误具体文本展示-2026-10-04.md @@ -63,6 +63,7 @@ DirectProject 回合失败在确认不是客户端内部不可归类故障时, - 资源画布历史生成任务账本读取失败原来统一显示“暂时无法恢复历史任务”;现在保留 Tauri/文件读取正文,非数组响应仍单独保留契约错误提示。 - 发现默认 `requestChatPromptPolish` 还会把 Tauri 润色命令异常吞成 `null`,导致共享 hook 无法看到平台正文;现仅对空回包/桥不可用保留 `null`,原生异常继续上抛并统一脱敏。 - 润色提醒偏好读取失败原来被当成“未关闭”默认值,写入失败只进错误上报池;现把读取/保存的 IPC 正文显示在输入区状态提示中,同时仍维持安全默认和回退行为。 +- Direct 项目清单首次读取和清单事件订阅失败原来只让 `@` 候选退化为空;现把脱敏后的读取/订阅正文送入项目聊天状态条。 - Direct 活动回合事件桥注册失败原来只退回启动快照,面板没有说明后续更新已失联;现保留事件桥 IPC 正文并在标题栏/活动项目面板显示。 - 首页富文本输入的原生剪贴板图片/文本读取失败原来直接按空剪贴板处理;现通过输入区错误提示保留脱敏后的剪贴板 IPC 正文。 - 账号邀请码复制和微信充值状态确认仍有固定“复制失败/暂时没能确认到账”路径;现保留剪贴板/支付查询错误正文并继续给操作指引。