597 lines
18 KiB
JavaScript
597 lines
18 KiB
JavaScript
|
|
'use client';
|
|||
|
|
|
|||
|
|
import { useState, useEffect, useCallback } from 'react';
|
|||
|
|
import { Container, Box, Typography, Button, Card, useTheme, alpha } from '@mui/material';
|
|||
|
|
import DeleteIcon from '@mui/icons-material/Delete';
|
|||
|
|
import { useRouter } from 'next/navigation';
|
|||
|
|
import ExportDatasetDialog from '@/components/ExportDatasetDialog';
|
|||
|
|
import ExportProgressDialog from '@/components/ExportProgressDialog';
|
|||
|
|
import ImportDatasetDialog from '@/components/datasets/ImportDatasetDialog';
|
|||
|
|
import { useTranslation } from 'react-i18next';
|
|||
|
|
import DatasetList from './components/DatasetList';
|
|||
|
|
import SearchBar from './components/SearchBar';
|
|||
|
|
import ActionBar from './components/ActionBar';
|
|||
|
|
import FilterDialog from './components/FilterDialog';
|
|||
|
|
import DeleteConfirmDialog from './components/DeleteConfirmDialog';
|
|||
|
|
import useDatasetExport from './hooks/useDatasetExport';
|
|||
|
|
import useDatasetEvaluation from './hooks/useDatasetEvaluation';
|
|||
|
|
import useDatasetFilters from './hooks/useDatasetFilters';
|
|||
|
|
import { processInParallel } from '@/lib/util/async';
|
|||
|
|
import axios from 'axios';
|
|||
|
|
import { useDebounce } from '@/hooks/useDebounce';
|
|||
|
|
import { toast } from 'sonner';
|
|||
|
|
|
|||
|
|
// 主页面组件
|
|||
|
|
export default function DatasetsPage({ params }) {
|
|||
|
|
const { projectId } = params;
|
|||
|
|
const router = useRouter();
|
|||
|
|
const theme = useTheme();
|
|||
|
|
const [datasets, setDatasets] = useState({ data: [], total: 0, confirmedCount: 0 });
|
|||
|
|
const [loading, setLoading] = useState(true);
|
|||
|
|
const [deleteDialog, setDeleteDialog] = useState({
|
|||
|
|
open: false,
|
|||
|
|
datasets: null,
|
|||
|
|
batch: false,
|
|||
|
|
deleting: false
|
|||
|
|
});
|
|||
|
|
const [exportDialog, setExportDialog] = useState({ open: false });
|
|||
|
|
const [importDialog, setImportDialog] = useState({ open: false });
|
|||
|
|
const [selectedIds, setselectedIds] = useState([]);
|
|||
|
|
const [availableTags, setAvailableTags] = useState([]);
|
|||
|
|
const [filterDialogOpen, setFilterDialogOpen] = useState(false);
|
|||
|
|
const { t } = useTranslation();
|
|||
|
|
|
|||
|
|
// 使用 useDatasetFilters Hook 管理筛选条件
|
|||
|
|
const {
|
|||
|
|
filterConfirmed,
|
|||
|
|
setFilterConfirmed,
|
|||
|
|
filterHasCot,
|
|||
|
|
setFilterHasCot,
|
|||
|
|
filterIsDistill,
|
|||
|
|
setFilterIsDistill,
|
|||
|
|
filterScoreRange,
|
|||
|
|
setFilterScoreRange,
|
|||
|
|
filterCustomTag,
|
|||
|
|
setFilterCustomTag,
|
|||
|
|
filterNoteKeyword,
|
|||
|
|
setFilterNoteKeyword,
|
|||
|
|
filterChunkName,
|
|||
|
|
setFilterChunkName,
|
|||
|
|
searchQuery,
|
|||
|
|
setSearchQuery,
|
|||
|
|
searchField,
|
|||
|
|
setSearchField,
|
|||
|
|
page,
|
|||
|
|
setPage,
|
|||
|
|
rowsPerPage,
|
|||
|
|
setRowsPerPage,
|
|||
|
|
isInitialized,
|
|||
|
|
getActiveFilterCount
|
|||
|
|
} = useDatasetFilters(projectId);
|
|||
|
|
|
|||
|
|
const debouncedSearchQuery = useDebounce(searchQuery);
|
|||
|
|
// 删除进度状态
|
|||
|
|
const [deleteProgress, setDeteleProgress] = useState({
|
|||
|
|
total: 0, // 总删除问题数量
|
|||
|
|
completed: 0, // 已删除完成的数量
|
|||
|
|
percentage: 0 // 进度百分比
|
|||
|
|
});
|
|||
|
|
// 导出进度状态
|
|||
|
|
const [exportProgress, setExportProgress] = useState({
|
|||
|
|
show: false, // 是否显示进度
|
|||
|
|
processed: 0, // 已处理数量
|
|||
|
|
total: 0, // 总数量
|
|||
|
|
hasMore: true // 是否还有更多数据
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 3. 添加打开导出对话框的处理函数
|
|||
|
|
const handleOpenExportDialog = () => {
|
|||
|
|
setExportDialog({ open: true });
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 4. 添加关闭导出对话框的处理函数
|
|||
|
|
const handleCloseExportDialog = () => {
|
|||
|
|
setExportDialog({ open: false });
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 5. 添加打开导入对话框的处理函数
|
|||
|
|
const handleOpenImportDialog = () => {
|
|||
|
|
setImportDialog({ open: true });
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 6. 添加关闭导入对话框的处理函数
|
|||
|
|
const handleCloseImportDialog = () => {
|
|||
|
|
setImportDialog({ open: false });
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 7. 导入成功后的处理函数
|
|||
|
|
const handleImportSuccess = () => {
|
|||
|
|
// 刷新数据集列表
|
|||
|
|
getDatasetsList();
|
|||
|
|
toast.success(t('import.importSuccess', '数据集导入成功'));
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 获取数据集列表
|
|||
|
|
const getDatasetsList = useCallback(
|
|||
|
|
async ({ pageOverride } = {}) => {
|
|||
|
|
const effectivePage = pageOverride ?? page;
|
|||
|
|
try {
|
|||
|
|
setLoading(true);
|
|||
|
|
let url = `/api/projects/${projectId}/datasets?page=${effectivePage}&size=${rowsPerPage}`;
|
|||
|
|
|
|||
|
|
if (filterConfirmed !== 'all') {
|
|||
|
|
url += `&status=${filterConfirmed}`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (debouncedSearchQuery) {
|
|||
|
|
url += `&input=${encodeURIComponent(debouncedSearchQuery)}&field=${searchField}`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (filterHasCot !== 'all') {
|
|||
|
|
url += `&hasCot=${filterHasCot}`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (filterIsDistill !== 'all') {
|
|||
|
|
url += `&isDistill=${filterIsDistill}`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (filterScoreRange[0] > 0 || filterScoreRange[1] < 5) {
|
|||
|
|
url += `&scoreRange=${filterScoreRange[0]}-${filterScoreRange[1]}`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (filterCustomTag) {
|
|||
|
|
url += `&customTag=${encodeURIComponent(filterCustomTag)}`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (filterNoteKeyword) {
|
|||
|
|
url += `¬eKeyword=${encodeURIComponent(filterNoteKeyword)}`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (filterChunkName) {
|
|||
|
|
url += `&chunkName=${encodeURIComponent(filterChunkName)}`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const response = await axios.get(url);
|
|||
|
|
setDatasets(response.data || { data: [], total: 0, confirmedCount: 0 });
|
|||
|
|
} catch (error) {
|
|||
|
|
toast.error(error.message);
|
|||
|
|
} finally {
|
|||
|
|
setLoading(false);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
[
|
|||
|
|
debouncedSearchQuery,
|
|||
|
|
filterConfirmed,
|
|||
|
|
filterCustomTag,
|
|||
|
|
filterHasCot,
|
|||
|
|
filterIsDistill,
|
|||
|
|
filterNoteKeyword,
|
|||
|
|
filterChunkName,
|
|||
|
|
filterScoreRange,
|
|||
|
|
page,
|
|||
|
|
projectId,
|
|||
|
|
rowsPerPage,
|
|||
|
|
searchField
|
|||
|
|
]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
useEffect(() => {
|
|||
|
|
if (!isInitialized) return;
|
|||
|
|
|
|||
|
|
getDatasetsList();
|
|||
|
|
// 获取项目中所有使用过的标签
|
|||
|
|
const fetchAvailableTags = async () => {
|
|||
|
|
try {
|
|||
|
|
const response = await fetch(`/api/projects/${projectId}/datasets/tags`);
|
|||
|
|
if (response.ok) {
|
|||
|
|
const data = await response.json();
|
|||
|
|
setAvailableTags(data.tags || []);
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取标签失败:', error);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
fetchAvailableTags();
|
|||
|
|
}, [projectId, page, rowsPerPage, debouncedSearchQuery, searchField, isInitialized]);
|
|||
|
|
|
|||
|
|
// 处理页码变化
|
|||
|
|
const handlePageChange = (_event, newPage) => {
|
|||
|
|
// MUI TablePagination 的页码从 0 开始,而我们的 API 从 1 开始
|
|||
|
|
setPage(newPage + 1);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 处理每页行数变化
|
|||
|
|
const handleRowsPerPageChange = event => {
|
|||
|
|
setPage(1);
|
|||
|
|
setRowsPerPage(parseInt(event.target.value, 10));
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 打开删除确认框
|
|||
|
|
const handleOpenDeleteDialog = dataset => {
|
|||
|
|
setDeleteDialog({
|
|||
|
|
open: true,
|
|||
|
|
datasets: [dataset]
|
|||
|
|
});
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 关闭删除确认框
|
|||
|
|
const handleCloseDeleteDialog = () => {
|
|||
|
|
setDeleteDialog({
|
|||
|
|
open: false,
|
|||
|
|
dataset: null
|
|||
|
|
});
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleBatchDeleteDataset = async () => {
|
|||
|
|
const datasetsArray = selectedIds.map(id => ({ id }));
|
|||
|
|
setDeleteDialog({
|
|||
|
|
open: true,
|
|||
|
|
datasets: datasetsArray,
|
|||
|
|
batch: true,
|
|||
|
|
count: selectedIds.length
|
|||
|
|
});
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const resetProgress = () => {
|
|||
|
|
setDeteleProgress({
|
|||
|
|
total: deleteDialog.count,
|
|||
|
|
completed: 0,
|
|||
|
|
percentage: 0
|
|||
|
|
});
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleDeleteConfirm = async () => {
|
|||
|
|
if (deleteDialog.batch) {
|
|||
|
|
setDeleteDialog({
|
|||
|
|
...deleteDialog,
|
|||
|
|
deleting: true
|
|||
|
|
});
|
|||
|
|
await handleBatchDelete();
|
|||
|
|
resetProgress();
|
|||
|
|
} else {
|
|||
|
|
const [dataset] = deleteDialog.datasets;
|
|||
|
|
if (!dataset) return;
|
|||
|
|
await handleDelete(dataset);
|
|||
|
|
}
|
|||
|
|
setselectedIds([]);
|
|||
|
|
// 刷新数据
|
|||
|
|
getDatasetsList();
|
|||
|
|
// 关闭确认框
|
|||
|
|
handleCloseDeleteDialog();
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 批量删除数据集
|
|||
|
|
const handleBatchDelete = async () => {
|
|||
|
|
try {
|
|||
|
|
await processInParallel(
|
|||
|
|
selectedIds,
|
|||
|
|
async datasetId => {
|
|||
|
|
await fetch(`/api/projects/${projectId}/datasets?id=${datasetId}`, {
|
|||
|
|
method: 'DELETE'
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
3,
|
|||
|
|
(cur, total) => {
|
|||
|
|
setDeteleProgress({
|
|||
|
|
total,
|
|||
|
|
completed: cur,
|
|||
|
|
percentage: Math.floor((cur / total) * 100)
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
toast.success(t('common.deleteSuccess'));
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('批量删除失败:', error);
|
|||
|
|
toast.error(error.message || t('common.deleteFailed'));
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 删除数据集
|
|||
|
|
const handleDelete = async dataset => {
|
|||
|
|
try {
|
|||
|
|
const response = await fetch(`/api/projects/${projectId}/datasets?id=${dataset.id}`, {
|
|||
|
|
method: 'DELETE'
|
|||
|
|
});
|
|||
|
|
if (!response.ok) throw new Error(t('datasets.deleteFailed'));
|
|||
|
|
|
|||
|
|
toast.success(t('datasets.deleteSuccess'));
|
|||
|
|
} catch (error) {
|
|||
|
|
toast.error(error.message || t('datasets.deleteFailed'));
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 使用自定义 Hook 处理数据集导出逻辑
|
|||
|
|
const { exportDatasets, exportDatasetsStreaming } = useDatasetExport(projectId);
|
|||
|
|
|
|||
|
|
// 使用自定义 Hook 处理数据集评估逻辑
|
|||
|
|
const { evaluatingIds, batchEvaluating, handleEvaluateDataset, handleBatchEvaluate } = useDatasetEvaluation(
|
|||
|
|
projectId,
|
|||
|
|
getDatasetsList
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 处理导出数据集 - 智能选择导出方式
|
|||
|
|
const handleExportDatasets = async exportOptions => {
|
|||
|
|
try {
|
|||
|
|
// 如果是平衡导出,则忽略选中项,按 balanceConfig 导出
|
|||
|
|
const exportOptionsWithSelection = exportOptions.balanceMode
|
|||
|
|
? { ...exportOptions }
|
|||
|
|
: { ...exportOptions, ...(selectedIds.length > 0 && { selectedIds }) };
|
|||
|
|
|
|||
|
|
// 获取数据总量:
|
|||
|
|
// 平衡导出时,按 balanceConfig 的总量计算;
|
|||
|
|
// 其他情况:如果有选中数据集则使用选中数量,否则使用当前筛选条件下的数据总量
|
|||
|
|
const balancedTotal = Array.isArray(exportOptions.balanceConfig)
|
|||
|
|
? exportOptions.balanceConfig.reduce((sum, c) => sum + (parseInt(c.maxCount) || 0), 0)
|
|||
|
|
: 0;
|
|||
|
|
const totalCount = exportOptions.balanceMode
|
|||
|
|
? balancedTotal
|
|||
|
|
: selectedIds.length > 0
|
|||
|
|
? selectedIds.length
|
|||
|
|
: datasets.total || 0;
|
|||
|
|
|
|||
|
|
// 设置阈值:超过1000条数据使用流式导出
|
|||
|
|
const STREAMING_THRESHOLD = 1000;
|
|||
|
|
|
|||
|
|
// 检查是否需要包含文本块内容
|
|||
|
|
const needsChunkContent = exportOptions.formatType === 'custom' && exportOptions.customFields?.includeChunk;
|
|||
|
|
|
|||
|
|
let success = false;
|
|||
|
|
|
|||
|
|
// 如果数据量大于阈值或需要查询文本块内容,使用流式导出
|
|||
|
|
if (totalCount > STREAMING_THRESHOLD || needsChunkContent) {
|
|||
|
|
// 使用流式导出,显示进度
|
|||
|
|
setExportProgress({ show: true, processed: 0, total: totalCount });
|
|||
|
|
|
|||
|
|
success = await exportDatasetsStreaming(exportOptionsWithSelection, progress => {
|
|||
|
|
setExportProgress(prev => ({
|
|||
|
|
...prev,
|
|||
|
|
processed: progress.processed,
|
|||
|
|
hasMore: progress.hasMore
|
|||
|
|
}));
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 隐藏进度
|
|||
|
|
setExportProgress({ show: false, processed: 0, total: 0 });
|
|||
|
|
} else {
|
|||
|
|
// 使用传统导出方式
|
|||
|
|
success = await exportDatasets(exportOptionsWithSelection);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (success) {
|
|||
|
|
// 关闭export对话框
|
|||
|
|
handleCloseExportDialog();
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('Export failed:', error);
|
|||
|
|
setExportProgress({ show: false, processed: 0, total: 0 });
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 查看详情
|
|||
|
|
const handleViewDetails = id => {
|
|||
|
|
router.push(`/projects/${projectId}/datasets/${id}`);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 处理全选/取消全选
|
|||
|
|
const handleSelectAll = async event => {
|
|||
|
|
if (event.target.checked) {
|
|||
|
|
// 获取所有符合当前筛选条件的数据,不受分页限制
|
|||
|
|
let url = `/api/projects/${projectId}/datasets?selectedAll=1`;
|
|||
|
|
|
|||
|
|
if (filterConfirmed !== 'all') {
|
|||
|
|
url += `&status=${filterConfirmed}`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (debouncedSearchQuery) {
|
|||
|
|
url += `&input=${encodeURIComponent(debouncedSearchQuery)}&field=${searchField}`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (filterHasCot !== 'all') {
|
|||
|
|
url += `&hasCot=${filterHasCot}`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (filterIsDistill !== 'all') {
|
|||
|
|
url += `&isDistill=${filterIsDistill}`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (filterScoreRange[0] > 0 || filterScoreRange[1] < 5) {
|
|||
|
|
url += `&scoreRange=${filterScoreRange[0]}-${filterScoreRange[1]}`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (filterCustomTag) {
|
|||
|
|
url += `&customTag=${encodeURIComponent(filterCustomTag)}`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (filterNoteKeyword) {
|
|||
|
|
url += `¬eKeyword=${encodeURIComponent(filterNoteKeyword)}`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const response = await axios.get(url);
|
|||
|
|
setselectedIds(response.data.map(dataset => dataset.id));
|
|||
|
|
} else {
|
|||
|
|
setselectedIds([]);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 处理单个选择
|
|||
|
|
const handleSelectItem = id => {
|
|||
|
|
setselectedIds(prev => {
|
|||
|
|
if (prev.includes(id)) {
|
|||
|
|
return prev.filter(item => item !== id);
|
|||
|
|
} else {
|
|||
|
|
return [...prev, id];
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleResetFilters = useCallback(() => {
|
|||
|
|
setFilterConfirmed('all');
|
|||
|
|
setFilterHasCot('all');
|
|||
|
|
setFilterIsDistill('all');
|
|||
|
|
setFilterScoreRange([0, 5]);
|
|||
|
|
setFilterCustomTag('');
|
|||
|
|
setFilterNoteKeyword('');
|
|||
|
|
setFilterChunkName('');
|
|||
|
|
setPage(1);
|
|||
|
|
getDatasetsList({ pageOverride: 1 });
|
|||
|
|
}, [
|
|||
|
|
getDatasetsList,
|
|||
|
|
setFilterConfirmed,
|
|||
|
|
setFilterHasCot,
|
|||
|
|
setFilterIsDistill,
|
|||
|
|
setFilterScoreRange,
|
|||
|
|
setFilterCustomTag,
|
|||
|
|
setFilterNoteKeyword,
|
|||
|
|
setFilterChunkName,
|
|||
|
|
setPage
|
|||
|
|
]);
|
|||
|
|
|
|||
|
|
const handleApplyFilters = useCallback(() => {
|
|||
|
|
setFilterDialogOpen(false);
|
|||
|
|
setPage(1);
|
|||
|
|
getDatasetsList({ pageOverride: 1 });
|
|||
|
|
}, [getDatasetsList, setFilterDialogOpen, setPage]);
|
|||
|
|
const handleCloseFilterDialog = useCallback(() => setFilterDialogOpen(false), [setFilterDialogOpen]);
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<Container maxWidth="xl" sx={{ mt: 4, mb: 6 }}>
|
|||
|
|
<Card
|
|||
|
|
elevation={0}
|
|||
|
|
sx={{
|
|||
|
|
mb: 4,
|
|||
|
|
p: 3,
|
|||
|
|
backgroundColor: alpha(theme.palette.primary.light, 0.05),
|
|||
|
|
borderRadius: 2
|
|||
|
|
}}
|
|||
|
|
>
|
|||
|
|
<Box
|
|||
|
|
sx={{
|
|||
|
|
display: 'flex',
|
|||
|
|
justifyContent: 'space-between',
|
|||
|
|
alignItems: 'center',
|
|||
|
|
flexWrap: 'wrap',
|
|||
|
|
gap: 2
|
|||
|
|
}}
|
|||
|
|
>
|
|||
|
|
<SearchBar
|
|||
|
|
searchQuery={searchQuery}
|
|||
|
|
searchField={searchField}
|
|||
|
|
onSearchQueryChange={value => {
|
|||
|
|
setSearchQuery(value);
|
|||
|
|
setPage(1);
|
|||
|
|
}}
|
|||
|
|
onSearchFieldChange={value => {
|
|||
|
|
setSearchField(value);
|
|||
|
|
setPage(1);
|
|||
|
|
}}
|
|||
|
|
onMoreFiltersClick={() => setFilterDialogOpen(true)}
|
|||
|
|
activeFilterCount={getActiveFilterCount()}
|
|||
|
|
/>
|
|||
|
|
<ActionBar
|
|||
|
|
batchEvaluating={batchEvaluating}
|
|||
|
|
onBatchEvaluate={handleBatchEvaluate}
|
|||
|
|
onImport={handleOpenImportDialog}
|
|||
|
|
onExport={handleOpenExportDialog}
|
|||
|
|
/>
|
|||
|
|
</Box>
|
|||
|
|
</Card>
|
|||
|
|
{selectedIds.length ? (
|
|||
|
|
<Box
|
|||
|
|
sx={{
|
|||
|
|
display: 'flex',
|
|||
|
|
justifyContent: 'space-between',
|
|||
|
|
alignItems: 'center',
|
|||
|
|
flexWrap: 'wrap',
|
|||
|
|
marginTop: '10px',
|
|||
|
|
gap: 2
|
|||
|
|
}}
|
|||
|
|
>
|
|||
|
|
<Typography variant="body1" color="text.secondary">
|
|||
|
|
{t('datasets.selected', {
|
|||
|
|
count: selectedIds.length
|
|||
|
|
})}
|
|||
|
|
</Typography>
|
|||
|
|
<Button
|
|||
|
|
variant="outlined"
|
|||
|
|
color="error"
|
|||
|
|
startIcon={<DeleteIcon />}
|
|||
|
|
sx={{ borderRadius: 2 }}
|
|||
|
|
onClick={handleBatchDeleteDataset}
|
|||
|
|
>
|
|||
|
|
{t('datasets.batchDelete')}
|
|||
|
|
</Button>
|
|||
|
|
</Box>
|
|||
|
|
) : (
|
|||
|
|
''
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
<DatasetList
|
|||
|
|
datasets={datasets.data || []}
|
|||
|
|
onViewDetails={handleViewDetails}
|
|||
|
|
onDelete={handleOpenDeleteDialog}
|
|||
|
|
onEvaluate={handleEvaluateDataset}
|
|||
|
|
page={page}
|
|||
|
|
rowsPerPage={rowsPerPage}
|
|||
|
|
onPageChange={handlePageChange}
|
|||
|
|
onRowsPerPageChange={handleRowsPerPageChange}
|
|||
|
|
total={datasets.total || 0}
|
|||
|
|
selectedIds={selectedIds}
|
|||
|
|
onSelectAll={handleSelectAll}
|
|||
|
|
onSelectItem={handleSelectItem}
|
|||
|
|
evaluatingIds={evaluatingIds}
|
|||
|
|
loading={loading}
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
<DeleteConfirmDialog
|
|||
|
|
open={deleteDialog.open}
|
|||
|
|
datasets={deleteDialog.datasets || []}
|
|||
|
|
onClose={handleCloseDeleteDialog}
|
|||
|
|
onConfirm={handleDeleteConfirm}
|
|||
|
|
batch={deleteDialog.batch}
|
|||
|
|
progress={deleteProgress}
|
|||
|
|
deleting={deleteDialog.deleting}
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
<FilterDialog
|
|||
|
|
open={filterDialogOpen}
|
|||
|
|
onClose={handleCloseFilterDialog}
|
|||
|
|
filterConfirmed={filterConfirmed}
|
|||
|
|
filterHasCot={filterHasCot}
|
|||
|
|
filterIsDistill={filterIsDistill}
|
|||
|
|
filterScoreRange={filterScoreRange}
|
|||
|
|
filterCustomTag={filterCustomTag}
|
|||
|
|
filterNoteKeyword={filterNoteKeyword}
|
|||
|
|
filterChunkName={filterChunkName}
|
|||
|
|
availableTags={availableTags}
|
|||
|
|
onFilterConfirmedChange={setFilterConfirmed}
|
|||
|
|
onFilterHasCotChange={setFilterHasCot}
|
|||
|
|
onFilterIsDistillChange={setFilterIsDistill}
|
|||
|
|
onFilterScoreRangeChange={setFilterScoreRange}
|
|||
|
|
onFilterCustomTagChange={setFilterCustomTag}
|
|||
|
|
onFilterNoteKeywordChange={setFilterNoteKeyword}
|
|||
|
|
onFilterChunkNameChange={setFilterChunkName}
|
|||
|
|
onResetFilters={handleResetFilters}
|
|||
|
|
onApplyFilters={handleApplyFilters}
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
<ExportDatasetDialog
|
|||
|
|
open={exportDialog.open}
|
|||
|
|
onClose={handleCloseExportDialog}
|
|||
|
|
onExport={handleExportDatasets}
|
|||
|
|
projectId={projectId}
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
<ImportDatasetDialog
|
|||
|
|
open={importDialog.open}
|
|||
|
|
onClose={handleCloseImportDialog}
|
|||
|
|
onImportSuccess={handleImportSuccess}
|
|||
|
|
projectId={projectId}
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
{/* 导出进度对话框 */}
|
|||
|
|
<ExportProgressDialog open={exportProgress.show} progress={exportProgress} />
|
|||
|
|
</Container>
|
|||
|
|
);
|
|||
|
|
}
|