'use client'; import axios from 'axios'; import { useState, useEffect, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { Container, Box, Tabs, Tab, IconButton, Collapse, Dialog, DialogContent, DialogTitle, Typography, LinearProgress, CircularProgress } from '@mui/material'; import { useTheme } from '@mui/material/styles'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import ExpandLessIcon from '@mui/icons-material/ExpandLess'; import FullscreenIcon from '@mui/icons-material/Fullscreen'; import CloseIcon from '@mui/icons-material/Close'; import FileUploader from '@/components/text-split/FileUploader'; import FileList from '@/components/text-split/components/FileList'; import DeleteConfirmDialog from '@/components/text-split/components/DeleteConfirmDialog'; import PdfSettings from '@/components/text-split/PdfSettings'; import ChunkList from '@/components/text-split/ChunkList'; import DomainAnalysis from '@/components/text-split/DomainAnalysis'; import useTaskSettings from '@/hooks/useTaskSettings'; import { useAtomValue } from 'jotai/index'; import { selectedModelInfoAtom } from '@/lib/store'; import useChunks from './useChunks'; import useQuestionGeneration from './useQuestionGeneration'; import useDataCleaning from './useDataCleaning'; import useEvalGeneration from './useEvalGeneration'; import useFileProcessing from './useFileProcessing'; import useFileProcessingStatus from '@/hooks/useFileProcessingStatus'; import { toast } from 'sonner'; export default function TextSplitPage({ params }) { const { t } = useTranslation(); const theme = useTheme(); const { projectId } = params; const [activeTab, setActiveTab] = useState(0); const [renderedTab, setRenderedTab] = useState(0); const [tabSwitching, setTabSwitching] = useState(false); const tabSwitchTimerRef = useRef(null); const { taskSettings } = useTaskSettings(projectId); const [pdfStrategy, setPdfStrategy] = useState('default'); const [questionFilter, setQuestionFilter] = useState('all'); // 'all', 'generated', 'ungenerated' const [selectedViosnModel, setSelectedViosnModel] = useState(''); const selectedModelInfo = useAtomValue(selectedModelInfoAtom); const { taskFileProcessing, task } = useFileProcessingStatus(); const [currentPage, setCurrentPage] = useState(1); const [uploadedFiles, setUploadedFiles] = useState({ data: [], total: 0 }); const [searchFileName, setSearchFileName] = useState(''); const [showLoadingBar, setShowLoadingBar] = useState(false); // 娑撳﹣绱堕崠鍝勭厵閻ㄥ嫬鐫嶅鈧?閹舵ê褰旈悩鑸碘偓? const [uploaderExpanded, setUploaderExpanded] = useState(true); // 閺傚洨灏為崚妤勩€?FileList)鐏炴洜銇氱€电鐦藉鍡欏Ц閹? const [fileListDialogOpen, setFileListDialogOpen] = useState(false); // 娴h法鏁ら懛顏勭暰娑斿“ooks const { chunks, tocData, loading, fetchChunks, handleDeleteChunk, handleEditChunk, updateChunks, setLoading } = useChunks(projectId, questionFilter); // 閼惧嘲褰囬弬鍥︽閸掓銆? const fetchUploadedFiles = async (page = currentPage, fileName = searchFileName) => { try { setLoading(true); const params = new URLSearchParams({ page: page.toString(), size: '10' }); if (fileName && fileName.trim()) { params.append('fileName', fileName.trim()); } const response = await axios.get(`/api/projects/${projectId}/files?${params}`); setUploadedFiles(response.data); } catch (error) { console.error('Error fetching files:', error); toast.error(error.message || '閼惧嘲褰囬弬鍥︽閸掓銆冩径杈Е'); } finally { setLoading(false); } }; // 閸掔娀娅庨弬鍥︽绾喛顓荤€电鐦藉鍡欏Ц閹? const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false); const [fileToDelete, setFileToDelete] = useState(null); // 閹垫挸绱戦崚鐘绘珟绾喛顓荤€电鐦藉? const openDeleteConfirm = (fileId, fileName) => { setFileToDelete({ fileId, fileName }); setDeleteConfirmOpen(true); }; // 閸忔娊妫撮崚鐘绘珟绾喛顓荤€电鐦藉? const closeDeleteConfirm = () => { setDeleteConfirmOpen(false); setFileToDelete(null); }; // 绾喛顓婚崚鐘绘珟閺傚洣娆? const confirmDeleteFile = async () => { if (!fileToDelete) return; try { setLoading(true); closeDeleteConfirm(); await axios.delete(`/api/projects/${projectId}/files/${fileToDelete.fileId}`); await fetchUploadedFiles(); fetchChunks(); toast.success( t('textSplit.deleteSuccess', { fileName: fileToDelete.fileName }) || `删除 ${fileToDelete.fileName} 成功` ); } catch (error) { console.error('删除文件出错:', error); toast.error(error.message || '删除文件失败'); } finally { setLoading(false); setFileToDelete(null); } }; const { handleGenerateQuestions } = useQuestionGeneration(projectId, taskSettings); const { handleDataCleaning } = useDataCleaning(projectId, taskSettings); const { handleGenerateEvalQuestions } = useEvalGeneration(projectId); const { handleFileProcessing } = useFileProcessing(projectId); // 文本块数据刷新:初始化 + 文件处理任务状态变化 useEffect(() => { fetchChunks('all'); }, [fetchChunks, taskFileProcessing]); // 文件列表刷新:文件分页、搜索关键词变化时触发 useEffect(() => { fetchUploadedFiles(currentPage, searchFileName); }, [projectId, currentPage, searchFileName]); useEffect(() => { let timerId; if (loading) { timerId = setTimeout(() => setShowLoadingBar(true), 180); } else { setShowLoadingBar(false); } return () => { if (timerId) clearTimeout(timerId); }; }, [loading]); useEffect(() => { return () => { if (tabSwitchTimerRef.current) { clearTimeout(tabSwitchTimerRef.current); } }; }, []); const handleTabChange = (event, newValue) => { if (newValue === activeTab) return; setActiveTab(newValue); setTabSwitching(true); if (tabSwitchTimerRef.current) { clearTimeout(tabSwitchTimerRef.current); } const switchContent = () => { setRenderedTab(newValue); tabSwitchTimerRef.current = null; if (typeof window !== 'undefined') { window.requestAnimationFrame(() => setTabSwitching(false)); } else { setTabSwitching(false); } }; if (typeof window !== 'undefined') { window.requestAnimationFrame(() => { tabSwitchTimerRef.current = setTimeout(switchContent, 80); }); } else { switchContent(); } }; /** * 鐎甸€涚瑐娴肩姴鎮楅惃鍕瀮娴犳儼绻樼悰灞筋槱閻? */ const handleUploadSuccess = async (fileNames, pdfFiles, domainTreeAction) => { try { await handleFileProcessing(fileNames, pdfStrategy, selectedViosnModel, domainTreeAction); location.reload(); } catch (error) { toast.error('File upload failed' + error.message || ''); } }; // 閸栧懓顥婇悽鐔稿灇闂傤噣顣介惃鍕槱閻炲棗鍤遍弫? const onGenerateQuestions = async chunkIds => { await handleGenerateQuestions(chunkIds, selectedModelInfo, fetchChunks); }; // 閸栧懓顥婇弫鐗堝祦濞撳懏绀傞惃鍕槱閻炲棗鍤遍弫? const onDataCleaning = async chunkIds => { await handleDataCleaning(chunkIds, selectedModelInfo, fetchChunks); }; // 閸栧懓顥婇悽鐔稿灇濞村鐦庢0妯兼窗閻ㄥ嫬顦╅悶鍡楀毐閺? const onGenerateEvalQuestions = async chunkId => { await handleGenerateEvalQuestions(chunkId, selectedModelInfo, () => { // 閹存劕濮涢崥搴″煕閺傛澘鍨悰? fetchChunks(); }); }; useEffect(() => { const url = new URL(window.location.href); if (questionFilter !== 'all') { url.searchParams.set('filter', questionFilter); } else { url.searchParams.delete('filter'); } window.history.replaceState({}, '', url); fetchChunks(questionFilter); }, [questionFilter]); const handleSelected = array => { if (array.length > 0) { axios.post(`/api/projects/${projectId}/chunks`, { array }).then(response => { updateChunks(response.data); }); } else { fetchChunks(); } }; return ( {/* 閺傚洣娆㈡稉濠佺炊缂佸嫪娆?*/} setUploaderExpanded(!uploaderExpanded)} sx={{ bgcolor: 'background.paper', boxShadow: 1, mr: uploaderExpanded ? 1 : 0 // 鐏炴洖绱戦弮鑸靛瘻闁筋喕绠i梻瀵告殌閻愬綊妫跨捄? }} size="small" > {uploaderExpanded ? : } {/* 閺傚洨灏為崚妤勩€冮幍鈺佺潔閹稿鎸抽敍灞肩矌閸︺劋绗傞柈銊ュ隘閸╃喎鐫嶅鈧弮鑸垫▔缁€?*/} {uploaderExpanded && ( setFileListDialogOpen(true)} sx={{ bgcolor: 'background.paper', boxShadow: 1 }} size="small" title={t('textSplit.expandFileList') || '扩展文件列表'} > )} {/* 閺嶅洨顒锋い?*/} {/* 閺呴缚鍏橀崚鍡楀閺嶅洨顒烽崘鍛啇 */} {tabSwitching ? ( {t('common.loading')} ) : ( <> {renderedTab === 0 && ( )} {renderedTab === 1 && } )} {/* 閸旂姾娴囨稉顓℃寢閻?*/} {showLoadingBar && ( {t('textSplit.loading')} )} {/* 婢跺嫮鎮婃稉顓℃寢閻?*/} {/* 閺佺増宓佸〒鍛鏉╂稑瀹抽拏娆戝 */} {/* 閺傚洣娆㈡径鍕倞鏉╂稑瀹抽拏娆戝 */} {/* 閺傚洣娆㈤崚鐘绘珟绾喛顓荤€电鐦藉?*/} {/* 閺傚洨灏為崚妤勩€冪€电鐦藉?*/} setFileListDialogOpen(false)} maxWidth="lg" fullWidth sx={{ '& .MuiDialog-paper': { bgcolor: 'background.default' } }} > {t('textSplit.fileList')} setFileListDialogOpen(false)} aria-label="close"> {/* 濮濄倕顦╂径宥囨暏 FileUploader 缂佸嫪娆㈡稉顓犳畱 FileList 闁劌鍨?*/} {/* 閺傚洣娆㈤崚妤勩€冮崘鍛啇 */} handleSelected(array)} onDeleteFile={(fileId, fileName) => openDeleteConfirm(fileId, fileName)} projectId={projectId} currentPage={currentPage} onPageChange={(page, fileName) => { if (fileName !== undefined) { // 閹兼粎鍌ㄩ弮鑸垫纯閺傜増鎮崇槐銏犲彠闁款喛鐦濋崪宀勩€夐惍? setSearchFileName(fileName); setCurrentPage(page); } else { // 缂堝銆夐弮璺哄涧閺囧瓨鏌婃い鐢电垳 setCurrentPage(page); } }} onRefresh={fetchUploadedFiles} // 娴肩娀鈧帒鍩涢弬鏉垮毐閺? isFullscreen={true} // 閸︺劌顕拠婵囶攱娑擃厾些闂勩倝鐝惔锕傛閸? /> ); }