Files
YG-Datasets/easy-dataset-main/components/text-split/FileUploader.js

361 lines
11 KiB
JavaScript
Raw Normal View History

2026-03-17 14:36:31 +08:00
'use client';
import { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { Paper, Grid } from '@mui/material';
import { useTheme } from '@mui/material/styles';
import { useAtomValue } from 'jotai/index';
import { selectedModelInfoAtom } from '@/lib/store';
import UploadArea from './components/UploadArea';
import FileList from './components/FileList';
import DeleteConfirmDialog from './components/DeleteConfirmDialog';
import PdfProcessingDialog from './components/PdfProcessingDialog';
import DomainTreeActionDialog from './components/DomainTreeActionDialog';
import FileLoadingProgress from './components/FileLoadingProgress';
import { fileApi, taskApi } from '@/lib/api';
import { getContent, checkMaxSize, checkInvalidFiles, getvalidFiles } from '@/lib/file/file-process';
import { toast } from 'sonner';
export default function FileUploader({
projectId,
onUploadSuccess,
onFileDeleted,
sendToPages,
setPdfStrategy,
pdfStrategy,
selectedViosnModel,
setSelectedViosnModel,
setPageLoading,
taskFileProcessing,
fileTask
}) {
const theme = useTheme();
const { t } = useTranslation();
const [files, setFiles] = useState([]);
const [pdfFiles, setPdfFiles] = useState([]);
const [uploadedFiles, setUploadedFiles] = useState({});
const selectedModelInfo = useAtomValue(selectedModelInfoAtom);
const [uploading, setUploading] = useState(false);
const [loading, setLoading] = useState(false);
const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false);
const [pdfProcessConfirmOpen, setpdfProcessConfirmOpen] = useState(false);
const [fileToDelete, setFileToDelete] = useState({});
const [domainTreeActionOpen, setDomainTreeActionOpen] = useState(false);
const [domainTreeAction, setDomainTreeAction] = useState('');
const [isFirstUpload, setIsFirstUpload] = useState(false);
const [pendingAction, setPendingAction] = useState(null);
const [taskSettings, setTaskSettings] = useState(null);
const [visionModels, setVisionModels] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const [pageSize] = useState(10);
const [searchFileName, setSearchFileName] = useState('');
useEffect(() => {
fetchUploadedFiles();
}, [currentPage, searchFileName]);
/**
* 处理 PDF 处理方式选择
*/
const handleRadioChange = event => {
const modelId = event.target.selectedVision;
setPdfStrategy(event.target.value);
if (event.target.value === 'mineru') {
toast.success(t('textSplit.mineruSelected'));
} else if (event.target.value === 'mineru-local') {
toast.success(t('textSplit.mineruLocalSelected'));
} else if (event.target.value === 'vision') {
const model = visionModels.find(item => item.id === modelId);
toast.success(
t('textSplit.customVisionModelSelected', {
name: model.modelName,
provider: model.projectName
})
);
} else {
toast.success(t('textSplit.defaultSelected'));
}
};
/**
* 获取上传的文件列表
* @param {*} page
* @param {*} size
* @param {*} fileName
*/
const fetchUploadedFiles = async (page = currentPage, size = pageSize, fileName = searchFileName) => {
try {
setLoading(true);
const data = await fileApi.getFiles({ projectId, page, size, fileName, t });
setUploadedFiles(data);
setIsFirstUpload(data.total === 0);
const taskData = await taskApi.getProjectTasks(projectId);
setTaskSettings(taskData);
//使用Jotai会出现数据获取的延迟导致这里模型获取不到改用localStorage获取模型信息
const model = JSON.parse(localStorage.getItem('modelConfigList'));
//过滤出视觉模型
const visionItems = model.filter(item => item.type === 'vision');
//先默认选择第一个配置的视觉模型
if (visionItems.length > 0) {
setSelectedViosnModel(visionItems[0].id);
}
setVisionModels(visionItems);
} catch (error) {
toast.error(error.message);
} finally {
setLoading(false);
}
};
/**
* 处理文件选择
*/
const handleFileSelect = event => {
const selectedFiles = Array.from(event.target.files);
checkMaxSize(selectedFiles);
checkInvalidFiles(selectedFiles);
const validFiles = getvalidFiles(selectedFiles);
if (validFiles.length > 0) {
setFiles(prev => [...prev, ...validFiles]);
}
const hasPdfFiles = selectedFiles.filter(file => file.name.endsWith('.pdf'));
if (hasPdfFiles.length > 0) {
setpdfProcessConfirmOpen(true);
setPdfFiles(hasPdfFiles);
}
};
/**
* 从待上传文件列表中移除文件
*/
const removeFile = index => {
const fileToRemove = files[index];
setFiles(prev => prev.filter((_, i) => i !== index));
if (fileToRemove && fileToRemove.name.toLowerCase().endsWith('.pdf')) {
setPdfFiles(prevPdfFiles => prevPdfFiles.filter(pdfFile => pdfFile.name !== fileToRemove.name));
}
};
/**
* 上传文件
*/
const uploadFiles = async () => {
if (files.length === 0) return;
// 如果是第一次上传,直接走默认逻辑
if (isFirstUpload) {
handleStartUpload('rebuild');
return;
}
// 否则打开领域树操作选择对话框
setDomainTreeAction('upload');
setPendingAction({ type: 'upload' });
setDomainTreeActionOpen(true);
};
/**
* 处理领域树操作选择
*/
const handleDomainTreeAction = action => {
setDomainTreeActionOpen(false);
// 执行挂起的操作
if (pendingAction && pendingAction.type === 'upload') {
handleStartUpload(action);
} else if (pendingAction && pendingAction.type === 'delete') {
handleDeleteFile(action);
}
// 清除挂起的操作
setPendingAction(null);
};
/**
* 开始上传文件
*/
const handleStartUpload = async domainTreeActionType => {
setUploading(true);
try {
const uploadedFileInfos = [];
for (const file of files) {
const { fileContent, fileName } = await getContent(file);
const data = await fileApi.uploadFile({ file, projectId, fileContent, fileName, t });
uploadedFileInfos.push({ fileName: data.fileName, fileId: data.fileId });
}
toast.success(t('textSplit.uploadSuccess', { count: files.length }));
setFiles([]);
setCurrentPage(1);
await fetchUploadedFiles();
if (onUploadSuccess) {
await onUploadSuccess(uploadedFileInfos, pdfFiles, domainTreeActionType);
}
} catch (err) {
toast.error(err.message || t('textSplit.uploadFailed'));
} finally {
setUploading(false);
}
};
// 打开删除确认对话框
const openDeleteConfirm = (fileId, fileName) => {
setFileToDelete({ fileId, fileName });
setDeleteConfirmOpen(true);
};
// 关闭删除确认对话框
const closeDeleteConfirm = () => {
setDeleteConfirmOpen(false);
setFileToDelete(null);
};
// 删除文件前确认领域树操作
const confirmDeleteFile = () => {
setDeleteConfirmOpen(false);
// 如果没有其他文件了(删除后会变为空),直接删除
if (uploadedFiles.total <= 1) {
handleDeleteFile('keep');
return;
}
// 否则打开领域树操作选择对话框
setDomainTreeAction('delete');
setPendingAction({ type: 'delete' });
setDomainTreeActionOpen(true);
};
// 处理删除文件
const handleDeleteFile = async domainTreeActionType => {
if (!fileToDelete) return;
try {
setLoading(true);
closeDeleteConfirm();
await fileApi.deleteFile({
fileToDelete,
projectId,
domainTreeActionType,
modelInfo: selectedModelInfo || {},
t
});
await fetchUploadedFiles();
if (onFileDeleted) {
const filesLength = uploadedFiles.total;
onFileDeleted(fileToDelete, filesLength);
}
if (uploadedFiles.data && uploadedFiles.data.length <= 1 && currentPage > 1) {
setCurrentPage(1);
}
toast.success(t('textSplit.deleteSuccess', { fileName: fileToDelete.fileName }));
} catch (error) {
console.error('Error deleting file:', error);
toast.error(error.message);
} finally {
setLoading(false);
setFileToDelete(null);
}
};
return (
<Paper
elevation={0}
sx={{
p: 3,
mb: 3,
border: `1px solid ${theme.palette.divider}`,
borderRadius: 2
}}
>
{taskFileProcessing ? (
<FileLoadingProgress fileTask={fileTask} />
) : (
<>
<Grid container spacing={3}>
{/* 左侧:上传文件区域 */}
<Grid item xs={10} md={5} sx={{ maxWidth: '100%', width: '100%' }}>
<UploadArea
theme={theme}
files={files}
uploading={uploading}
uploadedFiles={uploadedFiles}
onFileSelect={handleFileSelect}
onRemoveFile={removeFile}
onUpload={uploadFiles}
selectedModel={selectedModelInfo}
/>
</Grid>
{/* 右侧:已上传文件列表 */}
<Grid item xs={14} md={7} sx={{ maxWidth: '100%', width: '100%' }}>
<FileList
theme={theme}
files={uploadedFiles}
loading={loading}
setPageLoading={setPageLoading}
sendToFileUploader={array => sendToPages(array)}
onDeleteFile={openDeleteConfirm}
projectId={projectId}
currentPage={currentPage}
onPageChange={(page, fileName) => {
if (fileName !== undefined) {
// 搜索时更新搜索关键词和页码
setSearchFileName(fileName);
setCurrentPage(page);
} else {
// 翻页时只更新页码
setCurrentPage(page);
}
}}
onRefresh={fetchUploadedFiles} // 传递刷新函数
/>
</Grid>
</Grid>
<DeleteConfirmDialog
open={deleteConfirmOpen}
fileName={fileToDelete?.fileName}
onClose={closeDeleteConfirm}
onConfirm={confirmDeleteFile}
/>
{/* 领域树操作选择对话框 */}
<DomainTreeActionDialog
open={domainTreeActionOpen}
onClose={() => setDomainTreeActionOpen(false)}
onConfirm={handleDomainTreeAction}
isFirstUpload={isFirstUpload}
action={domainTreeAction}
/>
{/* 检测到pdf的处理框 */}
<PdfProcessingDialog
open={pdfProcessConfirmOpen}
onClose={() => setpdfProcessConfirmOpen(false)}
onRadioChange={handleRadioChange}
value={pdfStrategy}
projectId={projectId}
taskSettings={taskSettings}
visionModels={visionModels}
selectedViosnModel={selectedViosnModel}
setSelectedViosnModel={setSelectedViosnModel}
/>
</>
)}
</Paper>
);
}