first-update
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
'use client';
|
||||
|
||||
import { Box, Button } from '@mui/material';
|
||||
import AssessmentIcon from '@mui/icons-material/Assessment';
|
||||
import FileUploadIcon from '@mui/icons-material/FileUpload';
|
||||
import FileDownloadIcon from '@mui/icons-material/FileDownload';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const ActionBar = ({ onBatchEvaluate, onImport, onExport, batchEvaluating = false }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', gap: 2 }}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
startIcon={<AssessmentIcon />}
|
||||
sx={{ borderRadius: 2 }}
|
||||
onClick={onBatchEvaluate}
|
||||
disabled={batchEvaluating}
|
||||
>
|
||||
{batchEvaluating ? t('datasets.evaluating', '评估中...') : t('datasets.batchEvaluate', '批量评估')}
|
||||
</Button>
|
||||
<Button variant="outlined" startIcon={<FileUploadIcon />} sx={{ borderRadius: 2 }} onClick={onImport}>
|
||||
{t('import.title', '导入')}
|
||||
</Button>
|
||||
<Button variant="outlined" startIcon={<FileDownloadIcon />} sx={{ borderRadius: 2 }} onClick={onExport}>
|
||||
{t('export.title')}
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default ActionBar;
|
||||
@@ -0,0 +1,422 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
IconButton,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Chip,
|
||||
Divider,
|
||||
useTheme,
|
||||
alpha,
|
||||
Tooltip,
|
||||
Checkbox,
|
||||
TablePagination,
|
||||
TextField,
|
||||
Card,
|
||||
CircularProgress
|
||||
} from '@mui/material';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import VisibilityIcon from '@mui/icons-material/Visibility';
|
||||
import AssessmentIcon from '@mui/icons-material/Assessment';
|
||||
import StarIcon from '@mui/icons-material/Star';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { getRatingConfigI18n, formatScore } from '@/components/datasets/utils/ratingUtils';
|
||||
|
||||
// 数据集列表组件
|
||||
const DatasetList = ({
|
||||
datasets,
|
||||
onViewDetails,
|
||||
onDelete,
|
||||
onEvaluate,
|
||||
page,
|
||||
rowsPerPage,
|
||||
onPageChange,
|
||||
onRowsPerPageChange,
|
||||
total,
|
||||
selectedIds,
|
||||
onSelectAll,
|
||||
onSelectItem,
|
||||
evaluatingIds = [],
|
||||
loading = false
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const bgColor = theme.palette.mode === 'dark' ? theme.palette.primary.dark : theme.palette.primary.light;
|
||||
const color =
|
||||
theme.palette.mode === 'dark'
|
||||
? theme.palette.getContrastText(theme.palette.primary.main)
|
||||
: theme.palette.getContrastText(theme.palette.primary.contrastText);
|
||||
|
||||
const RatingChip = ({ score }) => {
|
||||
const config = getRatingConfigI18n(score, t);
|
||||
return (
|
||||
<Chip
|
||||
icon={<StarIcon sx={{ fontSize: '14px !important' }} />}
|
||||
label={`${formatScore(score)} ${config.label}`}
|
||||
size="small"
|
||||
sx={{
|
||||
backgroundColor: config.backgroundColor,
|
||||
color: config.color,
|
||||
fontWeight: 'medium',
|
||||
'& .MuiChip-icon': {
|
||||
color: config.color
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card elevation={2}>
|
||||
<Box sx={{ position: 'relative' }}>
|
||||
<TableContainer sx={{ overflowX: 'auto' }}>
|
||||
<Table sx={{ minWidth: 900 }}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell
|
||||
padding="checkbox"
|
||||
sx={{
|
||||
backgroundColor: bgColor,
|
||||
color: color,
|
||||
borderBottom: `2px solid ${theme.palette.divider}`
|
||||
}}
|
||||
>
|
||||
<Checkbox
|
||||
color="primary"
|
||||
indeterminate={selectedIds.length > 0 && selectedIds.length < total}
|
||||
checked={total > 0 && selectedIds.length === total}
|
||||
onChange={onSelectAll}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
sx={{
|
||||
backgroundColor: bgColor,
|
||||
color: color,
|
||||
fontWeight: 'bold',
|
||||
padding: '16px 8px',
|
||||
borderBottom: `2px solid ${theme.palette.divider}`,
|
||||
minWidth: 200
|
||||
}}
|
||||
>
|
||||
{t('datasets.question')}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
sx={{
|
||||
backgroundColor: bgColor,
|
||||
color: color,
|
||||
fontWeight: 'bold',
|
||||
padding: '16px 8px',
|
||||
borderBottom: `2px solid ${theme.palette.divider}`,
|
||||
width: 120
|
||||
}}
|
||||
>
|
||||
{t('datasets.rating', '评分')}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
sx={{
|
||||
backgroundColor: bgColor,
|
||||
color: color,
|
||||
fontWeight: 'bold',
|
||||
padding: '16px 8px',
|
||||
borderBottom: `2px solid ${theme.palette.divider}`,
|
||||
width: 100
|
||||
}}
|
||||
>
|
||||
{t('datasets.model')}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
sx={{
|
||||
backgroundColor: bgColor,
|
||||
color: color,
|
||||
fontWeight: 'bold',
|
||||
padding: '16px 8px',
|
||||
borderBottom: `2px solid ${theme.palette.divider}`,
|
||||
width: 100
|
||||
}}
|
||||
>
|
||||
{t('datasets.domainTag')}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
sx={{
|
||||
backgroundColor: bgColor,
|
||||
color: color,
|
||||
fontWeight: 'bold',
|
||||
padding: '16px 8px',
|
||||
borderBottom: `2px solid ${theme.palette.divider}`,
|
||||
width: 120
|
||||
}}
|
||||
>
|
||||
{t('datasets.createdAt')}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
sx={{
|
||||
backgroundColor: bgColor,
|
||||
color: color,
|
||||
fontWeight: 'bold',
|
||||
padding: '16px 8px',
|
||||
borderBottom: `2px solid ${theme.palette.divider}`,
|
||||
width: 120
|
||||
}}
|
||||
>
|
||||
{t('common.actions')}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{datasets.map((dataset, index) => (
|
||||
<>
|
||||
<TableRow
|
||||
key={dataset.id}
|
||||
sx={{
|
||||
'&:nth-of-type(odd)': { backgroundColor: alpha(theme.palette.primary.light, 0.05) },
|
||||
'&:hover': { backgroundColor: alpha(theme.palette.primary.light, 0.1) },
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
onClick={() => onViewDetails(dataset.id)}
|
||||
>
|
||||
<TableCell
|
||||
padding="checkbox"
|
||||
sx={{
|
||||
borderLeft: `4px solid ${theme.palette.primary.main}`
|
||||
}}
|
||||
>
|
||||
<Checkbox
|
||||
color="primary"
|
||||
checked={selectedIds.includes(dataset.id)}
|
||||
onChange={e => {
|
||||
e.stopPropagation();
|
||||
onSelectItem(dataset.id);
|
||||
}}
|
||||
onClick={e => e.stopPropagation()}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell sx={{ py: 2 }}>
|
||||
<Box>
|
||||
<Typography
|
||||
variant="body2"
|
||||
fontWeight="medium"
|
||||
sx={{
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
display: '-webkit-box',
|
||||
WebkitLineClamp: 2,
|
||||
WebkitBoxOrient: 'vertical',
|
||||
lineHeight: 1.4,
|
||||
mb: 0.5
|
||||
}}
|
||||
>
|
||||
{dataset.question}
|
||||
</Typography>
|
||||
{dataset.confirmed && (
|
||||
<Chip
|
||||
label={t('datasets.confirmed')}
|
||||
size="small"
|
||||
sx={{
|
||||
backgroundColor: alpha(theme.palette.success.main, 0.1),
|
||||
color: theme.palette.success.dark,
|
||||
fontWeight: 'medium',
|
||||
height: 20,
|
||||
fontSize: '0.7rem',
|
||||
mt: 1
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<RatingChip score={dataset.score || 0} />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Chip
|
||||
label={dataset.model}
|
||||
size="small"
|
||||
sx={{
|
||||
backgroundColor: alpha(theme.palette.info.main, 0.1),
|
||||
color: theme.palette.info.dark,
|
||||
fontWeight: 'medium',
|
||||
maxWidth: '100%',
|
||||
'& .MuiChip-label': {
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{dataset.questionLabel ? (
|
||||
<Chip
|
||||
label={dataset.questionLabel}
|
||||
size="small"
|
||||
sx={{
|
||||
backgroundColor: alpha(theme.palette.primary.main, 0.1),
|
||||
color: theme.palette.primary.dark,
|
||||
fontWeight: 'medium',
|
||||
maxWidth: '100%',
|
||||
'& .MuiChip-label': {
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Typography variant="body2" color="text.disabled" fontSize="0.75rem">
|
||||
{t('datasets.noTag')}
|
||||
</Typography>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Typography variant="body2" color="text.secondary" fontSize="0.75rem">
|
||||
{new Date(dataset.createAt).toLocaleDateString('zh-CN')}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Box sx={{ display: 'flex', gap: 0.5 }}>
|
||||
<Tooltip title={t('datasets.viewDetails')}>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
onViewDetails(dataset.id);
|
||||
}}
|
||||
sx={{
|
||||
color: theme.palette.primary.main,
|
||||
'&:hover': { backgroundColor: alpha(theme.palette.primary.main, 0.1) }
|
||||
}}
|
||||
>
|
||||
<VisibilityIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title={t('datasets.evaluate')}>
|
||||
<IconButton
|
||||
size="small"
|
||||
disabled={evaluatingIds.includes(dataset.id)}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
onEvaluate && onEvaluate(dataset);
|
||||
}}
|
||||
sx={{
|
||||
color: theme.palette.secondary.main,
|
||||
'&:hover': { backgroundColor: alpha(theme.palette.secondary.main, 0.1) }
|
||||
}}
|
||||
>
|
||||
{evaluatingIds.includes(dataset.id) ? (
|
||||
<CircularProgress size={20} sx={{ color: theme.palette.secondary.main }} />
|
||||
) : (
|
||||
<AssessmentIcon fontSize="small" />
|
||||
)}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title={t('common.delete')}>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
onDelete(dataset);
|
||||
}}
|
||||
sx={{
|
||||
color: theme.palette.error.main,
|
||||
'&:hover': { backgroundColor: alpha(theme.palette.error.main, 0.1) }
|
||||
}}
|
||||
>
|
||||
<DeleteIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</>
|
||||
))}
|
||||
{datasets.length === 0 && (
|
||||
<TableRow>
|
||||
<TableCell colSpan={8} align="center" sx={{ py: 6 }}>
|
||||
<Typography variant="body1" color="text.secondary">
|
||||
{t('datasets.noData')}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
{loading && (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
inset: 0,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: alpha(theme.palette.background.paper, 0.6),
|
||||
backdropFilter: 'blur(2px)',
|
||||
zIndex: 1
|
||||
}}
|
||||
>
|
||||
<CircularProgress size={32} />
|
||||
<Typography variant="body2" sx={{ mt: 1 }} color="text.secondary">
|
||||
{t('datasets.loading')}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
<Divider />
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
px: 2,
|
||||
py: 1,
|
||||
borderTop: `1px solid ${theme.palette.divider}`
|
||||
}}
|
||||
>
|
||||
<TablePagination
|
||||
component="div"
|
||||
count={total}
|
||||
page={page - 1}
|
||||
onPageChange={onPageChange}
|
||||
rowsPerPage={rowsPerPage}
|
||||
onRowsPerPageChange={onRowsPerPageChange}
|
||||
labelRowsPerPage={t('datasets.rowsPerPage')}
|
||||
labelDisplayedRows={({ from, to, count }) => t('datasets.pagination', { from, to, count })}
|
||||
sx={{
|
||||
'.MuiTablePagination-selectLabel, .MuiTablePagination-displayedRows': {
|
||||
fontWeight: 'medium'
|
||||
},
|
||||
border: 'none'
|
||||
}}
|
||||
/>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<Typography variant="body2">{t('common.jumpTo')}:</Typography>
|
||||
<TextField
|
||||
size="small"
|
||||
type="number"
|
||||
inputProps={{
|
||||
min: 1,
|
||||
max: Math.ceil(total / rowsPerPage),
|
||||
style: { padding: '4px 8px', width: '50px' }
|
||||
}}
|
||||
onKeyPress={e => {
|
||||
if (e.key === 'Enter') {
|
||||
const pageNum = parseInt(e.target.value, 10);
|
||||
if (pageNum >= 1 && pageNum <= Math.ceil(total / rowsPerPage)) {
|
||||
onPageChange(null, pageNum - 1);
|
||||
e.target.value = '';
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default DatasetList;
|
||||
@@ -0,0 +1,105 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Typography,
|
||||
Paper,
|
||||
Box,
|
||||
LinearProgress,
|
||||
Button,
|
||||
useTheme,
|
||||
alpha
|
||||
} from '@mui/material';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const DeleteConfirmDialog = ({ open, datasets, onClose, onConfirm, batch, progress, deleting }) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const dataset = datasets?.[0];
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
PaperProps={{
|
||||
elevation: 3,
|
||||
sx: { borderRadius: 2 }
|
||||
}}
|
||||
>
|
||||
<DialogTitle sx={{ pb: 1 }}>
|
||||
<Typography variant="h6" fontWeight="bold">
|
||||
{t('common.confirmDelete')}
|
||||
</Typography>
|
||||
</DialogTitle>
|
||||
<DialogContent sx={{ pb: 2, pt: 1 }}>
|
||||
<Typography variant="body1" sx={{ mb: 2 }}>
|
||||
{batch
|
||||
? t('datasets.batchconfirmDeleteMessage', {
|
||||
count: datasets.length
|
||||
})
|
||||
: t('common.confirmDeleteDataSet')}
|
||||
</Typography>
|
||||
{batch ? (
|
||||
''
|
||||
) : (
|
||||
<Paper
|
||||
variant="outlined"
|
||||
sx={{
|
||||
p: 2,
|
||||
backgroundColor: alpha(theme.palette.warning.light, 0.1),
|
||||
borderColor: theme.palette.warning.light
|
||||
}}
|
||||
>
|
||||
<Typography variant="subtitle2" color="text.secondary" fontWeight="bold">
|
||||
{t('datasets.question')}:
|
||||
</Typography>
|
||||
<Typography variant="body2">{dataset?.question}</Typography>
|
||||
</Paper>
|
||||
)}
|
||||
{deleting && progress ? (
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 1 }}>
|
||||
<Typography variant="body1" sx={{ mr: 1 }}>
|
||||
{progress.percentage}%
|
||||
</Typography>
|
||||
<Box sx={{ width: '100%' }}>
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={progress.percentage}
|
||||
sx={{
|
||||
height: 8,
|
||||
borderRadius: 4,
|
||||
backgroundColor: alpha(theme.palette.primary.main, 0.1),
|
||||
'& .MuiLinearProgress-bar': {
|
||||
borderRadius: 4,
|
||||
backgroundColor: theme.palette.primary.main
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{t('datasets.deletingProgress', '正在删除 {{completed}}/{{total}} 个数据集...', {
|
||||
completed: progress.completed,
|
||||
total: progress.total
|
||||
})}
|
||||
</Typography>
|
||||
</Box>
|
||||
) : null}
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ px: 3, pb: 2 }}>
|
||||
<Button onClick={onClose} disabled={deleting} sx={{ borderRadius: 2 }}>
|
||||
{t('common.cancel')}
|
||||
</Button>
|
||||
<Button onClick={onConfirm} variant="contained" color="error" disabled={deleting} sx={{ borderRadius: 2 }}>
|
||||
{deleting ? t('common.deleting') : t('common.delete')}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeleteConfirmDialog;
|
||||
@@ -0,0 +1,198 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Box,
|
||||
Typography,
|
||||
Select,
|
||||
MenuItem,
|
||||
Slider,
|
||||
TextField,
|
||||
Button,
|
||||
InputAdornment
|
||||
} from '@mui/material';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const FilterDialog = ({
|
||||
open,
|
||||
onClose,
|
||||
filterConfirmed,
|
||||
filterHasCot,
|
||||
filterIsDistill,
|
||||
filterScoreRange,
|
||||
filterCustomTag,
|
||||
filterNoteKeyword,
|
||||
filterChunkName,
|
||||
availableTags,
|
||||
onFilterConfirmedChange,
|
||||
onFilterHasCotChange,
|
||||
onFilterIsDistillChange,
|
||||
onFilterScoreRangeChange,
|
||||
onFilterCustomTagChange,
|
||||
onFilterNoteKeywordChange,
|
||||
onFilterChunkNameChange,
|
||||
onResetFilters,
|
||||
onApplyFilters
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose} maxWidth="xs" fullWidth>
|
||||
<DialogTitle>{t('datasets.filtersTitle')}</DialogTitle>
|
||||
<DialogContent>
|
||||
<Box sx={{ mb: 3, mt: 1 }}>
|
||||
<Typography variant="subtitle2" gutterBottom>
|
||||
{t('datasets.filterConfirmationStatus')}
|
||||
</Typography>
|
||||
<Select
|
||||
value={filterConfirmed}
|
||||
onChange={e => onFilterConfirmedChange(e.target.value)}
|
||||
fullWidth
|
||||
size="small"
|
||||
sx={{ mt: 1 }}
|
||||
>
|
||||
<MenuItem value="all">{t('datasets.filterAll')}</MenuItem>
|
||||
<MenuItem value="confirmed">{t('datasets.filterConfirmed')}</MenuItem>
|
||||
<MenuItem value="unconfirmed">{t('datasets.filterUnconfirmed')}</MenuItem>
|
||||
</Select>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Typography variant="subtitle2" gutterBottom>
|
||||
{t('datasets.filterCotStatus')}
|
||||
</Typography>
|
||||
<Select
|
||||
value={filterHasCot}
|
||||
onChange={e => onFilterHasCotChange(e.target.value)}
|
||||
fullWidth
|
||||
size="small"
|
||||
sx={{ mt: 1 }}
|
||||
>
|
||||
<MenuItem value="all">{t('datasets.filterAll')}</MenuItem>
|
||||
<MenuItem value="yes">{t('datasets.filterHasCot')}</MenuItem>
|
||||
<MenuItem value="no">{t('datasets.filterNoCot')}</MenuItem>
|
||||
</Select>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Typography variant="subtitle2" gutterBottom>
|
||||
{t('datasets.filterDistill')}
|
||||
</Typography>
|
||||
<Select
|
||||
value={filterIsDistill}
|
||||
onChange={e => onFilterIsDistillChange(e.target.value)}
|
||||
fullWidth
|
||||
size="small"
|
||||
sx={{ mt: 1 }}
|
||||
>
|
||||
<MenuItem value="all">{t('datasets.filterAll')}</MenuItem>
|
||||
<MenuItem value="yes">{t('datasets.filterDistillYes')}</MenuItem>
|
||||
<MenuItem value="no">{t('datasets.filterDistillNo')}</MenuItem>
|
||||
</Select>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Typography variant="subtitle2" gutterBottom>
|
||||
{t('datasets.filterScoreRange')}
|
||||
</Typography>
|
||||
<Box sx={{ px: 1, mt: 2 }}>
|
||||
<Slider
|
||||
value={filterScoreRange}
|
||||
onChange={(_, newValue) => onFilterScoreRangeChange(newValue)}
|
||||
valueLabelDisplay="auto"
|
||||
min={0}
|
||||
max={5}
|
||||
step={0.5}
|
||||
marks={[
|
||||
{ value: 0, label: '0' },
|
||||
{ value: 2.5, label: '2.5' },
|
||||
{ value: 5, label: '5' }
|
||||
]}
|
||||
sx={{ mt: 1 }}
|
||||
/>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
{t('datasets.scoreRange', '{{min}} - {{max}} 分', {
|
||||
min: filterScoreRange[0],
|
||||
max: filterScoreRange[1]
|
||||
})}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Typography variant="subtitle2" gutterBottom>
|
||||
{t('datasets.filterCustomTag')}
|
||||
</Typography>
|
||||
<Select
|
||||
value={filterCustomTag}
|
||||
onChange={e => onFilterCustomTagChange(e.target.value)}
|
||||
fullWidth
|
||||
size="small"
|
||||
sx={{ mt: 1 }}
|
||||
>
|
||||
<MenuItem value="">{t('datasets.filterAll')}</MenuItem>
|
||||
{availableTags.map(tag => (
|
||||
<MenuItem key={tag} value={tag}>
|
||||
{tag}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Typography variant="subtitle2" gutterBottom>
|
||||
{t('datasets.filterNoteKeyword')}
|
||||
</Typography>
|
||||
<TextField
|
||||
value={filterNoteKeyword}
|
||||
onChange={e => onFilterNoteKeywordChange(e.target.value)}
|
||||
placeholder={t('datasets.filterNoteKeywordPlaceholder')}
|
||||
fullWidth
|
||||
size="small"
|
||||
sx={{ mt: 1 }}
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon fontSize="small" />
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Typography variant="subtitle2" gutterBottom>
|
||||
{t('datasets.filterChunkName')}
|
||||
</Typography>
|
||||
<TextField
|
||||
value={filterChunkName}
|
||||
onChange={e => onFilterChunkNameChange(e.target.value)}
|
||||
placeholder={t('datasets.filterChunkNamePlaceholder')}
|
||||
fullWidth
|
||||
size="small"
|
||||
sx={{ mt: 1 }}
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon fontSize="small" />
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onResetFilters}>{t('datasets.resetFilters')}</Button>
|
||||
<Button onClick={onApplyFilters} variant="contained">
|
||||
{t('datasets.applyFilters')}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default FilterDialog;
|
||||
@@ -0,0 +1,68 @@
|
||||
'use client';
|
||||
|
||||
import { Box, Paper, IconButton, InputBase, Select, MenuItem, Button, Badge } from '@mui/material';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
import FilterListIcon from '@mui/icons-material/FilterList';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const SearchBar = ({
|
||||
searchQuery,
|
||||
searchField,
|
||||
onSearchQueryChange,
|
||||
onSearchFieldChange,
|
||||
onMoreFiltersClick,
|
||||
activeFilterCount = 0
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', gap: 2 }}>
|
||||
<Paper
|
||||
component="form"
|
||||
sx={{
|
||||
p: '2px 4px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
width: 400,
|
||||
borderRadius: 2
|
||||
}}
|
||||
>
|
||||
<IconButton sx={{ p: '10px' }} aria-label="search">
|
||||
<SearchIcon />
|
||||
</IconButton>
|
||||
<InputBase
|
||||
sx={{ ml: 1, flex: 1 }}
|
||||
placeholder={t('datasets.searchPlaceholder')}
|
||||
value={searchQuery}
|
||||
onChange={e => onSearchQueryChange(e.target.value)}
|
||||
endAdornment={
|
||||
<Select
|
||||
value={searchField}
|
||||
onChange={e => onSearchFieldChange(e.target.value)}
|
||||
variant="standard"
|
||||
sx={{
|
||||
minWidth: 90,
|
||||
'& .MuiInput-underline:before': { borderBottom: 'none' },
|
||||
'& .MuiInput-underline:after': { borderBottom: 'none' },
|
||||
'& .MuiInput-underline:hover:not(.Mui-disabled):before': { borderBottom: 'none' }
|
||||
}}
|
||||
disableUnderline
|
||||
>
|
||||
<MenuItem value="question">{t('datasets.fieldQuestion')}</MenuItem>
|
||||
<MenuItem value="answer">{t('datasets.fieldAnswer')}</MenuItem>
|
||||
<MenuItem value="cot">{t('datasets.fieldCOT')}</MenuItem>
|
||||
<MenuItem value="questionLabel">{t('datasets.fieldLabel')}</MenuItem>
|
||||
</Select>
|
||||
}
|
||||
/>
|
||||
</Paper>
|
||||
<Badge badgeContent={activeFilterCount} color="error" overlap="circular">
|
||||
<Button variant="outlined" onClick={onMoreFiltersClick} startIcon={<FilterListIcon />} sx={{ borderRadius: 2 }}>
|
||||
{t('datasets.moreFilters')}
|
||||
</Button>
|
||||
</Badge>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default SearchBar;
|
||||
Reference in New Issue
Block a user