Files
agent/app/components/Sidebar.tsx
2025-10-16 21:24:18 +08:00

296 lines
8.6 KiB
TypeScript

import React from 'react';
import {
Box,
List,
ListItem,
ListItemText,
Avatar,
Typography,
Button,
Divider,
useTheme,
Paper
} from '@mui/material';
import { Add, Settings } from '@mui/icons-material';
interface Conversation {
id: string;
name: string;
lastMessage?: string;
timestamp?: Date;
isActive?: boolean;
avatar?: string;
unreadCount?: number;
}
export const Sidebar: React.FC = () => {
const theme = useTheme();
// 模拟对话历史数据
const conversations: Conversation[] = [
{
id: '1',
name: '开始',
lastMessage: '守正出奇,让你的内容脱颖而出',
timestamp: new Date(),
isActive: true,
avatar: '🤖',
unreadCount: 0
},
{
id: '2',
name: '营销文案',
lastMessage: '爆款标题的3个关键要素...',
timestamp: new Date(Date.now() - 3600000),
isActive: false,
avatar: '📝',
unreadCount: 1
},
{
id: '3',
name: '社交媒体策略',
lastMessage: '如何提高社交媒体互动率?',
timestamp: new Date(Date.now() - 86400000),
isActive: false,
avatar: '📱',
unreadCount: 0
}
];
// 处理新建对话
const handleNewConversation = () => {
console.log('创建新对话');
};
// 处理选择对话
const handleSelectConversation = (conversationId: string) => {
console.log('选择对话:', conversationId);
};
// 格式化时间
const formatTime = (date: Date) => {
const now = new Date();
const diff = now.getTime() - date.getTime();
if (diff < 86400000) { // 24小时内
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
} else if (diff < 172800000) { // 48小时内
return '昨天';
} else {
return date.toLocaleDateString('zh-CN', { month: '2-digit', day: '2-digit' });
}
};
return (
<Paper
elevation={2}
sx={{
width: { xs: '100%', sm: 300 },
height: '100vh',
display: 'flex',
flexDirection: 'column',
backgroundColor: theme.palette.background.paper,
borderRight: `1px solid ${theme.palette.divider}`,
position: 'relative',
overflow: 'hidden',
}}
>
{/* 用户信息区域 */}
<Box sx={{ p: 2, borderBottom: `1px solid ${theme.palette.divider}` }}>
<Box sx={{ mb: 2 }}>
<Typography
variant="h6"
component="h1"
sx={{
fontWeight: 600,
color: theme.palette.primary.main,
mb: 0.5,
fontSize: '1.1rem',
}}
>
</Typography>
<Typography
variant="body2"
color="text.secondary"
sx={{ fontSize: '0.85rem' }}
>
</Typography>
</Box>
{/* 新建对话按钮 */}
<Button
fullWidth
variant="contained"
startIcon={<Add fontSize="small" />}
onClick={handleNewConversation}
sx={{
backgroundColor: theme.palette.primary.main,
'&:hover': {
backgroundColor: theme.palette.primary.dark,
},
py: 1.2,
borderRadius: 2,
textTransform: 'none',
fontWeight: 500,
fontSize: '0.9rem',
boxShadow: theme.shadows[1],
}}
>
</Button>
</Box>
{/* 对话历史列表 */}
<List
component="nav"
sx={{
flex: 1,
overflow: 'auto',
'&::-webkit-scrollbar': {
width: 6,
},
'&::-webkit-scrollbar-track': {
backgroundColor: theme.palette.background.default,
},
'&::-webkit-scrollbar-thumb': {
backgroundColor: theme.palette.divider,
borderRadius: 3,
},
}}
>
<Typography
variant="caption"
sx={{
px: 2,
pt: 2,
pb: 1,
display: 'block',
color: theme.palette.text.secondary,
fontWeight: 500,
textTransform: 'uppercase',
letterSpacing: 0.5,
}}
>
</Typography>
{conversations.map((conversation) => (
<React.Fragment key={conversation.id}>
<ListItem
onClick={() => handleSelectConversation(conversation.id)}
sx={{
borderRadius: 1,
mx: 1,
'&:hover': {
backgroundColor: theme.palette.action.hover,
},
backgroundColor: conversation.isActive ? theme.palette.action.selected : 'transparent',
transition: 'all 0.2s ease',
paddingX: 2,
paddingY: 1.5,
}}
>
<Avatar
sx={{
width: 44,
height: 44,
bgcolor: conversation.isActive ? theme.palette.primary.main : theme.palette.background.default,
color: conversation.isActive ? 'white' : theme.palette.text.primary,
fontSize: '1.2rem',
boxShadow: conversation.isActive ? theme.shadows[2] : 'none',
}}
>
{conversation.avatar}
</Avatar>
<ListItemText
primary={
<Box display="flex" justifyContent="space-between" alignItems="center" gap={1}>
<Typography
variant="subtitle1"
sx={{
fontWeight: conversation.isActive ? 600 : 500,
fontSize: '0.9rem',
flex: 1,
}}
>
{conversation.name}
</Typography>
<Typography
variant="caption"
color="text.secondary"
sx={{ fontSize: '0.75rem' }}
>
{conversation.timestamp && formatTime(conversation.timestamp)}
</Typography>
</Box>
}
secondary={
<Box display="flex" alignItems="center" gap={1}>
<Typography
variant="body2"
color="text.secondary"
sx={{
flex: 1,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
fontSize: '0.8rem',
}}
>
{conversation.lastMessage}
</Typography>
{conversation.unreadCount && conversation.unreadCount > 0 && (
<Box
sx={{
minWidth: '18px',
height: '18px',
borderRadius: '9px',
backgroundColor: theme.palette.primary.main,
color: 'white',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
px: 0.5,
}}
>
<Typography variant="caption" sx={{ fontSize: '0.7rem', fontWeight: 600 }}>
{conversation.unreadCount}
</Typography>
</Box>
)}
</Box>
}
primaryTypographyProps={{ component: 'div' }}
secondaryTypographyProps={{ component: 'div' }}
/>
</ListItem>
<Divider light />
</React.Fragment>
))}
</List>
{/* 底部设置区域 */}
<Box sx={{ p: 2, borderTop: `1px solid ${theme.palette.divider}` }}>
<Button
fullWidth
startIcon={<Settings fontSize="small" />}
sx={{
justifyContent: 'flex-start',
color: theme.palette.text.primary,
'&:hover': {
backgroundColor: theme.palette.action.hover,
},
textTransform: 'none',
fontWeight: 500,
fontSize: '0.9rem',
}}
>
</Button>
</Box>
</Paper>
);
};