168 lines
5.9 KiB
TypeScript
168 lines
5.9 KiB
TypeScript
import * as React from 'react';
|
|
const { useRef } = React;
|
|
import { Box, Paper, Typography, Avatar, useMediaQuery, useTheme, Theme } 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: React.ComponentProps<'h1'>) => (
|
|
<h1 style={{ fontSize: '1.4rem', fontWeight: 'bold', margin: '12px 0 8px 0' }} {...props} />
|
|
),
|
|
h2: (props: React.ComponentProps<'h2'>) => (
|
|
<h2 style={{ fontSize: '1.2rem', fontWeight: 'bold', margin: '10px 0 6px 0' }} {...props} />
|
|
),
|
|
h3: (props: React.ComponentProps<'h3'>) => (
|
|
<h3 style={{ fontSize: '1.1rem', fontWeight: 'bold', margin: '8px 0 4px 0' }} {...props} />
|
|
),
|
|
// 自定义列表样式
|
|
ul: (props: React.ComponentProps<'ul'>) => (
|
|
<ul style={{ paddingLeft: '20px', margin: '6px 0' }} {...props} />
|
|
),
|
|
ol: (props: React.ComponentProps<'ol'>) => (
|
|
<ol style={{ paddingLeft: '20px', margin: '6px 0' }} {...props} />
|
|
),
|
|
li: (props: React.ComponentProps<'li'>) => (
|
|
<li style={{ margin: '4px 0' }} {...props} />
|
|
),
|
|
// 自定义代码块样式
|
|
code: (props: React.ComponentProps<'code'>) => (
|
|
<code style={{ backgroundColor: '#f5f5f5', padding: '2px 4px', borderRadius: '3px', fontFamily: 'monospace' }} {...props} />
|
|
),
|
|
pre: (props: React.ComponentProps<'pre'>) => (
|
|
<pre style={{ backgroundColor: '#f5f5f5', padding: '12px', borderRadius: '6px', overflowX: 'auto', fontFamily: 'monospace', fontSize: '0.9rem' }} {...props} />
|
|
),
|
|
// 自定义引用样式
|
|
blockquote: (props: React.ComponentProps<'blockquote'>) => (
|
|
<blockquote style={{ borderLeft: '4px solid #ccc', margin: '10px 0', paddingLeft: '12px', color: '#666' }} {...props} />
|
|
),
|
|
// 自定义链接样式
|
|
a: (props: React.ComponentProps<'a'>) => (
|
|
<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; |