401 lines
13 KiB
JavaScript
401 lines
13 KiB
JavaScript
'use client';
|
||
|
||
import { Box, Typography, Checkbox, Button, Select, MenuItem, Tooltip, Menu, IconButton, Badge } from '@mui/material';
|
||
import QuizIcon from '@mui/icons-material/Quiz';
|
||
import DownloadIcon from '@mui/icons-material/Download';
|
||
import AutoFixHighIcon from '@mui/icons-material/AutoFixHigh';
|
||
import CleaningServicesIcon from '@mui/icons-material/CleaningServices';
|
||
import AssessmentIcon from '@mui/icons-material/Assessment';
|
||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||
import EditIcon from '@mui/icons-material/Edit';
|
||
import DeleteIcon from '@mui/icons-material/Delete';
|
||
import FilterListIcon from '@mui/icons-material/FilterList';
|
||
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
|
||
import axios from 'axios';
|
||
import { toast } from 'sonner';
|
||
import { useTranslation } from 'react-i18next';
|
||
import { useState } from 'react';
|
||
import ChunkFilterDialog from './ChunkFilterDialog';
|
||
|
||
export default function ChunkListHeader({
|
||
projectId,
|
||
totalChunks,
|
||
selectedChunks,
|
||
onSelectAll,
|
||
onBatchGenerateQuestions,
|
||
onBatchEditChunks,
|
||
onBatchDeleteChunks,
|
||
questionFilter,
|
||
setQuestionFilter,
|
||
chunks = [], // 添加chunks参数,用于导出文本块
|
||
selectedModel = {},
|
||
onFilterChange = null,
|
||
activeFilterCount = 0
|
||
}) {
|
||
const { t, i18n } = useTranslation();
|
||
|
||
// 添加更多菜单的状态和锚点
|
||
const [moreMenuAnchorEl, setMoreMenuAnchorEl] = useState(null);
|
||
const isMoreMenuOpen = Boolean(moreMenuAnchorEl);
|
||
|
||
// 添加筛选对话框状态
|
||
const [filterDialogOpen, setFilterDialogOpen] = useState(false);
|
||
|
||
// 自动任务菜单状态
|
||
const [autoTasksMenuAnchorEl, setAutoTasksMenuAnchorEl] = useState(null);
|
||
const isAutoTasksMenuOpen = Boolean(autoTasksMenuAnchorEl);
|
||
|
||
const handleAutoTasksClick = event => {
|
||
setAutoTasksMenuAnchorEl(event.currentTarget);
|
||
};
|
||
|
||
const handleAutoTasksClose = () => {
|
||
setAutoTasksMenuAnchorEl(null);
|
||
};
|
||
|
||
// 打开更多菜单
|
||
const handleMoreMenuClick = event => {
|
||
setMoreMenuAnchorEl(event.currentTarget);
|
||
};
|
||
|
||
// 关闭更多菜单
|
||
const handleMoreMenuClose = () => {
|
||
setMoreMenuAnchorEl(null);
|
||
};
|
||
|
||
// 处理批量编辑,关闭菜单并调用原有函数
|
||
const handleBatchEdit = () => {
|
||
handleMoreMenuClose();
|
||
onBatchEditChunks();
|
||
};
|
||
|
||
// 处理批量删除,关闭菜单并调用原有函数
|
||
const handleBatchDelete = () => {
|
||
handleMoreMenuClose();
|
||
onBatchDeleteChunks();
|
||
};
|
||
|
||
// 处理导出文本块,关闭菜单并调用原有函数
|
||
const handleExport = () => {
|
||
handleMoreMenuClose();
|
||
handleExportChunks();
|
||
};
|
||
|
||
// 创建自动提取问题任务
|
||
const handleCreateAutoQuestionTask = async () => {
|
||
if (!projectId || !selectedModel?.id) {
|
||
toast.error(t('textSplit.selectModelFirst', { defaultValue: '请先选择模型' }));
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// 调用创建任务接口
|
||
const response = await axios.post(`/api/projects/${projectId}/tasks`, {
|
||
taskType: 'question-generation',
|
||
modelInfo: selectedModel,
|
||
language: i18n.language,
|
||
detail: '批量生成问题任务'
|
||
});
|
||
|
||
if (response.data?.code === 0) {
|
||
toast.success(t('tasks.createSuccess', { defaultValue: '后台任务已创建,系统将自动处理未生成问题的文本块' }));
|
||
} else {
|
||
toast.error(t('tasks.createFailed', { defaultValue: '创建任务失败' }) + ': ' + response.data?.message);
|
||
}
|
||
} catch (error) {
|
||
console.error('创建自动提取问题任务失败:', error);
|
||
toast.error(t('tasks.createFailed', { defaultValue: '创建任务失败' }) + ': ' + error.message);
|
||
}
|
||
};
|
||
|
||
// 创建自动数据清洗任务
|
||
const handleCreateAutoDataCleaningTask = async () => {
|
||
if (!projectId || !selectedModel?.id) {
|
||
toast.error(t('textSplit.selectModelFirst', { defaultValue: '请先选择模型' }));
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// 调用创建任务接口
|
||
const response = await axios.post(`/api/projects/${projectId}/tasks`, {
|
||
taskType: 'data-cleaning',
|
||
modelInfo: selectedModel,
|
||
language: i18n.language,
|
||
detail: '批量数据清洗任务'
|
||
});
|
||
|
||
if (response.data?.code === 0) {
|
||
toast.success(
|
||
t('tasks.createSuccess', { defaultValue: '后台任务已创建,系统将自动处理所有文本块进行数据清洗' })
|
||
);
|
||
} else {
|
||
toast.error(t('tasks.createFailed', { defaultValue: '创建任务失败' }) + ': ' + response.data?.message);
|
||
}
|
||
} catch (error) {
|
||
console.error('创建自动数据清洗任务失败:', error);
|
||
toast.error(t('tasks.createFailed', { defaultValue: '创建任务失败' }) + ': ' + error.message);
|
||
}
|
||
};
|
||
|
||
// 创建自动生成评估数据集任务
|
||
const handleCreateAutoEvalGenerationTask = async () => {
|
||
if (!projectId || !selectedModel?.id) {
|
||
toast.error(t('textSplit.selectModelFirst', { defaultValue: '请先选择模型' }));
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// 调用创建任务接口
|
||
const response = await axios.post(`/api/projects/${projectId}/tasks`, {
|
||
taskType: 'eval-generation',
|
||
modelInfo: selectedModel,
|
||
language: i18n.language,
|
||
detail: '批量生成评估数据集任务'
|
||
});
|
||
|
||
if (response.data?.code === 0) {
|
||
toast.success(
|
||
t('tasks.createSuccess', {
|
||
defaultValue: '后台任务已创建,系统将自动为所有未生成评估题目的文本块生成评估数据集'
|
||
})
|
||
);
|
||
} else {
|
||
toast.error(t('tasks.createFailed', { defaultValue: '创建任务失败' }) + ': ' + response.data?.message);
|
||
}
|
||
} catch (error) {
|
||
console.error('创建自动生成评估数据集任务失败:', error);
|
||
toast.error(t('tasks.createFailed', { defaultValue: '创建任务失败' }) + ': ' + error.message);
|
||
}
|
||
};
|
||
|
||
// 导出文本块为JSON文件的函数
|
||
const handleExportChunks = () => {
|
||
if (!chunks || chunks.length === 0) return;
|
||
|
||
// 创建要导出的数据对象
|
||
const exportData = chunks.map(chunk => ({
|
||
name: chunk.name,
|
||
projectId: chunk.projectId,
|
||
fileName: chunk.fileName,
|
||
content: chunk.content,
|
||
summary: chunk.summary,
|
||
size: chunk.size
|
||
}));
|
||
|
||
// 将数据转换为JSON字符串
|
||
const jsonString = JSON.stringify(exportData, null, 2);
|
||
|
||
// 创建Blob对象
|
||
const blob = new Blob([jsonString], { type: 'application/json' });
|
||
|
||
// 创建下载链接
|
||
const url = URL.createObjectURL(blob);
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = `text-chunks-export-${new Date().toISOString().split('T')[0]}.json`;
|
||
|
||
// 触发下载
|
||
document.body.appendChild(a);
|
||
a.click();
|
||
|
||
// 清理
|
||
document.body.removeChild(a);
|
||
URL.revokeObjectURL(url);
|
||
};
|
||
|
||
return (
|
||
<Box
|
||
sx={{
|
||
display: 'flex',
|
||
flexDirection: { xs: 'column', md: 'row' },
|
||
justifyContent: 'space-between',
|
||
alignItems: { xs: 'flex-start', md: 'center' },
|
||
gap: 2,
|
||
mb: 3
|
||
}}
|
||
>
|
||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||
<Checkbox
|
||
checked={selectedChunks.length === totalChunks}
|
||
indeterminate={selectedChunks.length > 0 && selectedChunks.length < totalChunks}
|
||
onChange={onSelectAll}
|
||
/>
|
||
<Typography variant="body1">{t('textSplit.selectedCount', { count: selectedChunks.length })}</Typography>
|
||
</Box>
|
||
|
||
<Box
|
||
sx={{
|
||
display: 'flex',
|
||
flexDirection: { xs: 'column', sm: 'row' },
|
||
alignItems: { xs: 'flex-start', sm: 'center' },
|
||
flexWrap: 'wrap',
|
||
gap: 1.5,
|
||
width: { xs: '100%', md: 'auto' }
|
||
}}
|
||
>
|
||
{/* 更多筛选按钮 */}
|
||
<Tooltip title={t('datasets.moreFilters', { defaultValue: '更多筛选' })}>
|
||
<Badge badgeContent={activeFilterCount} color="error" overlap="circular">
|
||
<Button
|
||
variant="outlined"
|
||
startIcon={<FilterListIcon />}
|
||
onClick={() => setFilterDialogOpen(true)}
|
||
size="small"
|
||
sx={{ borderRadius: 1 }}
|
||
>
|
||
{t('datasets.moreFilters', { defaultValue: '更多筛选' })}
|
||
</Button>
|
||
</Badge>
|
||
</Tooltip>
|
||
|
||
<Box
|
||
sx={{
|
||
display: 'flex',
|
||
flexWrap: 'wrap',
|
||
gap: 1.5,
|
||
mt: { xs: 1, sm: 0 },
|
||
width: { xs: '100%', sm: 'auto' }
|
||
}}
|
||
>
|
||
<Button
|
||
variant="contained"
|
||
color="primary"
|
||
startIcon={<QuizIcon />}
|
||
disabled={selectedChunks.length === 0}
|
||
onClick={onBatchGenerateQuestions}
|
||
size="medium"
|
||
sx={{ minWidth: { xs: '48%', sm: 'auto' } }}
|
||
>
|
||
{t('textSplit.batchGenerateQuestions')}
|
||
</Button>
|
||
|
||
{/* 自动任务下拉菜单 */}
|
||
<Button
|
||
variant="outlined"
|
||
color="secondary"
|
||
startIcon={<AutoFixHighIcon />}
|
||
endIcon={<KeyboardArrowDownIcon />}
|
||
onClick={handleAutoTasksClick}
|
||
disabled={!projectId || !selectedModel?.id}
|
||
size="medium"
|
||
sx={{ minWidth: { xs: '48%', sm: 'auto' } }}
|
||
>
|
||
{t('textSplit.autoTasks', { defaultValue: '自动任务' })}
|
||
</Button>
|
||
|
||
<Menu
|
||
anchorEl={autoTasksMenuAnchorEl}
|
||
open={isAutoTasksMenuOpen}
|
||
onClose={handleAutoTasksClose}
|
||
anchorOrigin={{
|
||
vertical: 'bottom',
|
||
horizontal: 'right'
|
||
}}
|
||
transformOrigin={{
|
||
vertical: 'top',
|
||
horizontal: 'right'
|
||
}}
|
||
>
|
||
<Tooltip
|
||
title={t('textSplit.autoGenerateQuestionsTip', {
|
||
defaultValue: '创建后台批量处理任务:自动查询待生成问题的文本块并提取问题'
|
||
})}
|
||
placement="left"
|
||
>
|
||
<MenuItem
|
||
onClick={() => {
|
||
handleCreateAutoQuestionTask();
|
||
handleAutoTasksClose();
|
||
}}
|
||
>
|
||
<QuizIcon fontSize="small" sx={{ mr: 1, color: 'secondary.main' }} />
|
||
{t('textSplit.autoGenerateQuestions')}
|
||
</MenuItem>
|
||
</Tooltip>
|
||
|
||
<Tooltip
|
||
title={t('textSplit.autoEvalGenerationTip', {
|
||
defaultValue: '创建后台批量处理任务:自动为所有未生成评估题目的文本块生成评估数据集'
|
||
})}
|
||
placement="left"
|
||
>
|
||
<MenuItem
|
||
onClick={() => {
|
||
handleCreateAutoEvalGenerationTask();
|
||
handleAutoTasksClose();
|
||
}}
|
||
>
|
||
<AssessmentIcon fontSize="small" sx={{ mr: 1, color: 'secondary.main' }} />
|
||
{t('textSplit.autoEvalGeneration', { defaultValue: '自动生成评估集' })}
|
||
</MenuItem>
|
||
</Tooltip>
|
||
|
||
<Tooltip
|
||
title={t('textSplit.autoDataCleaningTip', {
|
||
defaultValue: '创建后台批量处理任务:自动对所有文本块进行数据清洗'
|
||
})}
|
||
placement="left"
|
||
>
|
||
<MenuItem
|
||
onClick={() => {
|
||
handleCreateAutoDataCleaningTask();
|
||
handleAutoTasksClose();
|
||
}}
|
||
>
|
||
<CleaningServicesIcon fontSize="small" sx={{ mr: 1, color: 'success.main' }} />
|
||
{t('textSplit.autoDataCleaning', { defaultValue: '自动数据清洗' })}
|
||
</MenuItem>
|
||
</Tooltip>
|
||
</Menu>
|
||
|
||
{/* 更多菜单按钮 */}
|
||
<Tooltip title={t('common.more', { defaultValue: '更多操作' })}>
|
||
<IconButton
|
||
onClick={handleMoreMenuClick}
|
||
color="primary"
|
||
size="medium"
|
||
sx={{
|
||
border: '1px solid',
|
||
borderColor: 'divider'
|
||
}}
|
||
>
|
||
<MoreVertIcon />
|
||
</IconButton>
|
||
</Tooltip>
|
||
|
||
{/* 更多操作下拉菜单 */}
|
||
<Menu
|
||
anchorEl={moreMenuAnchorEl}
|
||
open={isMoreMenuOpen}
|
||
onClose={handleMoreMenuClose}
|
||
anchorOrigin={{
|
||
vertical: 'bottom',
|
||
horizontal: 'right'
|
||
}}
|
||
transformOrigin={{
|
||
vertical: 'top',
|
||
horizontal: 'right'
|
||
}}
|
||
>
|
||
<MenuItem onClick={handleBatchEdit} disabled={selectedChunks.length === 0}>
|
||
<EditIcon fontSize="small" sx={{ mr: 1 }} />
|
||
{t('batchEdit.batchEdit', { defaultValue: '批量编辑' })}
|
||
</MenuItem>
|
||
<MenuItem onClick={handleBatchDelete} disabled={selectedChunks.length === 0}>
|
||
<DeleteIcon fontSize="small" sx={{ mr: 1, color: 'error.main' }} />
|
||
{t('textSplit.batchDeleteChunks', { defaultValue: '批量删除' })}
|
||
</MenuItem>
|
||
<MenuItem onClick={handleExport} disabled={chunks.length === 0}>
|
||
<DownloadIcon fontSize="small" sx={{ mr: 1 }} />
|
||
{t('textSplit.exportChunks', { defaultValue: '导出文本块' })}
|
||
</MenuItem>
|
||
</Menu>
|
||
</Box>
|
||
</Box>
|
||
|
||
{/* 筛选对话框 */}
|
||
<ChunkFilterDialog open={filterDialogOpen} onClose={() => setFilterDialogOpen(false)} onApply={onFilterChange} />
|
||
</Box>
|
||
);
|
||
}
|