Files
TERES_web_frontend/src/pages/Dashboard.tsx

495 lines
16 KiB
TypeScript
Raw Normal View History

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';
import KnowledgeGridView from '@/components/KnowledgeGridView';
import UserDataDebug from '@/components/UserDataDebug';
import { useNavigate } from 'react-router-dom';
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');
const navigate = useNavigate();
// 模拟知识库数据
const mockKnowledgeBases = [
{
id: '1',
name: '产品文档库',
description: '包含所有产品相关的技术文档和用户手册',
status: '1',
doc_num: 156,
chunk_num: 1240,
size: 2400000000, // 2.4GB
update_date: '2024-01-15',
created_by: 'admin',
nickname: '管理员',
create_date: '2024-01-01',
create_time: 1704067200,
tenant_id: 'tenant1',
token_num: 50000,
parser_config: {},
parser_id: 'parser1',
pipeline_id: 'pipeline1',
pipeline_name: 'Default Pipeline',
pipeline_avatar: '',
permission: 'read',
similarity_threshold: 0.8,
update_time: 1705305600,
vector_similarity_weight: 0.7,
embd_id: 'embd1',
operator_permission: 1,
},
{
id: '2',
name: '客服FAQ',
description: '常见问题解答和客服对话记录',
status: '1',
doc_num: 89,
chunk_num: 670,
size: 1100000000, // 1.1GB
update_date: '2024-01-14',
created_by: 'support',
nickname: '客服团队',
create_date: '2024-01-02',
create_time: 1704153600,
tenant_id: 'tenant1',
token_num: 30000,
parser_config: {},
parser_id: 'parser1',
pipeline_id: 'pipeline1',
pipeline_name: 'Default Pipeline',
pipeline_avatar: '',
permission: 'read',
similarity_threshold: 0.8,
update_time: 1705219200,
vector_similarity_weight: 0.7,
embd_id: 'embd1',
operator_permission: 1,
},
{
id: '3',
name: '法律合规',
description: '法律条文、合规要求和相关政策文档',
status: '0',
doc_num: 234,
chunk_num: 1890,
size: 3700000000, // 3.7GB
update_date: '2024-01-13',
created_by: 'legal',
nickname: '法务部',
create_date: '2024-01-03',
create_time: 1704240000,
tenant_id: 'tenant1',
token_num: 75000,
parser_config: {},
parser_id: 'parser1',
pipeline_id: 'pipeline1',
pipeline_name: 'Default Pipeline',
pipeline_avatar: '',
permission: 'read',
similarity_threshold: 0.8,
update_time: 1705132800,
vector_similarity_weight: 0.7,
embd_id: 'embd1',
operator_permission: 1,
},
];
const handleSeeAllKnowledgeBases = () => {
navigate('/knowledge');
};
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>
{/* 知识库概览 */}
<Card sx={{ border: '1px solid #E5E5E5', mb: 3 }}>
<CardContent>
<Box display="flex" justifyContent="space-between" alignItems="center" mb={2}>
<Typography variant="h6" fontWeight={600}>
</Typography>
<Button
variant="outlined"
size="small"
onClick={handleSeeAllKnowledgeBases}
>
</Button>
</Box>
<KnowledgeGridView
knowledgeBases={mockKnowledgeBases}
maxItems={3}
showSeeAll={true}
onSeeAll={handleSeeAllKnowledgeBases}
/>
</CardContent>
</Card>
{/* 系统状态 */}
<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>
{/* 用户数据调试组件 - 仅在开发环境显示 */}
{process.env.NODE_ENV === 'development' && (
<Card sx={{ border: '1px solid #E5E5E5', mt: 3 }}>
<CardContent>
<UserDataDebug />
</CardContent>
</Card>
)}
</PageContainer>
);
};
export default Dashboard;