2025-10-16 16:23:53 +08:00
|
|
|
|
import React, { useState, useEffect, useRef } from 'react';
|
2025-10-13 12:26:10 +08:00
|
|
|
|
import { useParams, useNavigate } from 'react-router-dom';
|
|
|
|
|
|
import {
|
|
|
|
|
|
Box,
|
|
|
|
|
|
Typography,
|
|
|
|
|
|
LinearProgress,
|
|
|
|
|
|
Alert,
|
|
|
|
|
|
Dialog,
|
|
|
|
|
|
DialogTitle,
|
|
|
|
|
|
DialogContent,
|
|
|
|
|
|
DialogActions,
|
2025-10-14 15:42:40 +08:00
|
|
|
|
Button,
|
|
|
|
|
|
TextField,
|
2025-10-13 12:26:10 +08:00
|
|
|
|
Breadcrumbs,
|
|
|
|
|
|
Link,
|
2025-10-14 15:42:40 +08:00
|
|
|
|
Stack,
|
2025-10-16 16:23:53 +08:00
|
|
|
|
Card,
|
|
|
|
|
|
CardContent,
|
|
|
|
|
|
Divider,
|
|
|
|
|
|
Chip,
|
2025-10-17 16:43:03 +08:00
|
|
|
|
Tabs,
|
|
|
|
|
|
Tab,
|
2025-10-13 12:26:10 +08:00
|
|
|
|
} from '@mui/material';
|
2025-10-14 15:42:40 +08:00
|
|
|
|
import { type GridRowSelectionModel } from '@mui/x-data-grid';
|
2025-10-13 12:26:10 +08:00
|
|
|
|
import type { IKnowledge, IKnowledgeFile } from '@/interfaces/database/knowledge';
|
2025-10-16 16:23:53 +08:00
|
|
|
|
import type { IDocumentInfoFilter } from '@/interfaces/database/document';
|
2025-10-14 15:42:40 +08:00
|
|
|
|
import FileUploadDialog from '@/components/FileUploadDialog';
|
|
|
|
|
|
import KnowledgeInfoCard from './components/KnowledgeInfoCard';
|
2025-10-16 16:23:53 +08:00
|
|
|
|
import DocumentListComponent from './components/DocumentListComponent';
|
2025-10-14 15:42:40 +08:00
|
|
|
|
import FloatingActionButtons from './components/FloatingActionButtons';
|
2025-10-14 18:06:12 +08:00
|
|
|
|
import KnowledgeBreadcrumbs from './components/KnowledgeBreadcrumbs';
|
2025-10-17 16:43:03 +08:00
|
|
|
|
import KnowledgeGraphView from './components/KnowledgeGraphView';
|
2025-10-14 15:42:40 +08:00
|
|
|
|
import { useDocumentList, useDocumentOperations } from '@/hooks/document-hooks';
|
2025-10-16 16:23:53 +08:00
|
|
|
|
import { RUNNING_STATUS_KEYS } from '@/constants/knowledge';
|
2025-10-17 16:43:03 +08:00
|
|
|
|
import { useKnowledgeDetail } from '@/hooks/knowledge-hooks';
|
2025-10-20 14:57:43 +08:00
|
|
|
|
import logger from '@/utils/logger';
|
2025-10-13 12:26:10 +08:00
|
|
|
|
|
|
|
|
|
|
function KnowledgeBaseDetail() {
|
|
|
|
|
|
const { id } = useParams<{ id: string }>();
|
|
|
|
|
|
const navigate = useNavigate();
|
2025-10-20 14:57:43 +08:00
|
|
|
|
|
2025-10-13 12:26:10 +08:00
|
|
|
|
// 状态管理
|
|
|
|
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
|
|
const [searchKeyword, setSearchKeyword] = useState('');
|
2025-10-14 15:42:40 +08:00
|
|
|
|
const [rowSelectionModel, setRowSelectionModel] = useState<GridRowSelectionModel>({
|
|
|
|
|
|
type: 'include',
|
|
|
|
|
|
ids: new Set()
|
|
|
|
|
|
});
|
2025-10-13 12:26:10 +08:00
|
|
|
|
const [uploadDialogOpen, setUploadDialogOpen] = useState(false);
|
|
|
|
|
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
2025-10-20 14:57:43 +08:00
|
|
|
|
|
2025-10-16 16:23:53 +08:00
|
|
|
|
const [processDetailsDialogOpen, setProcessDetailsDialogOpen] = useState(false);
|
|
|
|
|
|
const [selectedFileDetails, setSelectedFileDetails] = useState<IKnowledgeFile | null>(null);
|
2025-10-20 14:57:43 +08:00
|
|
|
|
|
2025-10-17 16:43:03 +08:00
|
|
|
|
// 标签页状态
|
|
|
|
|
|
const [currentTab, setCurrentTab] = useState(0);
|
2025-10-20 14:57:43 +08:00
|
|
|
|
|
2025-10-16 16:23:53 +08:00
|
|
|
|
// 轮询相关状态
|
|
|
|
|
|
const pollingIntervalRef = useRef<any>(null);
|
|
|
|
|
|
const [isPolling, setIsPolling] = useState(false);
|
2025-10-14 15:42:40 +08:00
|
|
|
|
|
2025-10-20 14:57:43 +08:00
|
|
|
|
// documents filter
|
|
|
|
|
|
const [documentsFilter, setDocumentsFilter] = useState<IDocumentInfoFilter | undefined>(undefined);
|
|
|
|
|
|
|
|
|
|
|
|
const {
|
|
|
|
|
|
knowledge: knowledgeBase,
|
|
|
|
|
|
refresh: fetchKnowledgeDetail,
|
|
|
|
|
|
loading, showKnowledgeGraph,
|
|
|
|
|
|
knowledgeGraph
|
|
|
|
|
|
} = useKnowledgeDetail(id || '');
|
|
|
|
|
|
|
2025-10-14 15:42:40 +08:00
|
|
|
|
// 使用新的document hooks
|
|
|
|
|
|
const {
|
|
|
|
|
|
documents: files,
|
2025-10-14 18:06:12 +08:00
|
|
|
|
total,
|
2025-10-14 15:42:40 +08:00
|
|
|
|
loading: filesLoading,
|
|
|
|
|
|
error: filesError,
|
2025-10-14 18:06:12 +08:00
|
|
|
|
currentPage,
|
|
|
|
|
|
pageSize,
|
2025-10-14 15:42:40 +08:00
|
|
|
|
refresh: refreshFiles,
|
|
|
|
|
|
setKeywords,
|
2025-10-14 18:06:12 +08:00
|
|
|
|
setCurrentPage,
|
|
|
|
|
|
setPageSize,
|
2025-10-20 14:57:43 +08:00
|
|
|
|
fetchDocuments,
|
|
|
|
|
|
fetchDocumentsFilter,
|
2025-10-14 15:42:40 +08:00
|
|
|
|
} = useDocumentList(id || '');
|
|
|
|
|
|
|
|
|
|
|
|
const {
|
|
|
|
|
|
uploadDocuments,
|
|
|
|
|
|
deleteDocuments,
|
|
|
|
|
|
runDocuments,
|
2025-10-16 16:23:53 +08:00
|
|
|
|
renameDocument,
|
|
|
|
|
|
changeDocumentStatus,
|
|
|
|
|
|
cancelRunDocuments,
|
2025-10-14 15:42:40 +08:00
|
|
|
|
error: operationError,
|
|
|
|
|
|
} = useDocumentOperations();
|
2025-10-13 12:26:10 +08:00
|
|
|
|
|
2025-10-17 16:43:03 +08:00
|
|
|
|
console.log('showKnowledgeGraph:', showKnowledgeGraph, knowledgeGraph);
|
2025-10-13 12:26:10 +08:00
|
|
|
|
|
2025-10-20 14:57:43 +08:00
|
|
|
|
const refreshDetailData = async () => {
|
|
|
|
|
|
await fetchKnowledgeDetail();
|
|
|
|
|
|
await refreshFiles();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-10-14 15:42:40 +08:00
|
|
|
|
// 删除文件
|
|
|
|
|
|
const handleDeleteFiles = async () => {
|
2025-10-20 14:57:43 +08:00
|
|
|
|
try {
|
2025-10-14 15:42:40 +08:00
|
|
|
|
await deleteDocuments(Array.from(rowSelectionModel.ids) as string[]);
|
|
|
|
|
|
setDeleteDialogOpen(false);
|
|
|
|
|
|
setRowSelectionModel({
|
|
|
|
|
|
type: 'include',
|
|
|
|
|
|
ids: new Set()
|
|
|
|
|
|
});
|
|
|
|
|
|
refreshFiles();
|
2025-10-14 18:06:12 +08:00
|
|
|
|
fetchKnowledgeDetail();
|
2025-10-13 12:26:10 +08:00
|
|
|
|
} catch (err: any) {
|
2025-10-14 15:42:40 +08:00
|
|
|
|
setError(err.response?.data?.message || err.message || '删除文件失败');
|
2025-10-13 12:26:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-10-14 15:42:40 +08:00
|
|
|
|
// 上传文件处理
|
|
|
|
|
|
const handleUploadFiles = async (uploadFiles: File[]) => {
|
|
|
|
|
|
console.log('上传文件:', uploadFiles);
|
|
|
|
|
|
const kb_id = knowledgeBase?.id || '';
|
2025-10-13 12:26:10 +08:00
|
|
|
|
try {
|
2025-10-14 15:42:40 +08:00
|
|
|
|
await uploadDocuments(kb_id, uploadFiles);
|
|
|
|
|
|
refreshFiles();
|
2025-10-14 18:06:12 +08:00
|
|
|
|
fetchKnowledgeDetail();
|
2025-10-13 12:26:10 +08:00
|
|
|
|
} catch (err: any) {
|
2025-10-14 15:42:40 +08:00
|
|
|
|
throw new Error(err.response?.data?.message || err.message || '上传文件失败');
|
2025-10-13 12:26:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 重新解析文件
|
|
|
|
|
|
const handleReparse = async (docIds: string[]) => {
|
|
|
|
|
|
try {
|
2025-10-14 15:42:40 +08:00
|
|
|
|
await runDocuments(docIds);
|
|
|
|
|
|
refreshFiles(); // 刷新列表
|
2025-10-16 16:23:53 +08:00
|
|
|
|
startPolling(); // 开始轮询
|
2025-10-13 12:26:10 +08:00
|
|
|
|
} catch (err: any) {
|
|
|
|
|
|
setError(err.response?.data?.message || err.message || '重新解析失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-10-16 16:23:53 +08:00
|
|
|
|
// 重命名文件
|
|
|
|
|
|
const handleRename = async (fileId: string, newName: string) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await renameDocument(fileId, newName);
|
|
|
|
|
|
refreshFiles();
|
|
|
|
|
|
} catch (err: any) {
|
|
|
|
|
|
setError(err.response?.data?.message || err.message || '重命名失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 更改文件状态
|
|
|
|
|
|
const handleChangeStatus = async (fileIds: string[], status: string) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await changeDocumentStatus(fileIds, status);
|
|
|
|
|
|
refreshFiles();
|
|
|
|
|
|
} catch (err: any) {
|
|
|
|
|
|
setError(err.response?.data?.message || err.message || '更改状态失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 取消运行
|
|
|
|
|
|
const handleCancelRun = async (fileIds: string[]) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await cancelRunDocuments(fileIds);
|
|
|
|
|
|
refreshFiles();
|
|
|
|
|
|
} catch (err: any) {
|
|
|
|
|
|
setError(err.response?.data?.message || err.message || '取消运行失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 查看详情
|
|
|
|
|
|
const handleViewDetails = (file: IKnowledgeFile) => {
|
|
|
|
|
|
console.log("查看详情:", file);
|
|
|
|
|
|
|
|
|
|
|
|
navigate(`/chunk/parsed-result?kb_id=${id}&doc_id=${file.id}`);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 查看解析详情
|
|
|
|
|
|
const handleViewProcessDetails = (file: IKnowledgeFile) => {
|
|
|
|
|
|
console.log("查看解析详情:", file);
|
|
|
|
|
|
setSelectedFileDetails(file);
|
|
|
|
|
|
setProcessDetailsDialogOpen(true);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 开始轮询
|
|
|
|
|
|
const startPolling = () => {
|
|
|
|
|
|
if (pollingIntervalRef.current) {
|
|
|
|
|
|
clearInterval(pollingIntervalRef.current);
|
|
|
|
|
|
}
|
2025-10-20 14:57:43 +08:00
|
|
|
|
|
2025-10-16 16:23:53 +08:00
|
|
|
|
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 === RUNNING_STATUS_KEYS.RUNNING);
|
|
|
|
|
|
|
|
|
|
|
|
// 根据运行状态自动管理轮询
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (hasRunningDocuments && !isPolling) {
|
|
|
|
|
|
startPolling();
|
|
|
|
|
|
} else if (!hasRunningDocuments && isPolling) {
|
|
|
|
|
|
stopPolling();
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [hasRunningDocuments, isPolling]);
|
|
|
|
|
|
|
|
|
|
|
|
// 组件卸载时清理轮询
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
return () => {
|
|
|
|
|
|
if (pollingIntervalRef.current) {
|
|
|
|
|
|
clearInterval(pollingIntervalRef.current);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
2025-10-13 12:26:10 +08:00
|
|
|
|
// 初始化数据
|
|
|
|
|
|
useEffect(() => {
|
2025-10-20 14:57:43 +08:00
|
|
|
|
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 || {},
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2025-10-13 12:26:10 +08:00
|
|
|
|
}, [id]);
|
|
|
|
|
|
|
|
|
|
|
|
// 搜索文件
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
const timer = setTimeout(() => {
|
2025-10-14 15:42:40 +08:00
|
|
|
|
setKeywords(searchKeyword);
|
2025-10-13 12:26:10 +08:00
|
|
|
|
}, 500);
|
2025-10-14 15:42:40 +08:00
|
|
|
|
|
2025-10-13 12:26:10 +08:00
|
|
|
|
return () => clearTimeout(timer);
|
2025-10-14 15:42:40 +08:00
|
|
|
|
}, [searchKeyword, setKeywords]);
|
2025-10-13 12:26:10 +08:00
|
|
|
|
|
2025-10-14 15:42:40 +08:00
|
|
|
|
// 合并错误状态
|
|
|
|
|
|
const combinedError = error || filesError || operationError;
|
2025-10-13 12:26:10 +08:00
|
|
|
|
|
2025-10-20 14:57:43 +08:00
|
|
|
|
if (loading || !knowledgeBase) {
|
2025-10-13 12:26:10 +08:00
|
|
|
|
return (
|
|
|
|
|
|
<Box sx={{ p: 3 }}>
|
|
|
|
|
|
<LinearProgress />
|
|
|
|
|
|
<Typography sx={{ mt: 2 }}>加载中...</Typography>
|
|
|
|
|
|
</Box>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-14 15:42:40 +08:00
|
|
|
|
if (combinedError) {
|
2025-10-13 12:26:10 +08:00
|
|
|
|
return (
|
|
|
|
|
|
<Box sx={{ p: 3 }}>
|
2025-10-14 15:42:40 +08:00
|
|
|
|
<Alert severity="error">{combinedError}</Alert>
|
2025-10-13 12:26:10 +08:00
|
|
|
|
</Box>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<Box sx={{ p: 3 }}>
|
|
|
|
|
|
{/* 面包屑导航 */}
|
2025-10-20 14:57:43 +08:00
|
|
|
|
<KnowledgeBreadcrumbs
|
2025-10-17 11:11:48 +08:00
|
|
|
|
kbItems={[
|
|
|
|
|
|
{
|
|
|
|
|
|
label: '知识库',
|
|
|
|
|
|
path: '/knowledge'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
label: knowledgeBase?.name || '知识库详情',
|
|
|
|
|
|
path: `/knowledge/${id}`
|
|
|
|
|
|
}
|
|
|
|
|
|
]}
|
|
|
|
|
|
/>
|
2025-10-13 12:26:10 +08:00
|
|
|
|
|
|
|
|
|
|
{/* 知识库信息卡片 */}
|
2025-10-14 15:42:40 +08:00
|
|
|
|
<KnowledgeInfoCard knowledgeBase={knowledgeBase} />
|
2025-10-13 12:26:10 +08:00
|
|
|
|
|
2025-10-17 16:43:03 +08:00
|
|
|
|
{/* 标签页组件 - 仅在showKnowledgeGraph为true时显示 */}
|
|
|
|
|
|
{showKnowledgeGraph ? (
|
|
|
|
|
|
<Box sx={{ mt: 3 }}>
|
2025-10-20 14:57:43 +08:00
|
|
|
|
<Tabs
|
|
|
|
|
|
value={currentTab}
|
2025-10-17 16:43:03 +08:00
|
|
|
|
onChange={(event, newValue) => setCurrentTab(newValue)}
|
|
|
|
|
|
sx={{ borderBottom: 1, borderColor: 'divider' }}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Tab label="Documents" />
|
|
|
|
|
|
<Tab label="Graph" />
|
|
|
|
|
|
</Tabs>
|
2025-10-20 14:57:43 +08:00
|
|
|
|
|
2025-10-17 16:43:03 +08:00
|
|
|
|
{/* Document List 标签页内容 */}
|
|
|
|
|
|
{currentTab === 0 && (
|
|
|
|
|
|
<Box sx={{ mt: 2 }}>
|
|
|
|
|
|
<DocumentListComponent
|
|
|
|
|
|
files={files}
|
|
|
|
|
|
loading={filesLoading}
|
|
|
|
|
|
searchKeyword={searchKeyword}
|
|
|
|
|
|
onSearchChange={setSearchKeyword}
|
|
|
|
|
|
onReparse={(fileIds) => 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}
|
2025-10-20 14:57:43 +08:00
|
|
|
|
documentFilter={documentsFilter}
|
|
|
|
|
|
onSelectedFilterChange={(filterBody) => {
|
|
|
|
|
|
fetchDocuments({}, filterBody);
|
|
|
|
|
|
}}
|
2025-10-17 16:43:03 +08:00
|
|
|
|
onPageChange={setCurrentPage}
|
|
|
|
|
|
onPageSizeChange={setPageSize}
|
|
|
|
|
|
onRename={handleRename}
|
|
|
|
|
|
onChangeStatus={handleChangeStatus}
|
|
|
|
|
|
onCancelRun={handleCancelRun}
|
|
|
|
|
|
onViewDetails={handleViewDetails}
|
|
|
|
|
|
onViewProcessDetails={handleViewProcessDetails}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Box>
|
|
|
|
|
|
)}
|
2025-10-20 14:57:43 +08:00
|
|
|
|
|
2025-10-17 16:43:03 +08:00
|
|
|
|
{/* Graph 标签页内容 */}
|
|
|
|
|
|
{currentTab === 1 && (
|
|
|
|
|
|
<Box sx={{ mt: 2 }}>
|
|
|
|
|
|
<KnowledgeGraphView knowledgeGraph={knowledgeGraph} />
|
|
|
|
|
|
</Box>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</Box>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
/* 原有的文件列表组件 - 当showKnowledgeGraph为false时显示 */
|
|
|
|
|
|
<DocumentListComponent
|
|
|
|
|
|
files={files}
|
|
|
|
|
|
loading={filesLoading}
|
|
|
|
|
|
searchKeyword={searchKeyword}
|
|
|
|
|
|
onSearchChange={setSearchKeyword}
|
|
|
|
|
|
onReparse={(fileIds) => handleReparse(fileIds)}
|
|
|
|
|
|
onDelete={(fileIds) => {
|
|
|
|
|
|
console.log('删除文件:', fileIds);
|
|
|
|
|
|
setRowSelectionModel({
|
|
|
|
|
|
type: 'include',
|
|
|
|
|
|
ids: new Set(fileIds)
|
|
|
|
|
|
});
|
|
|
|
|
|
setDeleteDialogOpen(true);
|
|
|
|
|
|
}}
|
|
|
|
|
|
onUpload={() => setUploadDialogOpen(true)}
|
|
|
|
|
|
onRefresh={() => {
|
|
|
|
|
|
refreshFiles();
|
|
|
|
|
|
fetchKnowledgeDetail();
|
|
|
|
|
|
}}
|
|
|
|
|
|
rowSelectionModel={rowSelectionModel}
|
|
|
|
|
|
onRowSelectionModelChange={(newModel) => {
|
|
|
|
|
|
console.log('新的选择模型:', newModel);
|
|
|
|
|
|
setRowSelectionModel(newModel);
|
|
|
|
|
|
}}
|
|
|
|
|
|
total={total}
|
|
|
|
|
|
page={currentPage}
|
|
|
|
|
|
pageSize={pageSize}
|
2025-10-20 14:57:43 +08:00
|
|
|
|
documentFilter={documentsFilter}
|
|
|
|
|
|
onSelectedFilterChange={(filterBody) => {
|
|
|
|
|
|
fetchDocuments({}, filterBody);
|
|
|
|
|
|
}}
|
2025-10-17 16:43:03 +08:00
|
|
|
|
onPageChange={setCurrentPage}
|
|
|
|
|
|
onPageSizeChange={setPageSize}
|
|
|
|
|
|
onRename={handleRename}
|
|
|
|
|
|
onChangeStatus={handleChangeStatus}
|
|
|
|
|
|
onCancelRun={handleCancelRun}
|
|
|
|
|
|
onViewDetails={handleViewDetails}
|
|
|
|
|
|
onViewProcessDetails={handleViewProcessDetails}
|
|
|
|
|
|
/>
|
|
|
|
|
|
)}
|
2025-10-13 12:26:10 +08:00
|
|
|
|
|
2025-10-14 15:42:40 +08:00
|
|
|
|
{/* 浮动操作按钮 */}
|
|
|
|
|
|
<FloatingActionButtons
|
2025-10-14 18:06:12 +08:00
|
|
|
|
onTestClick={() => navigate(`/knowledge/${id}/testing`)}
|
|
|
|
|
|
onConfigClick={() => navigate(`/knowledge/${id}/setting`)}
|
2025-10-14 15:42:40 +08:00
|
|
|
|
/>
|
2025-10-13 12:26:10 +08:00
|
|
|
|
|
|
|
|
|
|
{/* 上传文件对话框 */}
|
2025-10-14 15:42:40 +08:00
|
|
|
|
<FileUploadDialog
|
|
|
|
|
|
open={uploadDialogOpen}
|
|
|
|
|
|
onClose={() => setUploadDialogOpen(false)}
|
|
|
|
|
|
onUpload={handleUploadFiles}
|
|
|
|
|
|
title="上传文件到知识库"
|
|
|
|
|
|
acceptedFileTypes={['.pdf', '.docx', '.txt', '.md', '.png', '.jpg', '.jpeg', '.mp4', '.wav']}
|
|
|
|
|
|
maxFileSize={100}
|
|
|
|
|
|
maxFiles={10}
|
|
|
|
|
|
/>
|
2025-10-13 12:26:10 +08:00
|
|
|
|
|
|
|
|
|
|
{/* 删除确认对话框 */}
|
|
|
|
|
|
<Dialog open={deleteDialogOpen} onClose={() => setDeleteDialogOpen(false)}>
|
|
|
|
|
|
<DialogTitle>确认删除</DialogTitle>
|
|
|
|
|
|
<DialogContent>
|
|
|
|
|
|
<Typography>
|
2025-10-14 15:42:40 +08:00
|
|
|
|
确定要删除选中的 {rowSelectionModel.ids.size} 个文件吗?此操作不可撤销。
|
2025-10-13 12:26:10 +08:00
|
|
|
|
</Typography>
|
|
|
|
|
|
</DialogContent>
|
|
|
|
|
|
<DialogActions>
|
|
|
|
|
|
<Button onClick={() => setDeleteDialogOpen(false)}>取消</Button>
|
|
|
|
|
|
<Button color="error" onClick={handleDeleteFiles}>删除</Button>
|
|
|
|
|
|
</DialogActions>
|
|
|
|
|
|
</Dialog>
|
2025-10-14 15:42:40 +08:00
|
|
|
|
|
2025-10-16 16:23:53 +08:00
|
|
|
|
{/* 文档详情对话框 */}
|
|
|
|
|
|
<Dialog
|
|
|
|
|
|
open={processDetailsDialogOpen}
|
|
|
|
|
|
onClose={() => setProcessDetailsDialogOpen(false)}
|
|
|
|
|
|
maxWidth="md"
|
|
|
|
|
|
fullWidth
|
|
|
|
|
|
>
|
|
|
|
|
|
<DialogTitle>文档处理详情</DialogTitle>
|
|
|
|
|
|
<DialogContent sx={{ p: 3 }}>
|
|
|
|
|
|
{selectedFileDetails && (
|
|
|
|
|
|
<Stack spacing={3}>
|
|
|
|
|
|
{/* 基本信息卡片 */}
|
|
|
|
|
|
<Card variant="outlined">
|
|
|
|
|
|
<CardContent>
|
|
|
|
|
|
<Typography variant="h6" gutterBottom color="primary">
|
|
|
|
|
|
基本信息
|
|
|
|
|
|
</Typography>
|
|
|
|
|
|
<Stack spacing={2}>
|
|
|
|
|
|
<Box>
|
|
|
|
|
|
<Typography variant="subtitle2" color="text.secondary" gutterBottom>
|
|
|
|
|
|
文件名
|
|
|
|
|
|
</Typography>
|
|
|
|
|
|
<Typography variant="body1" sx={{ fontWeight: 500 }}>
|
|
|
|
|
|
{selectedFileDetails.name}
|
|
|
|
|
|
</Typography>
|
|
|
|
|
|
</Box>
|
|
|
|
|
|
<Divider />
|
|
|
|
|
|
<Box>
|
|
|
|
|
|
<Typography variant="subtitle2" color="text.secondary" gutterBottom>
|
|
|
|
|
|
解析器ID
|
|
|
|
|
|
</Typography>
|
|
|
|
|
|
<Typography variant="body1">
|
|
|
|
|
|
{selectedFileDetails.parser_id || '未指定'}
|
|
|
|
|
|
</Typography>
|
|
|
|
|
|
</Box>
|
|
|
|
|
|
</Stack>
|
|
|
|
|
|
</CardContent>
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 处理状态卡片 */}
|
|
|
|
|
|
<Card variant="outlined">
|
|
|
|
|
|
<CardContent>
|
|
|
|
|
|
<Typography variant="h6" gutterBottom color="primary">
|
|
|
|
|
|
处理状态
|
|
|
|
|
|
</Typography>
|
|
|
|
|
|
<Stack spacing={2}>
|
|
|
|
|
|
<Box>
|
|
|
|
|
|
<Typography variant="subtitle2" color="text.secondary" gutterBottom>
|
|
|
|
|
|
开始时间
|
|
|
|
|
|
</Typography>
|
|
|
|
|
|
<Typography variant="body1">
|
|
|
|
|
|
{selectedFileDetails.process_begin_at || '未开始'}
|
|
|
|
|
|
</Typography>
|
|
|
|
|
|
</Box>
|
|
|
|
|
|
<Divider />
|
|
|
|
|
|
<Box>
|
|
|
|
|
|
<Typography variant="subtitle2" color="text.secondary" gutterBottom>
|
|
|
|
|
|
处理时长
|
|
|
|
|
|
</Typography>
|
|
|
|
|
|
<Typography variant="body1">
|
|
|
|
|
|
{selectedFileDetails.process_duration ? `${selectedFileDetails.process_duration}秒` : '未完成'}
|
|
|
|
|
|
</Typography>
|
|
|
|
|
|
</Box>
|
|
|
|
|
|
<Divider />
|
|
|
|
|
|
<Box>
|
|
|
|
|
|
<Typography variant="subtitle2" color="text.secondary" gutterBottom>
|
|
|
|
|
|
进度
|
|
|
|
|
|
</Typography>
|
|
|
|
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
2025-10-20 14:57:43 +08:00
|
|
|
|
<Chip
|
2025-10-24 17:56:19 +08:00
|
|
|
|
label={`${(100 * (selectedFileDetails.progress || 0)).toFixed(2)}%`}
|
2025-10-20 14:57:43 +08:00
|
|
|
|
color="primary"
|
|
|
|
|
|
size="small"
|
2025-10-16 16:23:53 +08:00
|
|
|
|
/>
|
2025-10-20 14:57:43 +08:00
|
|
|
|
<LinearProgress
|
|
|
|
|
|
variant="determinate"
|
|
|
|
|
|
value={100 * (selectedFileDetails.progress || 0)}
|
2025-10-16 16:23:53 +08:00
|
|
|
|
sx={{ flexGrow: 1, height: 8, borderRadius: 4 }}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Box>
|
|
|
|
|
|
</Box>
|
|
|
|
|
|
</Stack>
|
|
|
|
|
|
</CardContent>
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 处理详情卡片 */}
|
|
|
|
|
|
{selectedFileDetails.progress_msg && (
|
|
|
|
|
|
<Card variant="outlined">
|
|
|
|
|
|
<CardContent>
|
|
|
|
|
|
<Typography variant="h6" gutterBottom color="primary">
|
|
|
|
|
|
处理详情
|
|
|
|
|
|
</Typography>
|
|
|
|
|
|
<Box
|
|
|
|
|
|
sx={{
|
|
|
|
|
|
maxHeight: 300,
|
|
|
|
|
|
overflow: 'auto',
|
|
|
|
|
|
backgroundColor: 'grey.50',
|
|
|
|
|
|
borderRadius: 1,
|
|
|
|
|
|
p: 2,
|
|
|
|
|
|
border: '1px solid',
|
|
|
|
|
|
borderColor: 'grey.200',
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Typography
|
|
|
|
|
|
variant="body2"
|
|
|
|
|
|
component="pre"
|
|
|
|
|
|
sx={{
|
|
|
|
|
|
fontFamily: 'monospace',
|
|
|
|
|
|
fontSize: '0.875rem',
|
|
|
|
|
|
whiteSpace: 'pre-wrap',
|
|
|
|
|
|
wordBreak: 'break-word',
|
|
|
|
|
|
margin: 0,
|
|
|
|
|
|
color: 'text.primary',
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
{selectedFileDetails.progress_msg}
|
|
|
|
|
|
</Typography>
|
|
|
|
|
|
</Box>
|
|
|
|
|
|
</CardContent>
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</Stack>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</DialogContent>
|
|
|
|
|
|
<DialogActions sx={{ p: 3, pt: 0 }}>
|
|
|
|
|
|
<Button onClick={() => setProcessDetailsDialogOpen(false)} variant="contained">
|
|
|
|
|
|
关闭
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</DialogActions>
|
|
|
|
|
|
</Dialog>
|
2025-10-13 12:26:10 +08:00
|
|
|
|
</Box>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default KnowledgeBaseDetail;
|