369 lines
13 KiB
TypeScript
369 lines
13 KiB
TypeScript
|
|
import React, { useState } from 'react';
|
|||
|
|
import {
|
|||
|
|
Box,
|
|||
|
|
Typography,
|
|||
|
|
Card,
|
|||
|
|
CardContent,
|
|||
|
|
Grid,
|
|||
|
|
Button,
|
|||
|
|
Table,
|
|||
|
|
TableBody,
|
|||
|
|
TableCell,
|
|||
|
|
TableContainer,
|
|||
|
|
TableHead,
|
|||
|
|
TableRow,
|
|||
|
|
Paper,
|
|||
|
|
Chip,
|
|||
|
|
LinearProgress,
|
|||
|
|
Select,
|
|||
|
|
MenuItem,
|
|||
|
|
FormControl,
|
|||
|
|
InputLabel,
|
|||
|
|
} from '@mui/material';
|
|||
|
|
import {
|
|||
|
|
TrendingUp as TrendingUpIcon,
|
|||
|
|
TrendingDown as TrendingDownIcon,
|
|||
|
|
Assessment as AssessmentIcon,
|
|||
|
|
Speed as SpeedIcon,
|
|||
|
|
Error as ErrorIcon,
|
|||
|
|
CheckCircle as CheckCircleIcon,
|
|||
|
|
} from '@mui/icons-material';
|
|||
|
|
import { styled } from '@mui/material/styles';
|
|||
|
|
|
|||
|
|
const PageContainer = styled(Box)(({ theme }) => ({
|
|||
|
|
padding: '1.5rem',
|
|||
|
|
backgroundColor: '#F8F9FA',
|
|||
|
|
minHeight: 'calc(100vh - 60px)',
|
|||
|
|
}));
|
|||
|
|
|
|||
|
|
const PageHeader = styled(Box)(({ theme }) => ({
|
|||
|
|
display: 'flex',
|
|||
|
|
justifyContent: 'space-between',
|
|||
|
|
alignItems: 'center',
|
|||
|
|
marginBottom: '1.5rem',
|
|||
|
|
}));
|
|||
|
|
|
|||
|
|
const MetricCard = styled(Card)(({ theme }) => ({
|
|||
|
|
height: '100%',
|
|||
|
|
border: '1px solid #E5E5E5',
|
|||
|
|
transition: 'all 0.2s ease-in-out',
|
|||
|
|
'&:hover': {
|
|||
|
|
boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
|
|||
|
|
},
|
|||
|
|
}));
|
|||
|
|
|
|||
|
|
const MetricValue = styled(Typography)(({ theme }) => ({
|
|||
|
|
fontSize: '2rem',
|
|||
|
|
fontWeight: 700,
|
|||
|
|
lineHeight: 1.2,
|
|||
|
|
}));
|
|||
|
|
|
|||
|
|
const TrendIndicator = styled(Box)<{ trend: 'up' | 'down' }>(({ trend, theme }) => ({
|
|||
|
|
display: 'flex',
|
|||
|
|
alignItems: 'center',
|
|||
|
|
gap: '0.25rem',
|
|||
|
|
color: trend === 'up' ? '#28A745' : '#DC3545',
|
|||
|
|
fontSize: '0.875rem',
|
|||
|
|
fontWeight: 500,
|
|||
|
|
}));
|
|||
|
|
|
|||
|
|
const StatusChip = styled(Chip)<{ status: string }>(({ status, theme }) => ({
|
|||
|
|
fontSize: '0.75rem',
|
|||
|
|
height: '24px',
|
|||
|
|
backgroundColor:
|
|||
|
|
status === 'success' ? '#E8F5E8' :
|
|||
|
|
status === 'warning' ? '#FFF3CD' :
|
|||
|
|
status === 'error' ? '#F8D7DA' : '#F8F9FA',
|
|||
|
|
color:
|
|||
|
|
status === 'success' ? '#155724' :
|
|||
|
|
status === 'warning' ? '#856404' :
|
|||
|
|
status === 'error' ? '#721C24' : '#666',
|
|||
|
|
}));
|
|||
|
|
|
|||
|
|
const mockMetrics = {
|
|||
|
|
totalQueries: { value: 12847, trend: 'up', change: '+12.5%' },
|
|||
|
|
avgResponseTime: { value: '2.3s', trend: 'down', change: '-8.2%' },
|
|||
|
|
successRate: { value: '98.7%', trend: 'up', change: '+0.3%' },
|
|||
|
|
activeUsers: { value: 1256, trend: 'up', change: '+5.7%' },
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const mockRecentQueries = [
|
|||
|
|
{
|
|||
|
|
id: 1,
|
|||
|
|
query: '如何配置RAG Pipeline的参数?',
|
|||
|
|
user: 'user@example.com',
|
|||
|
|
status: 'success',
|
|||
|
|
responseTime: '1.8s',
|
|||
|
|
timestamp: '2024-01-15 14:30:25',
|
|||
|
|
knowledgeBase: '产品文档库',
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 2,
|
|||
|
|
query: '客服系统的常见问题有哪些?',
|
|||
|
|
user: 'admin@company.com',
|
|||
|
|
status: 'success',
|
|||
|
|
responseTime: '2.1s',
|
|||
|
|
timestamp: '2024-01-15 14:28:15',
|
|||
|
|
knowledgeBase: '客服FAQ',
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 3,
|
|||
|
|
query: '法律合规要求的详细说明',
|
|||
|
|
user: 'legal@company.com',
|
|||
|
|
status: 'warning',
|
|||
|
|
responseTime: '4.2s',
|
|||
|
|
timestamp: '2024-01-15 14:25:10',
|
|||
|
|
knowledgeBase: '法律合规',
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 4,
|
|||
|
|
query: '员工培训流程和要求',
|
|||
|
|
user: 'hr@company.com',
|
|||
|
|
status: 'error',
|
|||
|
|
responseTime: 'N/A',
|
|||
|
|
timestamp: '2024-01-15 14:22:45',
|
|||
|
|
knowledgeBase: '培训资料',
|
|||
|
|
},
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
const Dashboard: React.FC = () => {
|
|||
|
|
const [timeRange, setTimeRange] = useState('24h');
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<PageContainer>
|
|||
|
|
<PageHeader>
|
|||
|
|
<Typography variant="h4" fontWeight={600} color="#333">
|
|||
|
|
运营监控
|
|||
|
|
</Typography>
|
|||
|
|
<FormControl size="small" sx={{ minWidth: 120 }}>
|
|||
|
|
<InputLabel>时间范围</InputLabel>
|
|||
|
|
<Select
|
|||
|
|
value={timeRange}
|
|||
|
|
onChange={(e) => setTimeRange(e.target.value)}
|
|||
|
|
label="时间范围"
|
|||
|
|
>
|
|||
|
|
<MenuItem value="1h">最近1小时</MenuItem>
|
|||
|
|
<MenuItem value="24h">最近24小时</MenuItem>
|
|||
|
|
<MenuItem value="7d">最近7天</MenuItem>
|
|||
|
|
<MenuItem value="30d">最近30天</MenuItem>
|
|||
|
|
</Select>
|
|||
|
|
</FormControl>
|
|||
|
|
</PageHeader>
|
|||
|
|
|
|||
|
|
{/* 关键指标卡片 */}
|
|||
|
|
<Grid container spacing={3} sx={{ mb: 3 }}>
|
|||
|
|
<Grid item xs={12} sm={6} md={3}>
|
|||
|
|
<MetricCard>
|
|||
|
|
<CardContent>
|
|||
|
|
<Box display="flex" alignItems="center" justifyContent="space-between">
|
|||
|
|
<Box>
|
|||
|
|
<Typography color="text.secondary" variant="body2">
|
|||
|
|
总查询数
|
|||
|
|
</Typography>
|
|||
|
|
<MetricValue color="primary">
|
|||
|
|
{mockMetrics.totalQueries.value.toLocaleString()}
|
|||
|
|
</MetricValue>
|
|||
|
|
<TrendIndicator trend={mockMetrics.totalQueries.trend}>
|
|||
|
|
{mockMetrics.totalQueries.trend === 'up' ?
|
|||
|
|
<TrendingUpIcon fontSize="small" /> :
|
|||
|
|
<TrendingDownIcon fontSize="small" />
|
|||
|
|
}
|
|||
|
|
{mockMetrics.totalQueries.change}
|
|||
|
|
</TrendIndicator>
|
|||
|
|
</Box>
|
|||
|
|
<AssessmentIcon color="primary" sx={{ fontSize: '3rem', opacity: 0.3 }} />
|
|||
|
|
</Box>
|
|||
|
|
</CardContent>
|
|||
|
|
</MetricCard>
|
|||
|
|
</Grid>
|
|||
|
|
|
|||
|
|
<Grid item xs={12} sm={6} md={3}>
|
|||
|
|
<MetricCard>
|
|||
|
|
<CardContent>
|
|||
|
|
<Box display="flex" alignItems="center" justifyContent="space-between">
|
|||
|
|
<Box>
|
|||
|
|
<Typography color="text.secondary" variant="body2">
|
|||
|
|
平均响应时间
|
|||
|
|
</Typography>
|
|||
|
|
<MetricValue color="warning.main">
|
|||
|
|
{mockMetrics.avgResponseTime.value}
|
|||
|
|
</MetricValue>
|
|||
|
|
<TrendIndicator trend={mockMetrics.avgResponseTime.trend}>
|
|||
|
|
{mockMetrics.avgResponseTime.trend === 'up' ?
|
|||
|
|
<TrendingUpIcon fontSize="small" /> :
|
|||
|
|
<TrendingDownIcon fontSize="small" />
|
|||
|
|
}
|
|||
|
|
{mockMetrics.avgResponseTime.change}
|
|||
|
|
</TrendIndicator>
|
|||
|
|
</Box>
|
|||
|
|
<SpeedIcon color="warning" sx={{ fontSize: '3rem', opacity: 0.3 }} />
|
|||
|
|
</Box>
|
|||
|
|
</CardContent>
|
|||
|
|
</MetricCard>
|
|||
|
|
</Grid>
|
|||
|
|
|
|||
|
|
<Grid item xs={12} sm={6} md={3}>
|
|||
|
|
<MetricCard>
|
|||
|
|
<CardContent>
|
|||
|
|
<Box display="flex" alignItems="center" justifyContent="space-between">
|
|||
|
|
<Box>
|
|||
|
|
<Typography color="text.secondary" variant="body2">
|
|||
|
|
成功率
|
|||
|
|
</Typography>
|
|||
|
|
<MetricValue color="success.main">
|
|||
|
|
{mockMetrics.successRate.value}
|
|||
|
|
</MetricValue>
|
|||
|
|
<TrendIndicator trend={mockMetrics.successRate.trend}>
|
|||
|
|
{mockMetrics.successRate.trend === 'up' ?
|
|||
|
|
<TrendingUpIcon fontSize="small" /> :
|
|||
|
|
<TrendingDownIcon fontSize="small" />
|
|||
|
|
}
|
|||
|
|
{mockMetrics.successRate.change}
|
|||
|
|
</TrendIndicator>
|
|||
|
|
</Box>
|
|||
|
|
<CheckCircleIcon color="success" sx={{ fontSize: '3rem', opacity: 0.3 }} />
|
|||
|
|
</Box>
|
|||
|
|
</CardContent>
|
|||
|
|
</MetricCard>
|
|||
|
|
</Grid>
|
|||
|
|
|
|||
|
|
<Grid item xs={12} sm={6} md={3}>
|
|||
|
|
<MetricCard>
|
|||
|
|
<CardContent>
|
|||
|
|
<Box display="flex" alignItems="center" justifyContent="space-between">
|
|||
|
|
<Box>
|
|||
|
|
<Typography color="text.secondary" variant="body2">
|
|||
|
|
活跃用户
|
|||
|
|
</Typography>
|
|||
|
|
<MetricValue color="info.main">
|
|||
|
|
{mockMetrics.activeUsers.value.toLocaleString()}
|
|||
|
|
</MetricValue>
|
|||
|
|
<TrendIndicator trend={mockMetrics.activeUsers.trend}>
|
|||
|
|
{mockMetrics.activeUsers.trend === 'up' ?
|
|||
|
|
<TrendingUpIcon fontSize="small" /> :
|
|||
|
|
<TrendingDownIcon fontSize="small" />
|
|||
|
|
}
|
|||
|
|
{mockMetrics.activeUsers.change}
|
|||
|
|
</TrendIndicator>
|
|||
|
|
</Box>
|
|||
|
|
<AssessmentIcon color="info" sx={{ fontSize: '3rem', opacity: 0.3 }} />
|
|||
|
|
</Box>
|
|||
|
|
</CardContent>
|
|||
|
|
</MetricCard>
|
|||
|
|
</Grid>
|
|||
|
|
</Grid>
|
|||
|
|
|
|||
|
|
{/* 系统状态 */}
|
|||
|
|
<Grid container spacing={3} sx={{ mb: 3 }}>
|
|||
|
|
<Grid item xs={12} md={6}>
|
|||
|
|
<Card sx={{ border: '1px solid #E5E5E5' }}>
|
|||
|
|
<CardContent>
|
|||
|
|
<Typography variant="h6" fontWeight={600} mb={2}>
|
|||
|
|
系统状态
|
|||
|
|
</Typography>
|
|||
|
|
<Box mb={2}>
|
|||
|
|
<Box display="flex" justifyContent="space-between" mb={1}>
|
|||
|
|
<Typography variant="body2">CPU 使用率</Typography>
|
|||
|
|
<Typography variant="body2" fontWeight={600}>45%</Typography>
|
|||
|
|
</Box>
|
|||
|
|
<LinearProgress variant="determinate" value={45} sx={{ height: 8, borderRadius: 4 }} />
|
|||
|
|
</Box>
|
|||
|
|
<Box mb={2}>
|
|||
|
|
<Box display="flex" justifyContent="space-between" mb={1}>
|
|||
|
|
<Typography variant="body2">内存使用率</Typography>
|
|||
|
|
<Typography variant="body2" fontWeight={600}>67%</Typography>
|
|||
|
|
</Box>
|
|||
|
|
<LinearProgress variant="determinate" value={67} color="warning" sx={{ height: 8, borderRadius: 4 }} />
|
|||
|
|
</Box>
|
|||
|
|
<Box>
|
|||
|
|
<Box display="flex" justifyContent="space-between" mb={1}>
|
|||
|
|
<Typography variant="body2">存储使用率</Typography>
|
|||
|
|
<Typography variant="body2" fontWeight={600}>23%</Typography>
|
|||
|
|
</Box>
|
|||
|
|
<LinearProgress variant="determinate" value={23} color="success" sx={{ height: 8, borderRadius: 4 }} />
|
|||
|
|
</Box>
|
|||
|
|
</CardContent>
|
|||
|
|
</Card>
|
|||
|
|
</Grid>
|
|||
|
|
|
|||
|
|
<Grid item xs={12} md={6}>
|
|||
|
|
<Card sx={{ border: '1px solid #E5E5E5' }}>
|
|||
|
|
<CardContent>
|
|||
|
|
<Typography variant="h6" fontWeight={600} mb={2}>
|
|||
|
|
知识库状态
|
|||
|
|
</Typography>
|
|||
|
|
<Box display="flex" flexDirection="column" gap={1}>
|
|||
|
|
<Box display="flex" justifyContent="space-between" alignItems="center">
|
|||
|
|
<Typography variant="body2">产品文档库</Typography>
|
|||
|
|
<StatusChip status="success" label="正常" size="small" />
|
|||
|
|
</Box>
|
|||
|
|
<Box display="flex" justifyContent="space-between" alignItems="center">
|
|||
|
|
<Typography variant="body2">客服FAQ</Typography>
|
|||
|
|
<StatusChip status="success" label="正常" size="small" />
|
|||
|
|
</Box>
|
|||
|
|
<Box display="flex" justifyContent="space-between" alignItems="center">
|
|||
|
|
<Typography variant="body2">法律合规</Typography>
|
|||
|
|
<StatusChip status="warning" label="同步中" size="small" />
|
|||
|
|
</Box>
|
|||
|
|
<Box display="flex" justifyContent="space-between" alignItems="center">
|
|||
|
|
<Typography variant="body2">培训资料</Typography>
|
|||
|
|
<StatusChip status="error" label="错误" size="small" />
|
|||
|
|
</Box>
|
|||
|
|
</Box>
|
|||
|
|
</CardContent>
|
|||
|
|
</Card>
|
|||
|
|
</Grid>
|
|||
|
|
</Grid>
|
|||
|
|
|
|||
|
|
{/* 最近查询记录 */}
|
|||
|
|
<Card sx={{ border: '1px solid #E5E5E5' }}>
|
|||
|
|
<CardContent>
|
|||
|
|
<Typography variant="h6" fontWeight={600} mb={2}>
|
|||
|
|
最近查询记录
|
|||
|
|
</Typography>
|
|||
|
|
<TableContainer>
|
|||
|
|
<Table>
|
|||
|
|
<TableHead>
|
|||
|
|
<TableRow>
|
|||
|
|
<TableCell>查询内容</TableCell>
|
|||
|
|
<TableCell>用户</TableCell>
|
|||
|
|
<TableCell>知识库</TableCell>
|
|||
|
|
<TableCell>状态</TableCell>
|
|||
|
|
<TableCell>响应时间</TableCell>
|
|||
|
|
<TableCell>时间</TableCell>
|
|||
|
|
</TableRow>
|
|||
|
|
</TableHead>
|
|||
|
|
<TableBody>
|
|||
|
|
{mockRecentQueries.map((query) => (
|
|||
|
|
<TableRow key={query.id} hover>
|
|||
|
|
<TableCell>
|
|||
|
|
<Typography variant="body2" sx={{ maxWidth: '300px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
|||
|
|
{query.query}
|
|||
|
|
</Typography>
|
|||
|
|
</TableCell>
|
|||
|
|
<TableCell>{query.user}</TableCell>
|
|||
|
|
<TableCell>{query.knowledgeBase}</TableCell>
|
|||
|
|
<TableCell>
|
|||
|
|
<StatusChip status={query.status} label={
|
|||
|
|
query.status === 'success' ? '成功' :
|
|||
|
|
query.status === 'warning' ? '警告' : '失败'
|
|||
|
|
} size="small" />
|
|||
|
|
</TableCell>
|
|||
|
|
<TableCell>{query.responseTime}</TableCell>
|
|||
|
|
<TableCell>
|
|||
|
|
<Typography variant="body2" color="text.secondary">
|
|||
|
|
{query.timestamp}
|
|||
|
|
</Typography>
|
|||
|
|
</TableCell>
|
|||
|
|
</TableRow>
|
|||
|
|
))}
|
|||
|
|
</TableBody>
|
|||
|
|
</Table>
|
|||
|
|
</TableContainer>
|
|||
|
|
</CardContent>
|
|||
|
|
</Card>
|
|||
|
|
</PageContainer>
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
export default Dashboard;
|