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:
540
src/pages/ModelsResources.tsx
Normal file
540
src/pages/ModelsResources.tsx
Normal 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;
|
||||
Reference in New Issue
Block a user