first-update
This commit is contained in:
@@ -0,0 +1,327 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useMemo } from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Button,
|
||||
Box,
|
||||
Typography,
|
||||
TextField,
|
||||
Card,
|
||||
CardActionArea,
|
||||
Chip,
|
||||
IconButton,
|
||||
Tooltip,
|
||||
InputAdornment,
|
||||
CircularProgress,
|
||||
DialogTitle,
|
||||
DialogContentText
|
||||
} from '@mui/material';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
import StorageIcon from '@mui/icons-material/Storage';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { alpha, useTheme } from '@mui/material/styles';
|
||||
import { StyledDialogTitle } from './ImportDialog.styles';
|
||||
import { DATA_SETS } from '../constants';
|
||||
|
||||
export default function BuiltinDatasetDialog({ open, onClose, projectId, onSuccess }) {
|
||||
const { t, i18n } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const [keyword, setKeyword] = useState('');
|
||||
const [selectedDataset, setSelectedDataset] = useState(null);
|
||||
const [confirmOpen, setConfirmOpen] = useState(false);
|
||||
const [downloading, setDownloading] = useState(false);
|
||||
|
||||
const isZh = i18n.language.startsWith('zh');
|
||||
|
||||
// 过滤数据集
|
||||
const filteredDatasets = useMemo(() => {
|
||||
if (!keyword) return DATA_SETS;
|
||||
const lowerKeyword = keyword.toLowerCase();
|
||||
return DATA_SETS.filter(
|
||||
ds =>
|
||||
ds.zh.toLowerCase().includes(lowerKeyword) ||
|
||||
ds.en.toLowerCase().includes(lowerKeyword) ||
|
||||
ds.type.toLowerCase().includes(lowerKeyword)
|
||||
);
|
||||
}, [keyword]);
|
||||
|
||||
const handleCardClick = dataset => {
|
||||
setSelectedDataset(dataset);
|
||||
setConfirmOpen(true);
|
||||
};
|
||||
|
||||
const handleConfirmClose = () => {
|
||||
setConfirmOpen(false);
|
||||
setSelectedDataset(null);
|
||||
};
|
||||
|
||||
const handleImport = async () => {
|
||||
if (!selectedDataset) return;
|
||||
|
||||
setDownloading(true);
|
||||
setConfirmOpen(false);
|
||||
|
||||
try {
|
||||
const cdnUrl = `https://raw.githubusercontent.com/ConardLi/easy-dataset-eval/main/${selectedDataset.file}`;
|
||||
const response = await fetch(cdnUrl);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch dataset: ${response.statusText}`);
|
||||
}
|
||||
const jsonData = await response.blob();
|
||||
|
||||
const formData = new FormData();
|
||||
const file = new File([jsonData], `${selectedDataset.en}.json`, { type: 'application/json' });
|
||||
formData.append('file', file);
|
||||
formData.append('questionType', selectedDataset.type);
|
||||
const tags = `[${selectedDataset.level}] ${selectedDataset.en}`;
|
||||
formData.append('tags', tags);
|
||||
|
||||
const importResponse = await fetch(`/api/projects/${projectId}/eval-datasets/import`, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
const result = await importResponse.json();
|
||||
|
||||
if (result.code === 0) {
|
||||
onSuccess?.(result.data);
|
||||
handleClose();
|
||||
} else {
|
||||
console.error(result.error);
|
||||
alert(result.error || t('evalDatasets.import.failed'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Import failed:', error);
|
||||
alert(error.message || t('evalDatasets.import.failed'));
|
||||
} finally {
|
||||
setDownloading(false);
|
||||
setSelectedDataset(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
if (downloading) return;
|
||||
setKeyword('');
|
||||
setSelectedDataset(null);
|
||||
setConfirmOpen(false);
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog open={open} onClose={handleClose} maxWidth="md" fullWidth>
|
||||
<StyledDialogTitle>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
|
||||
<StorageIcon color="primary" />
|
||||
<Typography variant="h6" sx={{ fontWeight: 700 }}>
|
||||
{t('evalDatasets.import.builtinTitle', '选择内置数据集')}
|
||||
</Typography>
|
||||
</Box>
|
||||
<IconButton onClick={handleClose} disabled={downloading} size="small">
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</StyledDialogTitle>
|
||||
|
||||
<DialogContent
|
||||
dividers
|
||||
sx={{
|
||||
p: 0,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '70vh',
|
||||
bgcolor: alpha(theme.palette.grey[50], 0.5)
|
||||
}}
|
||||
>
|
||||
{/* 搜索栏 */}
|
||||
<Box sx={{ p: 2, bgcolor: 'background.paper', borderBottom: 1, borderColor: 'divider' }}>
|
||||
<TextField
|
||||
fullWidth
|
||||
size="small"
|
||||
placeholder={t('evalDatasets.import.searchPlaceholder', '搜索数据集...')}
|
||||
value={keyword}
|
||||
onChange={e => setKeyword(e.target.value)}
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon sx={{ color: 'text.disabled', fontSize: 20 }} />
|
||||
</InputAdornment>
|
||||
),
|
||||
sx: { borderRadius: 2 }
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* 数据集列表 */}
|
||||
<Box sx={{ flex: 1, overflow: 'auto', p: 2 }}>
|
||||
{downloading ? (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: '100%',
|
||||
gap: 2
|
||||
}}
|
||||
>
|
||||
<CircularProgress size={32} thickness={4} />
|
||||
<Typography variant="body2" color="text.secondary" sx={{ fontWeight: 500 }}>
|
||||
{t('evalDatasets.import.downloading', '下载并导入中...')}
|
||||
</Typography>
|
||||
</Box>
|
||||
) : (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fill, minmax(140px, 1fr))',
|
||||
gap: 1.5,
|
||||
alignContent: 'start'
|
||||
}}
|
||||
>
|
||||
{filteredDatasets.map((ds, index) => {
|
||||
const difficultyColor = ds.level === 'easy' ? 'success.main' : 'warning.main';
|
||||
const typeLabel = t(`eval.questionTypes.${ds.type}`, ds.type);
|
||||
const tooltipTitle = (
|
||||
<Box sx={{ display: 'flex', gap: 0.8, p: 0.5 }}>
|
||||
<Chip
|
||||
label={typeLabel}
|
||||
size="small"
|
||||
sx={{
|
||||
height: 20,
|
||||
fontSize: '0.65rem',
|
||||
bgcolor: alpha('#fff', 0.15),
|
||||
color: '#fff',
|
||||
border: '1px solid',
|
||||
borderColor: alpha('#fff', 0.1),
|
||||
fontWeight: 500
|
||||
}}
|
||||
/>
|
||||
<Chip
|
||||
label={ds.level.toUpperCase()}
|
||||
size="small"
|
||||
color={ds.level === 'easy' ? 'success' : 'warning'}
|
||||
sx={{
|
||||
height: 20,
|
||||
fontSize: '0.65rem',
|
||||
fontWeight: 800,
|
||||
boxShadow: '0 2px 4px rgba(0,0,0,0.2)'
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
key={index}
|
||||
title={tooltipTitle}
|
||||
arrow
|
||||
placement="top"
|
||||
componentsProps={{
|
||||
tooltip: {
|
||||
sx: {
|
||||
bgcolor: 'rgba(33, 33, 33, 0.95)',
|
||||
backdropFilter: 'blur(4px)',
|
||||
boxShadow: '0 4px 20px rgba(0,0,0,0.15)',
|
||||
borderRadius: 1.5,
|
||||
padding: '4px 8px'
|
||||
}
|
||||
},
|
||||
arrow: {
|
||||
sx: {
|
||||
color: 'rgba(33, 33, 33, 0.95)'
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Card
|
||||
variant="outlined"
|
||||
sx={{
|
||||
borderRadius: 2,
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
borderLeft: '4px solid',
|
||||
borderLeftColor: difficultyColor,
|
||||
transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
bgcolor: 'background.paper',
|
||||
cursor: 'pointer',
|
||||
'&:hover': {
|
||||
transform: 'translateY(-2px)',
|
||||
boxShadow: `0 6px 16px ${alpha(theme.palette.primary.main, 0.1)}`,
|
||||
borderColor: theme.palette.primary.main,
|
||||
'& .dataset-title': { color: 'primary.main' }
|
||||
}
|
||||
}}
|
||||
onClick={() => handleCardClick(ds)}
|
||||
>
|
||||
<CardActionArea
|
||||
sx={{
|
||||
p: 1.5,
|
||||
height: '100%',
|
||||
minHeight: 64,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-start'
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
className="dataset-title"
|
||||
variant="subtitle2"
|
||||
sx={{
|
||||
fontSize: '0.85rem',
|
||||
fontWeight: 600,
|
||||
lineHeight: 1.3,
|
||||
color: 'text.primary',
|
||||
transition: 'color 0.2s',
|
||||
display: '-webkit-box',
|
||||
WebkitLineClamp: 2,
|
||||
WebkitBoxOrient: 'vertical',
|
||||
overflow: 'hidden',
|
||||
width: '100%'
|
||||
}}
|
||||
>
|
||||
{isZh ? ds.zh : ds.en}
|
||||
</Typography>
|
||||
</CardActionArea>
|
||||
</Card>
|
||||
</Tooltip>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<Dialog
|
||||
open={confirmOpen}
|
||||
onClose={handleConfirmClose}
|
||||
maxWidth="xs"
|
||||
fullWidth
|
||||
PaperProps={{ sx: { borderRadius: 3 } }}
|
||||
>
|
||||
<DialogTitle sx={{ fontWeight: 700, pb: 1 }}>
|
||||
{t('evalDatasets.import.confirmImportTitle', '确认导入')}
|
||||
</DialogTitle>
|
||||
<DialogContent sx={{ pb: 1 }}>
|
||||
<DialogContentText sx={{ color: 'text.primary' }}>
|
||||
{selectedDataset &&
|
||||
t('evalDatasets.import.confirmImportMessage', {
|
||||
name: isZh ? selectedDataset.zh : selectedDataset.en
|
||||
})}
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ p: 2.5, pt: 1.5 }}>
|
||||
<Button onClick={handleConfirmClose} color="inherit" sx={{ fontWeight: 600 }}>
|
||||
{t('common.cancel', '取消')}
|
||||
</Button>
|
||||
<Button onClick={handleImport} variant="contained" autoFocus sx={{ fontWeight: 600, px: 3 }}>
|
||||
{t('evalDatasets.import.import', '导入')}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user