diff --git a/apps/ai-game-creator-shell/src/app/AuthenticatedClient.tsx b/apps/ai-game-creator-shell/src/app/AuthenticatedClient.tsx index 40c0a8606..843ef706e 100644 --- a/apps/ai-game-creator-shell/src/app/AuthenticatedClient.tsx +++ b/apps/ai-game-creator-shell/src/app/AuthenticatedClient.tsx @@ -3,14 +3,13 @@ import { type ErrorInfo, type FormEvent, type ReactNode, - useCallback, useEffect, useState, } from 'react'; import type { AuthUser } from '../../../../packages/shared/src/contracts/auth'; import brandIcon from '../../../../packages/shared/src/icons/taonier-product-ip.png'; -import { ErrorReportDialog } from '../components/error-report/ErrorReportDialog'; +import { ErrorReportCenter } from '../components/error-report/ErrorReportDialog'; import { clearStoredAuthAccessToken, getClientAuthErrorMessage, @@ -34,7 +33,7 @@ import { import { captureClientError, installWebviewLogBridge, - OPEN_ERROR_REPORT_EVENT, + shouldCaptureClientError, } from '../services/errorReporting'; import { beginPlatformSessionClearTransition, @@ -50,7 +49,6 @@ import { type ClientRuntimeErrorBoundaryProps = { children: ReactNode; onLogout: () => void; - onReport?: () => void; }; type ClientRuntimeErrorBoundaryState = { @@ -95,11 +93,6 @@ export class ClientRuntimeErrorBoundary extends Component< - {this.props.onReport ? ( - - ) : null} ); @@ -123,7 +116,6 @@ export function AuthenticatedClient({ const [loginBusy, setLoginBusy] = useState(false); const [codeBusy, setCodeBusy] = useState(false); const [codeCooldownSeconds, setCodeCooldownSeconds] = useState(0); - const [errorReportOpen, setErrorReportOpen] = useState(false); const initialServerSelection = getClientServerSelection(); const [serverSelection, setServerSelection] = useState( initialServerSelection, @@ -131,13 +123,6 @@ export function AuthenticatedClient({ const [customServerUrl, setCustomServerUrl] = useState( initialServerSelection.customBaseUrl, ); - const openManualErrorReport = useCallback(async () => { - await captureClientError(new Error('用户主动报告问题'), { - source: 'manual', - }); - setErrorReportOpen(true); - }, []); - useEffect(() => { const uninstallWebviewLogBridge = installWebviewLogBridge(); const handleError = (event: ErrorEvent) => { @@ -157,15 +142,6 @@ export function AuthenticatedClient({ }; }, []); - useEffect(() => { - window.addEventListener(OPEN_ERROR_REPORT_EVENT, openManualErrorReport); - return () => - window.removeEventListener( - OPEN_ERROR_REPORT_EVENT, - openManualErrorReport, - ); - }, [openManualErrorReport]); - function persistServerSelection() { try { const next = setClientServerSelection({ @@ -357,10 +333,11 @@ export function AuthenticatedClient({ setCodeCooldownSeconds(Math.max(0, Math.floor(response.cooldownSeconds))); setLoginStatus(`验证码已发送,${response.expiresInSeconds} 秒内有效`); } catch (error) { - void captureClientError(error, { - source: 'auth', - action: 'send-login-code', - }); + if (shouldCaptureClientError(error)) + void captureClientError(error, { + source: 'auth', + action: 'send-login-code', + }); setLoginStatus(error instanceof Error ? error.message : String(error)); } finally { setCodeBusy(false); @@ -419,7 +396,8 @@ export function AuthenticatedClient({ setCode(''); setPassword(''); } catch (error) { - void captureClientError(error, { source: 'auth', action: 'login' }); + if (shouldCaptureClientError(error)) + void captureClientError(error, { source: 'auth', action: 'login' }); setLoginStatus(error instanceof Error ? error.message : String(error)); } finally { setLoginBusy(false); @@ -589,16 +567,10 @@ export function AuthenticatedClient({ return ( <> - void openManualErrorReport()} - > + {children({ user: authUser, logout })} - setErrorReportOpen(false)} - /> + ); } diff --git a/apps/ai-game-creator-shell/src/features/project-summary/projectDeliverySummaries.ts b/apps/ai-game-creator-shell/src/features/project-summary/projectDeliverySummaries.ts index 61ad15279..0e3d4091c 100644 --- a/apps/ai-game-creator-shell/src/features/project-summary/projectDeliverySummaries.ts +++ b/apps/ai-game-creator-shell/src/features/project-summary/projectDeliverySummaries.ts @@ -159,7 +159,7 @@ export function summarizeProjectKnownIssues( };预览 ${previewSummary}`, `- 已知问题:${knownIssueSummary}`, '- 试玩限制:本地 Web 原型;小范围 5-10 分钟试玩;不承诺账号、云存档、排行榜、付费或长期兼容', - '- 反馈入口:单个问题走 /bug-report;整体体验走 /feedback;版本变化走 /release-notes', + '- 反馈入口:客户端问题由自动诊断提示;整体体验走 /feedback;版本变化走 /release-notes', '- 发送前检查:可试玩状态先 /run;交付口径看 /share;对外资料看 /media-kit', '- 边界:只准备已知问题清单;不读取文件;不启动或打开预览;不导出试玩包;不上传云端;不发布作品;不写项目', `- 建议:${draftCommand}`, diff --git a/apps/ai-game-creator-shell/src/features/project-summary/projectGuidanceSummaries.ts b/apps/ai-game-creator-shell/src/features/project-summary/projectGuidanceSummaries.ts index ca30a9ab9..2bf3d12f9 100644 --- a/apps/ai-game-creator-shell/src/features/project-summary/projectGuidanceSummaries.ts +++ b/apps/ai-game-creator-shell/src/features/project-summary/projectGuidanceSummaries.ts @@ -75,7 +75,6 @@ export function summarizeNextProjectActions( addSuggestion('查看隐私与导出边界', '/privacy'); addSuggestion('查看首批试玩对象', '/audience'); addSuggestion('准备试玩邀请文案', '/invite'); - addSuggestion('报告问题', '/bug-report'); addSuggestion('准备试玩问卷问题', '/survey'); addSuggestion('准备封面与缩略图检查', '/cover'); addSuggestion('准备宣传截图清单', '/screenshots'); diff --git a/apps/ai-game-creator-shell/src/features/project-summary/projectPlaytestSummaries.ts b/apps/ai-game-creator-shell/src/features/project-summary/projectPlaytestSummaries.ts index 18442f17c..53c928a77 100644 --- a/apps/ai-game-creator-shell/src/features/project-summary/projectPlaytestSummaries.ts +++ b/apps/ai-game-creator-shell/src/features/project-summary/projectPlaytestSummaries.ts @@ -290,8 +290,8 @@ export function summarizeProjectPlaytestSurvey( '- 使用场景:发给首批 3-5 位测试者;每人 5-10 分钟;先自由玩一局再回答', '- 问题清单:1. 30 秒内你觉得目标是什么?2. 第一次操作哪里最卡?3. 胜负/重开是否清楚?4. 难度/节奏感觉如何?5. 最想保留和最想改的各一项?', '- 记录格式:每题 1-5 分 + 一句话;补充设备、输入方式、是否愿意再玩一局', - '- 追踪方式:单个问题走 /bug-report;整体反馈走 /feedback;下一轮改动走 /revise', - '- 参考:/invite;/audience;/feedback;/bug-report', + '- 追踪方式:客户端问题由自动诊断提示;整体反馈走 /feedback;下一轮改动走 /revise', + '- 参考:/invite;/audience;/feedback', '- 边界:只准备试玩问卷;不读取文件;不启动或打开预览;不导出试玩包;不写项目', `- 建议:${draftCommand}`, ].join('\n'), @@ -557,7 +557,7 @@ export function summarizeProjectPlaytestFaq( };预览 ${previewSummary}`, '- 问答清单:1. 这是什么?2. 怎么开始和重开?3. 需要反馈什么?4. 打不开或卡住怎么办?5. 能不能转发或公开?', '- 回答口径:早期本地 Web 原型;5-10 分钟试玩;重点反馈目标理解、操作手感、难度、bug 和还想不想再玩', - '- 测试者提醒:先自由玩一局;不要评价完成度;问卷走 /survey;单个问题走 /bug-report', + '- 测试者提醒:先自由玩一局;不要评价完成度;问卷走 /survey;客户端问题由自动诊断提示', '- 交付搭配:/invite;/share;/screenshots;/trailer', '- 边界:只准备试玩常见问答;不读取文件;不启动或打开预览;不导出试玩包;不写项目', `- 建议:${draftCommand}`, @@ -624,7 +624,7 @@ export function summarizeProjectCommunityPost( `- 短文案:我做了一个早期 Web 小游戏原型《${nextManifest.name}》,核心目标是${goal}。想找 3-5 位朋友试玩 5 分钟,重点看能不能理解目标、操作顺不顺、还想不想再来一局。`, '- 长文案结构:一句玩法目标;一张截图或短视频;试玩方式;希望收到的三类反馈;已知限制', '- 标签建议:#Web小游戏 #原型试玩 #AI游戏创作 #本地试玩', - '- CTA:愿意试玩请回复;遇到问题按 /faq 或 /bug-report 的口径反馈', + '- CTA:愿意试玩请回复;遇到问题按 /faq 的口径反馈,客户端问题会自动提示', '- 参考:/faq;/screenshots;/trailer;/store;/share', '- 边界:只准备社区发布文案;不上传云端;不发布作品;不读取文件;不启动或打开预览;不导出试玩包;不写项目', `- 建议:${draftCommand}`, diff --git a/apps/ai-game-creator-shell/src/features/project-summary/projectQualitySummaries.ts b/apps/ai-game-creator-shell/src/features/project-summary/projectQualitySummaries.ts index 0057fbb32..d95a6428f 100644 --- a/apps/ai-game-creator-shell/src/features/project-summary/projectQualitySummaries.ts +++ b/apps/ai-game-creator-shell/src/features/project-summary/projectQualitySummaries.ts @@ -290,7 +290,7 @@ export function summarizeProjectCompatibilityNotes( `- 输入兼容:${inputSummary}`, '- 推荐环境:桌面 Chrome / Edge 最新版;本机 127.0.0.1 预览;移动浏览器只做早期体验', '- 不承诺:旧浏览器、低端设备、离线模式、云存档、账号同步、手柄或多端数据一致', - '- 反馈口径:设备 / 浏览器 / 输入方式 / 截图或录屏;问题记录走 /bug-report', + '- 反馈口径:设备 / 浏览器 / 输入方式 / 截图或录屏;客户端检测到问题时会自动提示', '- 参考:/mobile;/accessibility;/performance;/known-issues', '- 边界:只准备兼容性说明;不读取文件;不启动或打开预览;不导出试玩包;不上传云端;不发布作品;不写项目', `- 建议:${draftCommand}`, diff --git a/apps/ai-game-creator-shell/src/features/project-summary/projectSummaryConstants.ts b/apps/ai-game-creator-shell/src/features/project-summary/projectSummaryConstants.ts index 1e9418aa5..8329268ae 100644 --- a/apps/ai-game-creator-shell/src/features/project-summary/projectSummaryConstants.ts +++ b/apps/ai-game-creator-shell/src/features/project-summary/projectSummaryConstants.ts @@ -123,7 +123,6 @@ export const chatCommandHelp = [ '/privacy:查看隐私与导出边界', '/audience:查看首批试玩对象', '/invite:准备试玩邀请文案', - '/bug-report:报告问题', '/survey:准备试玩问卷问题', '/cover:准备封面与缩略图检查', '/screenshots:准备宣传截图清单', diff --git a/apps/ai-game-creator-shell/src/features/project-workspace/projectSummaryCommands.ts b/apps/ai-game-creator-shell/src/features/project-workspace/projectSummaryCommands.ts index 25ada417a..065975c6a 100644 --- a/apps/ai-game-creator-shell/src/features/project-workspace/projectSummaryCommands.ts +++ b/apps/ai-game-creator-shell/src/features/project-workspace/projectSummaryCommands.ts @@ -9,7 +9,6 @@ import type { AgentStatusCard, ChatMessage, } from '../../app/types'; -import { OPEN_ERROR_REPORT_EVENT } from '../../services/errorReporting'; import { summarizeAgentReviewState, summarizeAgentRunBudget, @@ -506,20 +505,6 @@ export function handleProjectSummaryChatCommand({ return true; } - if (prompt === '/bug-report') { - setMessages((current) => [ - ...current, - { - role: 'assistant', - text: '已打开“报告问题”面板,请填写遇到的问题。', - }, - ]); - if (typeof window !== 'undefined') { - window.dispatchEvent(new CustomEvent(OPEN_ERROR_REPORT_EVENT)); - } - return true; - } - if (prompt === '/survey') { if (!requireChatProjectForUserAction()) { return true; diff --git a/apps/ai-game-creator-shell/tests/appSurface/developer-agent-window.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/developer-agent-window.suite.ts index 7a9d2cad7..edb340240 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/developer-agent-window.suite.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/developer-agent-window.suite.ts @@ -5072,7 +5072,6 @@ export function registerDeveloperToolsTests() { expect(screen.getByText(/\/privacy:查看隐私与导出边界/)).not.toBeNull(); expect(screen.getByText(/\/audience:查看首批试玩对象/)).not.toBeNull(); expect(screen.getByText(/\/invite:准备试玩邀请文案/)).not.toBeNull(); - expect(screen.getByText(/\/bug-report:报告问题/)).not.toBeNull(); expect(screen.getByText(/\/survey:准备试玩问卷问题/)).not.toBeNull(); expect(screen.getByText(/\/cover:准备封面与缩略图检查/)).not.toBeNull(); expect(screen.getByText(/\/screenshots:准备宣传截图清单/)).not.toBeNull(); diff --git a/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/assert-playtest-and-release-shortcuts.ts b/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/assert-playtest-and-release-shortcuts.ts index 76ea3bb15..cbcc6989f 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/assert-playtest-and-release-shortcuts.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/assert-playtest-and-release-shortcuts.ts @@ -1,4 +1,3 @@ -import { OPEN_ERROR_REPORT_EVENT } from '../../../../src/services/errorReporting'; import { expect, fireEvent, screen, submitChat, within } from '../../harness'; import type { PreviewShortcutInvoke } from './invoke-mock'; @@ -47,7 +46,6 @@ export async function assertPlaytestAndReleaseShortcutFlow( expect(screen.getByText(/查看隐私与导出边界:\/privacy/)).not.toBeNull(); expect(screen.getByText(/查看首批试玩对象:\/audience/)).not.toBeNull(); expect(screen.getByText(/准备试玩邀请文案:\/invite/)).not.toBeNull(); - expect(screen.getByText(/报告问题:\/bug-report/)).not.toBeNull(); expect(screen.getByText(/准备试玩问卷问题:\/survey/)).not.toBeNull(); expect(screen.getByText(/准备封面与缩略图检查:\/cover/)).not.toBeNull(); expect(screen.getByText(/准备宣传截图清单:\/screenshots/)).not.toBeNull(); @@ -648,20 +646,6 @@ export async function assertPlaytestAndReleaseShortcutFlow( ([command]) => command === 'control_agent_run', ).length, }; - let openedErrorReport = false; - const onErrorReportOpen = () => { - openedErrorReport = true; - }; - window.addEventListener(OPEN_ERROR_REPORT_EVENT, onErrorReportOpen); - try { - submitChat('/bug-report'); - expect( - await screen.findByText('已打开“报告问题”面板,请填写遇到的问题。'), - ).not.toBeNull(); - expect(openedErrorReport).toBe(true); - } finally { - window.removeEventListener(OPEN_ERROR_REPORT_EVENT, onErrorReportOpen); - } expect( invoke.mock.calls.filter( ([command]) => command === 'get_local_game_manifest', @@ -753,10 +737,10 @@ export async function assertPlaytestAndReleaseShortcutFlow( '记录格式:每题 1-5 分 + 一句话;补充设备、输入方式、是否愿意再玩一局', ); expect(surveyMessage.textContent).toContain( - '追踪方式:单个问题走 /bug-report;整体反馈走 /feedback;下一轮改动走 /revise', + '追踪方式:客户端问题由自动诊断提示;整体反馈走 /feedback;下一轮改动走 /revise', ); expect(surveyMessage.textContent).toContain( - '参考:/invite;/audience;/feedback;/bug-report', + '参考:/invite;/audience;/feedback', ); expect(surveyMessage.textContent).toContain( '边界:只准备试玩问卷;不读取文件;不启动或打开预览;不导出试玩包;不写项目', @@ -1185,7 +1169,7 @@ export async function assertPlaytestAndReleaseShortcutFlow( '回答口径:早期本地 Web 原型;5-10 分钟试玩;重点反馈目标理解、操作手感、难度、bug 和还想不想再玩', ); expect(faqMessage.textContent).toContain( - '测试者提醒:先自由玩一局;不要评价完成度;问卷走 /survey;单个问题走 /bug-report', + '测试者提醒:先自由玩一局;不要评价完成度;问卷走 /survey;客户端问题由自动诊断提示', ); expect(faqMessage.textContent).toContain( '交付搭配:/invite;/share;/screenshots;/trailer', @@ -1296,7 +1280,7 @@ export async function assertPlaytestAndReleaseShortcutFlow( '标签建议:#Web小游戏 #原型试玩 #AI游戏创作 #本地试玩', ); expect(postMessage.textContent).toContain( - 'CTA:愿意试玩请回复;遇到问题按 /faq 或 /bug-report 的口径反馈', + 'CTA:愿意试玩请回复;遇到问题按 /faq 的口径反馈,客户端问题会自动提示', ); expect(postMessage.textContent).toContain( '参考:/faq;/screenshots;/trailer;/store;/share', @@ -1735,7 +1719,7 @@ export async function assertPlaytestAndReleaseShortcutFlow( '试玩限制:本地 Web 原型;小范围 5-10 分钟试玩;不承诺账号、云存档、排行榜、付费或长期兼容', ); expect(knownIssueMessage.textContent).toContain( - '反馈入口:单个问题走 /bug-report;整体体验走 /feedback;版本变化走 /release-notes', + '反馈入口:客户端问题由自动诊断提示;整体体验走 /feedback;版本变化走 /release-notes', ); expect(knownIssueMessage.textContent).toContain( '发送前检查:可试玩状态先 /run;交付口径看 /share;对外资料看 /media-kit', diff --git a/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/assert-project-and-design-shortcuts.ts b/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/assert-project-and-design-shortcuts.ts index 97418c335..1aba097b3 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/assert-project-and-design-shortcuts.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/assert-project-and-design-shortcuts.ts @@ -916,7 +916,7 @@ export async function assertProjectAndDesignShortcutFlow( '不承诺:旧浏览器、低端设备、离线模式、云存档、账号同步、手柄或多端数据一致', ); expect(compatibilityMessage.textContent).toContain( - '反馈口径:设备 / 浏览器 / 输入方式 / 截图或录屏;问题记录走 /bug-report', + '反馈口径:设备 / 浏览器 / 输入方式 / 截图或录屏;客户端检测到问题时会自动提示', ); expect(compatibilityMessage.textContent).toContain( '参考:/mobile;/accessibility;/performance;/known-issues', diff --git a/docs/technical/【技术方案】AGC错误报告与诊断上传-2026-08-31.md b/docs/technical/【技术方案】AGC错误报告与诊断上传-2026-08-31.md index 4c6f8e259..bd85e77cb 100644 --- a/docs/technical/【技术方案】AGC错误报告与诊断上传-2026-08-31.md +++ b/docs/technical/【技术方案】AGC错误报告与诊断上传-2026-08-31.md @@ -11,7 +11,8 @@ AI Game Creator Shell 采用 IDEA 风格的当前进程错误报告:错误事 - 指纹计算可使用调用方的 page/action 及脱敏后的首个调用点作为进程内区分输入,但这些上下文不会作为事件字段上传;消息与 stack 在入池前统一脱敏,WebCrypto 失败时降级为稳定可读指纹,采集本身不得产生新的未处理拒绝。 - 客户端 API 自动采集只覆盖网络错误、408 和 5xx;预期的 4xx 登录/鉴权失败不进入错误报告池。 - Rust 侧通过 `app_log!` 将普通文本日志同时输出到 stderr 和 AppData `diagnostics/application.log`,超出 256 KiB 滚动到 `application.previous.log`;WebView 的 console 输出通过 `append_application_log` 镜像到同一 raw log,并在客户端桥接处再次脱敏;`read_diagnostic_logs` 只读取应用级日志。 -- 报告面板允许填写最多 2,000 字中文描述并取消日志附件;本版本不支持截图或任意文件附件。 +- 报告面板只由自动诊断通知中的“查看并报告”打开,不提供聊天命令、崩溃页按钮或其他手动入口;默认选中最新事件,其他事件可勾选。允许填写最多 2,000 字中文描述并取消日志附件;本版本不支持截图或任意文件附件。 +- 错误事件先在当前进程内存池按 fingerprint 合并,经过 5 秒聚合后只显示一条非阻塞通知;通知支持“查看并报告”和“忽略”,同一 fingerprint 在本次运行中只提醒一次。通知不直接打开阻塞式报告面板。 - 上传失败只在当前进程显示失败并允许用户再次提交,不跨重启恢复事件池,不后台自动重试。 ## HTTP 与存储