Files
YG-Datasets/easy-dataset-main/app/projects/[projectId]/eval-datasets/components/BuiltinDatasetDialog.js

328 lines
11 KiB
JavaScript

'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>
</>
);
}