import React, { useState, useEffect, useRef } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { Box, Typography, LinearProgress, Alert, Dialog, DialogTitle, DialogContent, DialogActions, Button, TextField, Breadcrumbs, Link, Stack, Card, CardContent, Divider, Chip, Tabs, Tab, } from '@mui/material'; import { type GridRowSelectionModel } from '@mui/x-data-grid'; import type { IKnowledge, IKnowledgeFile } from '@/interfaces/database/knowledge'; import type { IDocumentInfoFilter } from '@/interfaces/database/document'; import FileUploadDialog from '@/components/FileUploadDialog'; import KnowledgeInfoCard from './components/KnowledgeInfoCard'; import DocumentListComponent from './components/DocumentListComponent'; import FloatingActionButtons from './components/FloatingActionButtons'; import KnowledgeBreadcrumbs from './components/KnowledgeBreadcrumbs'; import KnowledgeGraphView from './components/KnowledgeGraphView'; import { useDocumentList, useDocumentOperations } from '@/hooks/document-hooks'; import { RunningStatus } from '@/constants/knowledge'; import { useKnowledgeDetail } from '@/hooks/knowledge-hooks'; import logger from '@/utils/logger'; function KnowledgeBaseDetail() { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); const { t } = useTranslation(); // 状态管理 const [error, setError] = useState(null); const [searchKeyword, setSearchKeyword] = useState(''); const [rowSelectionModel, setRowSelectionModel] = useState({ type: 'include', ids: new Set() }); const [uploadDialogOpen, setUploadDialogOpen] = useState(false); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [processDetailsDialogOpen, setProcessDetailsDialogOpen] = useState(false); const [selectedFileDetails, setSelectedFileDetails] = useState(null); // 标签页状态 const [currentTab, setCurrentTab] = useState(0); // 轮询相关状态 const pollingIntervalRef = useRef(null); const [isPolling, setIsPolling] = useState(false); // documents filter const [documentsFilter, setDocumentsFilter] = useState(undefined); const { knowledge: knowledgeBase, refresh: fetchKnowledgeDetail, loading, showKnowledgeGraph, knowledgeGraph } = useKnowledgeDetail(id || ''); // 使用新的document hooks const { documents: files, total, loading: filesLoading, error: filesError, currentPage, pageSize, refresh: refreshFiles, setKeywords, setCurrentPage, setPageSize, fetchDocuments, fetchDocumentsFilter, } = useDocumentList(id || ''); const { uploadDocuments, deleteDocuments, runDocuments, renameDocument, changeDocumentStatus, cancelRunDocuments, error: operationError, } = useDocumentOperations(); console.log('showKnowledgeGraph:', showKnowledgeGraph, knowledgeGraph); const refreshDetailData = async () => { await fetchKnowledgeDetail(); await refreshFiles(); }; // 删除文件 const handleDeleteFiles = async () => { try { await deleteDocuments(Array.from(rowSelectionModel.ids) as string[]); setDeleteDialogOpen(false); setRowSelectionModel({ type: 'include', ids: new Set() }); refreshFiles(); fetchKnowledgeDetail(); } catch (err: any) { setError(err.response?.data?.message || err.message || t('knowledgeDetails.deleteFileFailed')); } }; // 上传文件处理 const handleUploadFiles = async (uploadFiles: File[]) => { console.log(t('knowledgeDetails.uploadFiles'), uploadFiles); const kb_id = knowledgeBase?.id || ''; try { await uploadDocuments(kb_id, uploadFiles); refreshFiles(); fetchKnowledgeDetail(); } catch (err: any) { throw new Error(err.response?.data?.message || err.message || t('knowledgeDetails.uploadFileFailed')); } }; // 重新解析文件 const handleReparse = async (docIds: string[]) => { try { await runDocuments(docIds); refreshFiles(); // 刷新列表 startPolling(); // 开始轮询 } catch (err: any) { setError(err.response?.data?.message || err.message || t('knowledgeDetails.reparseFailed')); } }; // 重命名文件 const handleRename = async (fileId: string, newName: string) => { try { await renameDocument(fileId, newName); refreshFiles(); } catch (err: any) { setError(err.response?.data?.message || err.message || t('knowledgeDetails.renameFailed')); } }; // 更改文件状态 const handleChangeStatus = async (fileIds: string[], status: string) => { try { await changeDocumentStatus(fileIds, status); refreshFiles(); } catch (err: any) { setError(err.response?.data?.message || err.message || t('knowledgeDetails.changeStatusFailed')); } }; // 取消运行 const handleCancelRun = async (fileIds: string[]) => { try { await cancelRunDocuments(fileIds); refreshFiles(); } catch (err: any) { setError(err.response?.data?.message || err.message || t('knowledgeDetails.cancelRunFailed')); } }; // 查看详情 const handleViewDetails = (file: IKnowledgeFile) => { console.log(t('knowledgeDetails.viewDetails'), file); navigate(`/chunk/parsed-result?kb_id=${id}&doc_id=${file.id}`); }; // 查看解析详情 const handleViewProcessDetails = (file: IKnowledgeFile) => { console.log(t('knowledgeDetails.viewProcessDetails'), file); setSelectedFileDetails(file); setProcessDetailsDialogOpen(true); }; // 开始轮询 const startPolling = () => { if (pollingIntervalRef.current) { clearInterval(pollingIntervalRef.current); } setIsPolling(true); pollingIntervalRef.current = setInterval(() => { refreshFiles(); }, 3000); // 每3秒轮询一次 }; // 停止轮询 const stopPolling = () => { if (pollingIntervalRef.current) { clearInterval(pollingIntervalRef.current); pollingIntervalRef.current = null; } setIsPolling(false); }; // 检查是否有运行中的文档 const hasRunningDocuments = files.some(file => file.run === RunningStatus.RUNNING); // 根据运行状态自动管理轮询 useEffect(() => { if (hasRunningDocuments && !isPolling) { startPolling(); } else if (!hasRunningDocuments && isPolling) { stopPolling(); } }, [hasRunningDocuments, isPolling]); // 组件卸载时清理轮询 useEffect(() => { return () => { if (pollingIntervalRef.current) { clearInterval(pollingIntervalRef.current); } }; }, []); // 初始化数据 useEffect(() => { fetchDocumentsFilter().then(filterData => { if (filterData?.filter) { // setKeywords(filter.keywords || ''); logger.debug('filter:', filterData.filter); const filter = filterData.filter || {}; const showFilter = Object.keys(filter.run_status || {}).length > 0 || Object.keys(filter.suffix || {}).length > 0; if (showFilter) { setDocumentsFilter({ run_status: filterData.filter?.run_status || {}, suffix: filterData.filter?.suffix || {}, }); } } }); }, [id]); // 搜索文件 useEffect(() => { const timer = setTimeout(() => { setKeywords(searchKeyword); }, 500); return () => clearTimeout(timer); }, [searchKeyword, setKeywords]); // 合并错误状态 const combinedError = error || filesError || operationError; if (loading || !knowledgeBase) { return ( {t('common.loading')} ); } if (combinedError) { return ( {combinedError} ); } return ( {/* 面包屑导航 */} {/* 知识库信息卡片 */} {/* 标签页组件 - 仅在showKnowledgeGraph为true时显示 */} {showKnowledgeGraph ? ( setCurrentTab(newValue)} sx={{ borderBottom: 1, borderColor: 'divider' }} > {/* Document List 标签页内容 */} {currentTab === 0 && ( handleReparse(fileIds)} onDelete={(fileIds) => { setRowSelectionModel({ type: 'include', ids: new Set(fileIds) }); setDeleteDialogOpen(true); }} onUpload={() => setUploadDialogOpen(true)} onRefresh={() => { refreshFiles(); fetchKnowledgeDetail(); }} rowSelectionModel={rowSelectionModel} onRowSelectionModelChange={(newModel) => { setRowSelectionModel(newModel); }} total={total} page={currentPage} pageSize={pageSize} documentFilter={documentsFilter} onSelectedFilterChange={(filterBody) => { fetchDocuments({}, filterBody); }} onPageChange={setCurrentPage} onPageSizeChange={setPageSize} onRename={handleRename} onChangeStatus={handleChangeStatus} onCancelRun={handleCancelRun} onViewDetails={handleViewDetails} onViewProcessDetails={handleViewProcessDetails} /> )} {/* Graph 标签页内容 */} {currentTab === 1 && ( )} ) : ( /* 原有的文件列表组件 - 当showKnowledgeGraph为false时显示 */ handleReparse(fileIds)} onDelete={(fileIds) => { console.log(t('knowledgeDetails.deleteFiles'), fileIds); setRowSelectionModel({ type: 'include', ids: new Set(fileIds) }); setDeleteDialogOpen(true); }} onUpload={() => setUploadDialogOpen(true)} onRefresh={() => { refreshFiles(); fetchKnowledgeDetail(); }} rowSelectionModel={rowSelectionModel} onRowSelectionModelChange={(newModel) => { console.log(t('knowledgeDetails.newSelectionModel'), newModel); setRowSelectionModel(newModel); }} total={total} page={currentPage} pageSize={pageSize} documentFilter={documentsFilter} onSelectedFilterChange={(filterBody) => { fetchDocuments({}, filterBody); }} onPageChange={setCurrentPage} onPageSizeChange={setPageSize} onRename={handleRename} onChangeStatus={handleChangeStatus} onCancelRun={handleCancelRun} onViewDetails={handleViewDetails} onViewProcessDetails={handleViewProcessDetails} /> )} {/* 浮动操作按钮 */} navigate(`/knowledge/${id}/testing`)} onConfigClick={() => navigate(`/knowledge/${id}/setting`)} onOverviewClick={() => navigate(`/knowledge/${id}/overview`)} /> {/* 上传文件对话框 */} setUploadDialogOpen(false)} onUpload={handleUploadFiles} title={t('knowledgeDetails.uploadFilesToKnowledge')} acceptedFileTypes={['.pdf', '.docx', '.txt', '.md', '.png', '.jpg', '.jpeg', '.mp4', '.wav']} maxFileSize={100} maxFiles={10} /> {/* 删除确认对话框 */} setDeleteDialogOpen(false)}> {t('knowledgeDetails.confirmDelete')} {t('knowledgeDetails.confirmDeleteMessage', { count: rowSelectionModel.ids.size })} {/* 文档详情对话框 */} setProcessDetailsDialogOpen(false)} maxWidth="md" fullWidth > {t('knowledgeDetails.documentProcessDetails')} {selectedFileDetails && ( {/* 基本信息卡片 */} {t('knowledgeDetails.basicInfo')} {t('knowledgeDetails.fileName')} {selectedFileDetails.name} {t('knowledgeDetails.parserId')} {selectedFileDetails.parser_id || t('knowledgeDetails.notSpecified')} {/* 处理状态卡片 */} {t('knowledgeDetails.processStatus')} {t('knowledgeDetails.startTime')} {selectedFileDetails.process_begin_at || t('knowledgeDetails.notStarted')} {t('knowledgeDetails.processingTime')} {selectedFileDetails.process_duration ? `${selectedFileDetails.process_duration}${t('knowledgeDetails.seconds')}` : t('knowledgeDetails.notCompleted')} {t('knowledgeDetails.progress')} {/* 处理详情卡片 */} {selectedFileDetails.progress_msg && ( {t('knowledgeDetails.processDetails')} {selectedFileDetails.progress_msg} )} )} ); } export default KnowledgeBaseDetail;