import React, { useEffect, useRef } from 'react'; import { Avatar, Typography, Space, Tag } from 'antd'; import { UserOutlined, RobotOutlined, LoadingOutlined } from '@ant-design/icons'; import ReactMarkdown from 'react-markdown'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { oneLight } from 'react-syntax-highlighter/dist/esm/styles/prism'; import remarkGfm from 'remark-gfm'; import './MessageList.css'; const { Text } = Typography; interface Message { id: string; role: 'user' | 'assistant'; content: string; model?: string; tokens?: number; createdAt: string; isStreaming?: boolean; } interface MessageListProps { messages: Message[]; loading?: boolean; streamingContent?: string; } const MessageList: React.FC = ({ messages, loading = false, streamingContent = '' }) => { const messagesEndRef = useRef(null); // 自动滚动到底部 const scrollToBottom = () => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }; useEffect(() => { scrollToBottom(); }, [messages, streamingContent]); // Markdown代码块渲染 const MarkdownComponents = { code({ node, inline, className, children, ...props }: any) { const match = /language-(\w+)/.exec(className || ''); return !inline && match ? ( {String(children).replace(/\n$/, '')} ) : ( {children} ); }, }; const renderMessage = (message: Message) => { const isUser = message.role === 'user'; return (
: } style={{ backgroundColor: isUser ? '#1890ff' : '#52c41a', }} />
{isUser ? '我' : 'AI助手'} {message.model && ( {message.model} )} {new Date(message.createdAt).toLocaleTimeString('zh-CN')}
{isUser ? (
{message.content}
) : (
{message.content}
)}
{message.tokens && (
消耗Token: {message.tokens}
)}
); }; return (
{messages.map(renderMessage)} {/* 流式输出中的消息 */} {loading && streamingContent && (
} style={{ backgroundColor: '#52c41a' }} />
AI助手 生成中...
{streamingContent}
)} {/* 仅显示loading,无流式内容 */} {loading && !streamingContent && (
} style={{ backgroundColor: '#52c41a' }} />
AI正在思考中...
)}
{/* 自动滚动锚点 */}
); }; export default MessageList;