349 lines
11 KiB
TypeScript
349 lines
11 KiB
TypeScript
import React, { useState } from 'react';
|
||
import {
|
||
Box,
|
||
Typography,
|
||
Card,
|
||
CardContent,
|
||
Grid,
|
||
Button,
|
||
Switch,
|
||
FormControlLabel,
|
||
Slider,
|
||
TextField,
|
||
Select,
|
||
MenuItem,
|
||
FormControl,
|
||
InputLabel,
|
||
Chip,
|
||
Divider,
|
||
Alert,
|
||
LinearProgress,
|
||
} from '@mui/material';
|
||
import {
|
||
Settings as SettingsIcon,
|
||
PlayArrow as PlayIcon,
|
||
Stop as StopIcon,
|
||
Refresh as RefreshIcon,
|
||
Save as SaveIcon,
|
||
} 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 ConfigCard = styled(Card)(({ theme }) => ({
|
||
marginBottom: '1.5rem',
|
||
border: '1px solid #E5E5E5',
|
||
}));
|
||
|
||
const ConfigSection = styled(Box)(({ theme }) => ({
|
||
marginBottom: '1.5rem',
|
||
'&:last-child': {
|
||
marginBottom: 0,
|
||
},
|
||
}));
|
||
|
||
const StatusIndicator = styled(Box)<{ status: 'running' | 'stopped' | 'error' }>(({ status, theme }) => ({
|
||
display: 'inline-flex',
|
||
alignItems: 'center',
|
||
gap: '0.5rem',
|
||
padding: '0.25rem 0.75rem',
|
||
borderRadius: '12px',
|
||
fontSize: '0.875rem',
|
||
fontWeight: 500,
|
||
backgroundColor:
|
||
status === 'running' ? '#E8F5E8' :
|
||
status === 'stopped' ? '#F8F9FA' : '#F8D7DA',
|
||
color:
|
||
status === 'running' ? '#155724' :
|
||
status === 'stopped' ? '#666' : '#721C24',
|
||
'&::before': {
|
||
content: '""',
|
||
width: '8px',
|
||
height: '8px',
|
||
borderRadius: '50%',
|
||
backgroundColor:
|
||
status === 'running' ? '#28A745' :
|
||
status === 'stopped' ? '#6C757D' : '#DC3545',
|
||
},
|
||
}));
|
||
|
||
const PipelineConfig: React.FC = () => {
|
||
const [pipelineStatus, setPipelineStatus] = useState<'running' | 'stopped' | 'error'>('stopped');
|
||
const [config, setConfig] = useState({
|
||
enabled: false,
|
||
chunkSize: 512,
|
||
chunkOverlap: 50,
|
||
embeddingModel: 'text-embedding-ada-002',
|
||
retrievalTopK: 5,
|
||
temperature: 0.7,
|
||
maxTokens: 2048,
|
||
systemPrompt: '你是一个专业的AI助手,请基于提供的知识库内容回答用户问题。',
|
||
});
|
||
|
||
const handleConfigChange = (key: string, value: any) => {
|
||
setConfig(prev => ({ ...prev, [key]: value }));
|
||
};
|
||
|
||
const handleStartPipeline = () => {
|
||
setPipelineStatus('running');
|
||
};
|
||
|
||
const handleStopPipeline = () => {
|
||
setPipelineStatus('stopped');
|
||
};
|
||
|
||
const handleSaveConfig = () => {
|
||
// 保存配置逻辑
|
||
console.log('保存配置:', config);
|
||
};
|
||
|
||
return (
|
||
<PageContainer>
|
||
<PageHeader>
|
||
<Box>
|
||
<Typography variant="h4" fontWeight={600} color="#333">
|
||
RAG Pipeline 配置
|
||
</Typography>
|
||
<Box display="flex" alignItems="center" gap={2} mt={1}>
|
||
<StatusIndicator status={pipelineStatus}>
|
||
{pipelineStatus === 'running' ? '运行中' :
|
||
pipelineStatus === 'stopped' ? '已停止' : '错误'}
|
||
</StatusIndicator>
|
||
{pipelineStatus === 'running' && (
|
||
<LinearProgress sx={{ width: '200px', height: '6px', borderRadius: '3px' }} />
|
||
)}
|
||
</Box>
|
||
</Box>
|
||
<Box display="flex" gap={1}>
|
||
<Button
|
||
variant="outlined"
|
||
startIcon={<SaveIcon />}
|
||
onClick={handleSaveConfig}
|
||
>
|
||
保存配置
|
||
</Button>
|
||
{pipelineStatus === 'running' ? (
|
||
<Button
|
||
variant="contained"
|
||
color="error"
|
||
startIcon={<StopIcon />}
|
||
onClick={handleStopPipeline}
|
||
>
|
||
停止
|
||
</Button>
|
||
) : (
|
||
<Button
|
||
variant="contained"
|
||
startIcon={<PlayIcon />}
|
||
onClick={handleStartPipeline}
|
||
>
|
||
启动
|
||
</Button>
|
||
)}
|
||
</Box>
|
||
</PageHeader>
|
||
|
||
<Grid container spacing={3}>
|
||
<Grid size={{xs:12,md:6}}>
|
||
<ConfigCard>
|
||
<CardContent>
|
||
<Typography variant="h6" fontWeight={600} mb={2}>
|
||
<SettingsIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
|
||
基础配置
|
||
</Typography>
|
||
|
||
<ConfigSection>
|
||
<FormControlLabel
|
||
control={
|
||
<Switch
|
||
checked={config.enabled}
|
||
onChange={(e) => handleConfigChange('enabled', e.target.checked)}
|
||
/>
|
||
}
|
||
label="启用 RAG Pipeline"
|
||
/>
|
||
</ConfigSection>
|
||
|
||
<ConfigSection>
|
||
<Typography gutterBottom>文档分块大小</Typography>
|
||
<Slider
|
||
value={config.chunkSize}
|
||
onChange={(_, value) => handleConfigChange('chunkSize', value)}
|
||
min={128}
|
||
max={2048}
|
||
step={64}
|
||
marks={[
|
||
{ value: 128, label: '128' },
|
||
{ value: 512, label: '512' },
|
||
{ value: 1024, label: '1024' },
|
||
{ value: 2048, label: '2048' },
|
||
]}
|
||
valueLabelDisplay="on"
|
||
/>
|
||
</ConfigSection>
|
||
|
||
<ConfigSection>
|
||
<Typography gutterBottom>分块重叠 (%)</Typography>
|
||
<Slider
|
||
value={config.chunkOverlap}
|
||
onChange={(_, value) => handleConfigChange('chunkOverlap', value)}
|
||
min={0}
|
||
max={50}
|
||
step={5}
|
||
valueLabelDisplay="on"
|
||
/>
|
||
</ConfigSection>
|
||
|
||
<ConfigSection>
|
||
<FormControl fullWidth>
|
||
<InputLabel>嵌入模型</InputLabel>
|
||
<Select
|
||
value={config.embeddingModel}
|
||
onChange={(e) => handleConfigChange('embeddingModel', e.target.value)}
|
||
label="嵌入模型"
|
||
>
|
||
<MenuItem value="text-embedding-ada-002">text-embedding-ada-002</MenuItem>
|
||
<MenuItem value="text-embedding-3-small">text-embedding-3-small</MenuItem>
|
||
<MenuItem value="text-embedding-3-large">text-embedding-3-large</MenuItem>
|
||
</Select>
|
||
</FormControl>
|
||
</ConfigSection>
|
||
</CardContent>
|
||
</ConfigCard>
|
||
</Grid>
|
||
|
||
<Grid size={{xs:12,md:6}}>
|
||
<ConfigCard>
|
||
<CardContent>
|
||
<Typography variant="h6" fontWeight={600} mb={2}>
|
||
检索与生成配置
|
||
</Typography>
|
||
|
||
<ConfigSection>
|
||
<TextField
|
||
fullWidth
|
||
label="检索文档数量 (Top-K)"
|
||
type="number"
|
||
value={config.retrievalTopK}
|
||
onChange={(e) => handleConfigChange('retrievalTopK', parseInt(e.target.value))}
|
||
inputProps={{ min: 1, max: 20 }}
|
||
/>
|
||
</ConfigSection>
|
||
|
||
<ConfigSection>
|
||
<Typography gutterBottom>生成温度</Typography>
|
||
<Slider
|
||
value={config.temperature}
|
||
onChange={(_, value) => handleConfigChange('temperature', value)}
|
||
min={0}
|
||
max={1}
|
||
step={0.1}
|
||
marks={[
|
||
{ value: 0, label: '0' },
|
||
{ value: 0.5, label: '0.5' },
|
||
{ value: 1, label: '1' },
|
||
]}
|
||
valueLabelDisplay="on"
|
||
/>
|
||
</ConfigSection>
|
||
|
||
<ConfigSection>
|
||
<TextField
|
||
fullWidth
|
||
label="最大输出Token数"
|
||
type="number"
|
||
value={config.maxTokens}
|
||
onChange={(e) => handleConfigChange('maxTokens', parseInt(e.target.value))}
|
||
inputProps={{ min: 256, max: 4096 }}
|
||
/>
|
||
</ConfigSection>
|
||
|
||
<ConfigSection>
|
||
<TextField
|
||
fullWidth
|
||
label="系统提示词"
|
||
multiline
|
||
rows={4}
|
||
value={config.systemPrompt}
|
||
onChange={(e) => handleConfigChange('systemPrompt', e.target.value)}
|
||
/>
|
||
</ConfigSection>
|
||
</CardContent>
|
||
</ConfigCard>
|
||
</Grid>
|
||
|
||
<Grid size={{xs:12,md:6}}>
|
||
<ConfigCard>
|
||
<CardContent>
|
||
<Typography variant="h6" fontWeight={600} mb={2}>
|
||
Pipeline 状态监控
|
||
</Typography>
|
||
|
||
<Grid container spacing={2}>
|
||
<Grid size={{xs:12,sm:6,md:3}}>
|
||
<Box textAlign="center" p={2} bgcolor="#F8F9FA" borderRadius="6px">
|
||
<Typography variant="h4" color="primary" fontWeight={600}>
|
||
1,234
|
||
</Typography>
|
||
<Typography variant="body2" color="text.secondary">
|
||
已处理文档
|
||
</Typography>
|
||
</Box>
|
||
</Grid>
|
||
<Grid size={{xs:12,sm:6,md:3}}>
|
||
<Box textAlign="center" p={2} bgcolor="#F8F9FA" borderRadius="6px">
|
||
<Typography variant="h4" color="success.main" fontWeight={600}>
|
||
98.5%
|
||
</Typography>
|
||
<Typography variant="body2" color="text.secondary">
|
||
成功率
|
||
</Typography>
|
||
</Box>
|
||
</Grid>
|
||
<Grid size={{xs:12,sm:6,md:3}}>
|
||
<Box textAlign="center" p={2} bgcolor="#F8F9FA" borderRadius="6px">
|
||
<Typography variant="h4" color="warning.main" fontWeight={600}>
|
||
2.3s
|
||
</Typography>
|
||
<Typography variant="body2" color="text.secondary">
|
||
平均响应时间
|
||
</Typography>
|
||
</Box>
|
||
</Grid>
|
||
<Grid size={{xs:12,sm:6,md:3}}>
|
||
<Box textAlign="center" p={2} bgcolor="#F8F9FA" borderRadius="6px">
|
||
<Typography variant="h4" color="info.main" fontWeight={600}>
|
||
156
|
||
</Typography>
|
||
<Typography variant="body2" color="text.secondary">
|
||
今日查询数
|
||
</Typography>
|
||
</Box>
|
||
</Grid>
|
||
</Grid>
|
||
|
||
{pipelineStatus === 'error' && (
|
||
<Alert severity="error" sx={{ mt: 2 }}>
|
||
Pipeline 运行出现错误,请检查配置和日志。
|
||
</Alert>
|
||
)}
|
||
</CardContent>
|
||
</ConfigCard>
|
||
</Grid>
|
||
</Grid>
|
||
</PageContainer>
|
||
);
|
||
};
|
||
|
||
export default PipelineConfig; |