From 141eff203d402a0aeab5bbeef24579849a40dc8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Fri, 4 Sep 2026 11:46:47 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=81=8A=E5=A4=A9=20Markdown?= =?UTF-8?q?=20=E6=B6=88=E6=81=AF=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为 assistant 消息提供 GFM 与流式 Markdown 渲染 限制 HTML、链接和图片为纯文本展示 使用 Tailwind 内联样式并提供单条消息降级 --- .../components/ChatMarkdownMessage/index.tsx | 141 ++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 apps/ai-game-creator-shell/src/components/ChatMarkdownMessage/index.tsx 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}

+ ), + h2: ({ children }) => ( +

{children}

+ ), + h3: ({ children }) => ( +

{children}

+ ), + p: ({ children }) =>

{children}

, + ul: ({ children }) => ( + + ), + ol: ({ children }) => ( +
    {children}
+ ), + li: ({ children }) =>
  • {children}
  • , + blockquote: ({ children }) => ( +
    + {children} +
    + ), + pre: ({ children }) => ( +
    +      {children}
    +    
    + ), + code: ({ className, children, ...props }) => { + const isBlock = Boolean(className?.includes('language-')); + return isBlock ? ( + + {children} + + ) : ( + + {children} + + ); + }, + table: ({ children }) => ( +
    + + {children} +
    +
    + ), + th: ({ children }) => ( + + {children} + + ), + td: ({ children }) => ( + + {children} + + ), + hr: () => ( +
    + ), +}; + +export function ChatMarkdownMessage({ + text, + role, + streaming = false, +}: ChatMarkdownMessageProps) { + if (role === 'user') { + return {text}; + } + + return ( + +
    + + {text} + +
    +
    + ); +}