Files
AIclinicalresearch/frontend/src/components/chat/MessageList.tsx
AI Clinical Dev Team 0f0940534f fix: resolve black code block background and Ant Design warnings
Issue 1: Black background appearing in chat (CRITICAL)
- Cause: Using vscDarkPlus (dark theme) for code syntax highlighting
- Solution: Changed to oneLight (light theme) + custom styling
- Result: Code blocks now have light gray background matching UI

Issue 2: Ant Design deprecation warnings
- Removed deprecated 'popupClassName' from Select
- Removed deprecated 'overlayStyle' from Tooltip
- These warnings don't affect functionality but cleaned up console

Changes:
- MessageList.tsx: vscDarkPlus 鈫?oneLight + custom styles
- ModelSelector.tsx: Removed deprecated props

Test: Ask AI to write code, should see light-themed code blocks
2025-10-10 22:24:09 +08:00

194 lines
5.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<MessageListProps> = ({
messages,
loading = false,
streamingContent = ''
}) => {
const messagesEndRef = useRef<HTMLDivElement>(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 ? (
<SyntaxHighlighter
style={oneLight}
language={match[1]}
PreTag="div"
customStyle={{
backgroundColor: '#fafafa',
border: '1px solid #e8e8e8',
borderRadius: '6px',
fontSize: '13px',
margin: '12px 0',
}}
{...props}
>
{String(children).replace(/\n$/, '')}
</SyntaxHighlighter>
) : (
<code className={className} {...props}>
{children}
</code>
);
},
};
const renderMessage = (message: Message) => {
const isUser = message.role === 'user';
return (
<div
key={message.id}
className={`message-item ${isUser ? 'message-user' : 'message-assistant'}`}
>
<div className="message-avatar">
<Avatar
size={40}
icon={isUser ? <UserOutlined /> : <RobotOutlined />}
style={{
backgroundColor: isUser ? '#1890ff' : '#52c41a',
}}
/>
</div>
<div className="message-content">
<div className="message-header">
<Text strong>{isUser ? '我' : 'AI助手'}</Text>
{message.model && (
<Tag color="blue" style={{ marginLeft: 8 }}>
{message.model}
</Tag>
)}
<Text type="secondary" style={{ marginLeft: 8, fontSize: 12 }}>
{new Date(message.createdAt).toLocaleTimeString('zh-CN')}
</Text>
</div>
<div className="message-body">
{isUser ? (
<div className="user-message-text">{message.content}</div>
) : (
<div className="assistant-message-text">
<ReactMarkdown
remarkPlugins={[remarkGfm]}
components={MarkdownComponents}
>
{message.content}
</ReactMarkdown>
</div>
)}
</div>
{message.tokens && (
<div className="message-footer">
<Text type="secondary" style={{ fontSize: 12 }}>
Token: {message.tokens}
</Text>
</div>
)}
</div>
</div>
);
};
return (
<div className="message-list">
<Space direction="vertical" size="large" style={{ width: '100%' }}>
{messages.map(renderMessage)}
{/* 流式输出中的消息 */}
{loading && streamingContent && (
<div className="message-item message-assistant">
<div className="message-avatar">
<Avatar
size={40}
icon={<RobotOutlined />}
style={{ backgroundColor: '#52c41a' }}
/>
</div>
<div className="message-content">
<div className="message-header">
<Text strong>AI助手</Text>
<Tag color="processing" style={{ marginLeft: 8 }}>
<LoadingOutlined /> ...
</Tag>
</div>
<div className="message-body">
<div className="assistant-message-text streaming">
<ReactMarkdown
remarkPlugins={[remarkGfm]}
components={MarkdownComponents}
>
{streamingContent}
</ReactMarkdown>
<span className="cursor-blink"></span>
</div>
</div>
</div>
</div>
)}
{/* 仅显示loading无流式内容 */}
{loading && !streamingContent && (
<div className="message-item message-assistant">
<div className="message-avatar">
<Avatar
size={40}
icon={<LoadingOutlined spin />}
style={{ backgroundColor: '#52c41a' }}
/>
</div>
<div className="message-content">
<Text type="secondary">AI正在思考中...</Text>
</div>
</div>
)}
</Space>
{/* 自动滚动锚点 */}
<div ref={messagesEndRef} />
</div>
);
};
export default MessageList;