feat: implement RAG dashboard with MUI and react-router

Add new RAG dashboard feature including:
- Main application layout with header and sidebar
- Multiple pages (Home, KnowledgeBase, PipelineConfig, Dashboard, MCP)
- Theme configuration and styling
- Routing setup with protected routes
- Login page and authentication flow
- Various UI components and mock data for dashboard views
This commit is contained in:
2025-10-09 17:23:15 +08:00
parent 446b422a12
commit 5f93573e57
15 changed files with 3521 additions and 31 deletions

View File

@@ -0,0 +1,540 @@
import React, { useState } from 'react';
import {
Box,
Typography,
Card,
CardContent,
Grid,
Button,
Chip,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
LinearProgress,
IconButton,
Menu,
MenuItem,
Tabs,
Tab,
TextField,
InputAdornment,
} from '@mui/material';
import {
Memory as MemoryIcon,
Storage as StorageIcon,
Speed as SpeedIcon,
CloudDownload as CloudDownloadIcon,
Settings as SettingsIcon,
MoreVert as MoreVertIcon,
Search as SearchIcon,
Add as AddIcon,
} 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 ResourceCard = 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 StatusChip = styled(Chip)<{ status: string }>(({ status, theme }) => ({
fontSize: '0.75rem',
height: '24px',
backgroundColor:
status === 'active' ? '#E8F5E8' :
status === 'loading' ? '#FFF3CD' :
status === 'error' ? '#F8D7DA' : '#F8F9FA',
color:
status === 'active' ? '#155724' :
status === 'loading' ? '#856404' :
status === 'error' ? '#721C24' : '#666',
}));
const UsageBar = styled(Box)(({ theme }) => ({
display: 'flex',
alignItems: 'center',
gap: '0.5rem',
marginTop: '0.5rem',
}));
const mockModels = [
{
id: 1,
name: 'GPT-4',
type: 'LLM',
provider: 'OpenAI',
status: 'active',
usage: 75,
cost: '$234.56',
requests: '12.3K',
latency: '1.2s',
},
{
id: 2,
name: 'text-embedding-ada-002',
type: 'Embedding',
provider: 'OpenAI',
status: 'active',
usage: 45,
cost: '$89.12',
requests: '45.6K',
latency: '0.3s',
},
{
id: 3,
name: 'Claude-3-Sonnet',
type: 'LLM',
provider: 'Anthropic',
status: 'loading',
usage: 0,
cost: '$0.00',
requests: '0',
latency: 'N/A',
},
{
id: 4,
name: 'BGE-Large-ZH',
type: 'Embedding',
provider: 'BAAI',
status: 'error',
usage: 0,
cost: '$0.00',
requests: '0',
latency: 'N/A',
},
];
const mockResources = [
{
id: 1,
name: 'GPU-Server-01',
type: 'GPU',
specs: 'NVIDIA A100 80GB',
usage: 85,
status: 'active',
location: '北京机房',
},
{
id: 2,
name: 'CPU-Cluster-01',
type: 'CPU',
specs: '64 Core Intel Xeon',
usage: 45,
status: 'active',
location: '上海机房',
},
{
id: 3,
name: 'Storage-Pool-01',
type: 'Storage',
specs: '10TB NVMe SSD',
usage: 67,
status: 'active',
location: '深圳机房',
},
];
const ModelsResources: React.FC = () => {
const [tabValue, setTabValue] = useState(0);
const [searchTerm, setSearchTerm] = useState('');
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const [selectedItem, setSelectedItem] = useState<number | null>(null);
const handleTabChange = (event: React.SyntheticEvent, newValue: number) => {
setTabValue(newValue);
};
const handleMenuClick = (event: React.MouseEvent<HTMLElement>, itemId: number) => {
setAnchorEl(event.currentTarget);
setSelectedItem(itemId);
};
const handleMenuClose = () => {
setAnchorEl(null);
setSelectedItem(null);
};
const filteredModels = mockModels.filter(model =>
model.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
model.provider.toLowerCase().includes(searchTerm.toLowerCase())
);
const filteredResources = mockResources.filter(resource =>
resource.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
resource.type.toLowerCase().includes(searchTerm.toLowerCase())
);
return (
<PageContainer>
<PageHeader>
<Typography variant="h4" fontWeight={600} color="#333">
</Typography>
<Button
variant="contained"
startIcon={<AddIcon />}
sx={{ borderRadius: '6px' }}
>
</Button>
</PageHeader>
<Box sx={{ borderBottom: 1, borderColor: 'divider', mb: 3 }}>
<Tabs value={tabValue} onChange={handleTabChange}>
<Tab label="AI 模型" />
<Tab label="计算资源" />
</Tabs>
</Box>
<Box display="flex" gap={2} mb={3}>
<TextField
placeholder={tabValue === 0 ? "搜索模型..." : "搜索资源..."}
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchIcon color="action" />
</InputAdornment>
),
}}
sx={{ width: '400px' }}
/>
</Box>
{tabValue === 0 && (
<>
{/* 模型概览卡片 */}
<Grid container spacing={3} sx={{ mb: 3 }}>
<Grid item xs={12} sm={6} md={3}>
<ResourceCard>
<CardContent>
<Box display="flex" alignItems="center" justifyContent="space-between">
<Box>
<Typography color="text.secondary" variant="body2">
</Typography>
<Typography variant="h4" fontWeight={600} color="primary">
{mockModels.filter(m => m.status === 'active').length}
</Typography>
</Box>
<MemoryIcon color="primary" sx={{ fontSize: '3rem', opacity: 0.3 }} />
</Box>
</CardContent>
</ResourceCard>
</Grid>
<Grid item xs={12} sm={6} md={3}>
<ResourceCard>
<CardContent>
<Box display="flex" alignItems="center" justifyContent="space-between">
<Box>
<Typography color="text.secondary" variant="body2">
</Typography>
<Typography variant="h4" fontWeight={600} color="success.main">
57.9K
</Typography>
</Box>
<SpeedIcon color="success" sx={{ fontSize: '3rem', opacity: 0.3 }} />
</Box>
</CardContent>
</ResourceCard>
</Grid>
<Grid item xs={12} sm={6} md={3}>
<ResourceCard>
<CardContent>
<Box display="flex" alignItems="center" justifyContent="space-between">
<Box>
<Typography color="text.secondary" variant="body2">
</Typography>
<Typography variant="h4" fontWeight={600} color="warning.main">
$323.68
</Typography>
</Box>
<CloudDownloadIcon color="warning" sx={{ fontSize: '3rem', opacity: 0.3 }} />
</Box>
</CardContent>
</ResourceCard>
</Grid>
<Grid item xs={12} sm={6} md={3}>
<ResourceCard>
<CardContent>
<Box display="flex" alignItems="center" justifyContent="space-between">
<Box>
<Typography color="text.secondary" variant="body2">
</Typography>
<Typography variant="h4" fontWeight={600} color="info.main">
0.8s
</Typography>
</Box>
<SpeedIcon color="info" sx={{ fontSize: '3rem', opacity: 0.3 }} />
</Box>
</CardContent>
</ResourceCard>
</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>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
</TableRow>
</TableHead>
<TableBody>
{filteredModels.map((model) => (
<TableRow key={model.id} hover>
<TableCell>
<Typography variant="body2" fontWeight={600}>
{model.name}
</Typography>
</TableCell>
<TableCell>
<Chip
label={model.type}
size="small"
variant="outlined"
color={model.type === 'LLM' ? 'primary' : 'secondary'}
/>
</TableCell>
<TableCell>{model.provider}</TableCell>
<TableCell>
<StatusChip
status={model.status}
label={
model.status === 'active' ? '活跃' :
model.status === 'loading' ? '加载中' : '错误'
}
size="small"
/>
</TableCell>
<TableCell>
<UsageBar>
<LinearProgress
variant="determinate"
value={model.usage}
sx={{ width: '60px', height: '6px', borderRadius: '3px' }}
color={model.usage > 80 ? 'error' : model.usage > 60 ? 'warning' : 'primary'}
/>
<Typography variant="body2" color="text.secondary">
{model.usage}%
</Typography>
</UsageBar>
</TableCell>
<TableCell>{model.requests}</TableCell>
<TableCell>{model.cost}</TableCell>
<TableCell>{model.latency}</TableCell>
<TableCell>
<IconButton
size="small"
onClick={(e) => handleMenuClick(e, model.id)}
>
<MoreVertIcon />
</IconButton>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
</>
)}
{tabValue === 1 && (
<>
{/* 资源概览卡片 */}
<Grid container spacing={3} sx={{ mb: 3 }}>
<Grid item xs={12} sm={6} md={4}>
<ResourceCard>
<CardContent>
<Box display="flex" alignItems="center" justifyContent="space-between">
<Box>
<Typography color="text.secondary" variant="body2">
GPU 使
</Typography>
<Typography variant="h4" fontWeight={600} color="error.main">
85%
</Typography>
</Box>
<MemoryIcon color="error" sx={{ fontSize: '3rem', opacity: 0.3 }} />
</Box>
</CardContent>
</ResourceCard>
</Grid>
<Grid item xs={12} sm={6} md={4}>
<ResourceCard>
<CardContent>
<Box display="flex" alignItems="center" justifyContent="space-between">
<Box>
<Typography color="text.secondary" variant="body2">
CPU 使
</Typography>
<Typography variant="h4" fontWeight={600} color="warning.main">
45%
</Typography>
</Box>
<SpeedIcon color="warning" sx={{ fontSize: '3rem', opacity: 0.3 }} />
</Box>
</CardContent>
</ResourceCard>
</Grid>
<Grid item xs={12} sm={6} md={4}>
<ResourceCard>
<CardContent>
<Box display="flex" alignItems="center" justifyContent="space-between">
<Box>
<Typography color="text.secondary" variant="body2">
使
</Typography>
<Typography variant="h4" fontWeight={600} color="success.main">
67%
</Typography>
</Box>
<StorageIcon color="success" sx={{ fontSize: '3rem', opacity: 0.3 }} />
</Box>
</CardContent>
</ResourceCard>
</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>
<TableCell></TableCell>
</TableRow>
</TableHead>
<TableBody>
{filteredResources.map((resource) => (
<TableRow key={resource.id} hover>
<TableCell>
<Typography variant="body2" fontWeight={600}>
{resource.name}
</Typography>
</TableCell>
<TableCell>
<Chip
label={resource.type}
size="small"
variant="outlined"
color={
resource.type === 'GPU' ? 'error' :
resource.type === 'CPU' ? 'warning' : 'success'
}
/>
</TableCell>
<TableCell>{resource.specs}</TableCell>
<TableCell>
<UsageBar>
<LinearProgress
variant="determinate"
value={resource.usage}
sx={{ width: '80px', height: '6px', borderRadius: '3px' }}
color={resource.usage > 80 ? 'error' : resource.usage > 60 ? 'warning' : 'primary'}
/>
<Typography variant="body2" color="text.secondary">
{resource.usage}%
</Typography>
</UsageBar>
</TableCell>
<TableCell>
<StatusChip
status={resource.status}
label={resource.status === 'active' ? '活跃' : '离线'}
size="small"
/>
</TableCell>
<TableCell>{resource.location}</TableCell>
<TableCell>
<IconButton
size="small"
onClick={(e) => handleMenuClick(e, resource.id)}
>
<MoreVertIcon />
</IconButton>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
</>
)}
<Menu
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={handleMenuClose}
>
<MenuItem onClick={handleMenuClose}>
<SettingsIcon sx={{ mr: 1 }} fontSize="small" />
</MenuItem>
<MenuItem onClick={handleMenuClose}></MenuItem>
<MenuItem onClick={handleMenuClose}></MenuItem>
<MenuItem onClick={handleMenuClose} sx={{ color: 'error.main' }}>
</MenuItem>
</Menu>
</PageContainer>
);
};
export default ModelsResources;