Files
TERES_web_frontend/src/pages/Dashboard.tsx

486 lines
16 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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/knowledge/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: string }>(({ 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 size={{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 size={{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 size={{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 size={{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 as any}
maxItems={3}
showSeeAll={true}
onSeeAll={handleSeeAllKnowledgeBases}
/>
</CardContent>
</Card>
{/* 系统状态 */}
<Grid container spacing={3} sx={{ mb: 3 }}>
<Grid size={{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 size={{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;