diff --git a/apps/ai-game-creator-shell/src/components/ChatMarkdownMessage/index.tsx b/apps/ai-game-creator-shell/src/components/ChatMarkdownMessage/index.tsx index 3578d5801..ab7df6394 100644 --- a/apps/ai-game-creator-shell/src/components/ChatMarkdownMessage/index.tsx +++ b/apps/ai-game-creator-shell/src/components/ChatMarkdownMessage/index.tsx @@ -18,7 +18,7 @@ type MarkdownErrorBoundaryState = { hasError: boolean; }; -class MarkdownErrorBoundary extends Component< +export class MarkdownErrorBoundary extends Component< MarkdownErrorBoundaryProps, MarkdownErrorBoundaryState > { @@ -32,6 +32,15 @@ class MarkdownErrorBoundary extends Component< // Keep the original message visible without logging its potentially sensitive content. } + componentDidUpdate(prevProps: MarkdownErrorBoundaryProps) { + if ( + this.state.hasError && + prevProps.fallbackText !== this.props.fallbackText + ) { + this.setState({ hasError: false }); + } + } + render() { if (this.state.hasError) { return ( diff --git a/apps/ai-game-creator-shell/tests/ChatMarkdownMessage.test.tsx b/apps/ai-game-creator-shell/tests/ChatMarkdownMessage.test.tsx index 82aec9c8e..149b4d25c 100644 --- a/apps/ai-game-creator-shell/tests/ChatMarkdownMessage.test.tsx +++ b/apps/ai-game-creator-shell/tests/ChatMarkdownMessage.test.tsx @@ -1,9 +1,19 @@ // @vitest-environment jsdom import { render } from '@testing-library/react'; -import { describe, expect, it } from 'vitest'; +import { describe, expect, it, vi } from 'vitest'; -import { ChatMarkdownMessage } from '../src/components/ChatMarkdownMessage'; +import { + ChatMarkdownMessage, + MarkdownErrorBoundary, +} from '../src/components/ChatMarkdownMessage'; + +function FailingChild({ shouldThrow }: { shouldThrow: boolean }) { + if (shouldThrow) { + throw new Error('transient render failure'); + } + return markdown recovered; +} describe('ChatMarkdownMessage', () => { it('渲染 assistant 的 GFM 内容与代码块', () => { @@ -95,6 +105,31 @@ describe('ChatMarkdownMessage', () => { expect(code?.className).not.toContain('rounded'); }); + it('累计文本变化后可从 Markdown 错误回退中恢复', () => { + const consoleError = vi + .spyOn(console, 'error') + .mockImplementation(() => {}); + try { + const { container, rerender } = render( + + + , + ); + + expect(container.textContent).toBe('partial'); + + rerender( + + + , + ); + + expect(container.textContent).toBe('markdown recovered'); + } finally { + consoleError.mockRestore(); + } + }); + it('保留流式 assistant 文本并标记 streaming 状态', () => { const { container } = render(