Files
agent/app/components/Message.tsx
2025-10-18 04:49:50 +00:00

168 lines
5.7 KiB
TypeScript

import React, { useRef } from 'react';
import { Box, Paper, Typography, Avatar, useMediaQuery, useTheme } from '@mui/material';
import ReactMarkdown from 'react-markdown';
import rehypeRaw from 'rehype-raw';
import remarkGfm from 'remark-gfm';
import type { Message as MessageType } from '../types/types';
interface MessageProps {
message: MessageType;
isLast?: boolean;
}
const Message: React.FC<MessageProps> = ({ message, isLast = false }) => {
const isUser = message.sender === 'user';
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
const paperRef = useRef<HTMLDivElement>(null);
// 计算气泡的borderRadius
const borderRadius = isUser
? '18px 18px 4px 18px'
: '18px 18px 18px 4px';
return (
<Box
display="flex"
marginBottom={2}
justifyContent={isUser ? "flex-end" : "flex-start"}
alignItems="flex-end"
>
<Box
display="flex"
alignItems="flex-end"
maxWidth={isMobile ? "85%" : "70%"}
gap={1.5}
>
{!isUser && (
<Avatar
sx={{
width: 36,
height: 36,
bgcolor: theme.palette.primary.main,
boxShadow: theme.shadows[2],
transition: 'transform 0.2s',
'&:hover': {
transform: 'scale(1.05)',
}
}}
>
🤖
</Avatar>
)}
<Paper
ref={paperRef}
elevation={isLast ? 2 : 1}
sx={{
padding: 2,
backgroundColor: isUser
? theme.palette.primary.main
: theme.palette.background.default,
color: isUser ? 'white' : theme.palette.text.primary,
borderRadius: borderRadius,
position: 'relative',
transition: 'all 0.3s ease',
'&:hover': {
boxShadow: theme.shadows[isLast ? 4 : 2],
transform: 'translateY(-1px)',
}
}}
>
<div style={{ lineHeight: 1.6 }}>
{isUser ? (
// 用户消息仍然使用普通文本展示
message.content.split('\n').map((line: string, index: number) => (
<React.Fragment key={index}>
{line}
{index < message.content.split('\n').length - 1 && <br />}
</React.Fragment>
))
) : (
// 大模型回复使用markdown格式展示
<ReactMarkdown
rehypePlugins={[rehypeRaw]}
remarkPlugins={[remarkGfm]}
components={{
// 自定义标题样式
h1: (props: any) => (
<h1 style={{ fontSize: '1.4rem', fontWeight: 'bold', margin: '12px 0 8px 0' }} {...props} />
),
h2: (props: any) => (
<h2 style={{ fontSize: '1.2rem', fontWeight: 'bold', margin: '10px 0 6px 0' }} {...props} />
),
h3: (props: any) => (
<h3 style={{ fontSize: '1.1rem', fontWeight: 'bold', margin: '8px 0 4px 0' }} {...props} />
),
// 自定义列表样式
ul: (props: any) => (
<ul style={{ paddingLeft: '20px', margin: '6px 0' }} {...props} />
),
ol: (props: any) => (
<ol style={{ paddingLeft: '20px', margin: '6px 0' }} {...props} />
),
li: (props: any) => (
<li style={{ margin: '4px 0' }} {...props} />
),
// 自定义代码块样式
// 简化代码块处理,移除对 node 对象内部属性的访问
code: (props: any) => (
<code style={{ backgroundColor: '#f5f5f5', padding: '2px 4px', borderRadius: '3px', fontFamily: 'monospace' }} {...props} />
),
pre: (props: any) => (
<pre style={{ backgroundColor: '#f5f5f5', padding: '12px', borderRadius: '6px', overflowX: 'auto', fontFamily: 'monospace', fontSize: '0.9rem' }} {...props} />
),
// 自定义引用样式
blockquote: (props: any) => (
<blockquote style={{ borderLeft: '4px solid #ccc', margin: '10px 0', paddingLeft: '12px', color: '#666' }} {...props} />
),
// 自定义链接样式
a: (props: any) => (
<a style={{ color: theme.palette.primary.main, textDecoration: 'underline' }} {...props} />
)
}}
>
{message.content}
</ReactMarkdown>
)}
</div>
<Typography
variant="caption"
sx={{
display: 'block',
marginTop: 1,
opacity: 0.7,
fontSize: '0.75rem',
textAlign: 'right'
}}
>
{message.timestamp.toLocaleTimeString([], {
hour: '2-digit',
minute: '2-digit'
})}
</Typography>
</Paper>
{isUser && (
<Avatar
sx={{
width: 36,
height: 36,
bgcolor: theme.palette.primary.dark,
boxShadow: theme.shadows[2],
transition: 'transform 0.2s',
'&:hover': {
transform: 'scale(1.05)',
}
}}
>
👤
</Avatar>
)}
</Box>
</Box>
);
};
export default Message;