diff --git a/apps/ai-game-creator-shell/src/components/ChatMarkdownMessage/index.tsx b/apps/ai-game-creator-shell/src/components/ChatMarkdownMessage/index.tsx new file mode 100644 index 000000000..9920408ea --- /dev/null +++ b/apps/ai-game-creator-shell/src/components/ChatMarkdownMessage/index.tsx @@ -0,0 +1,141 @@ +import type { ErrorInfo, ReactNode } from 'react'; +import { Component } from 'react'; +import ReactMarkdown, { type Components } from 'react-markdown'; +import remarkGfm from 'remark-gfm'; + +export type ChatMarkdownMessageProps = { + text: string; + role: 'assistant' | 'user'; + streaming?: boolean; +}; + +type MarkdownErrorBoundaryProps = { + fallbackText: string; + children: ReactNode; +}; + +type MarkdownErrorBoundaryState = { + hasError: boolean; +}; + +class MarkdownErrorBoundary extends Component< + MarkdownErrorBoundaryProps, + MarkdownErrorBoundaryState +> { + state: MarkdownErrorBoundaryState = { hasError: false }; + + static getDerivedStateFromError(): MarkdownErrorBoundaryState { + return { hasError: true }; + } + + componentDidCatch(_error: unknown, _errorInfo: ErrorInfo) { + // Keep the original message visible without logging its potentially sensitive content. + } + + render() { + if (this.state.hasError) { + return ( + + {this.props.fallbackText} + + ); + } + return this.props.children; + } +} + +const markdownComponents: Components = { + a: ({ children }) => {children}, + img: ({ alt }) => {alt?.trim() ? `图片:${alt}` : '图片已省略'}, + h1: ({ children }) => ( +
{children}
, + ul: ({ children }) => ( ++ {children} ++ ), + pre: ({ children }) => ( +
+ {children}
+
+ ),
+ code: ({ className, children, ...props }) => {
+ const isBlock = Boolean(className?.includes('language-'));
+ return isBlock ? (
+
+ {children}
+
+ ) : (
+
+ {children}
+
+ );
+ },
+ table: ({ children }) => (
+