feat(knowledge): add chunk management and document processing features
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
Box,
|
||||
@@ -14,16 +14,22 @@ import {
|
||||
Breadcrumbs,
|
||||
Link,
|
||||
Stack,
|
||||
Card,
|
||||
CardContent,
|
||||
Divider,
|
||||
Chip,
|
||||
} from '@mui/material';
|
||||
import { type GridRowSelectionModel } from '@mui/x-data-grid';
|
||||
import knowledgeService from '@/services/knowledge_service';
|
||||
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 FileListComponent from './components/FileListComponent';
|
||||
import DocumentListComponent from './components/DocumentListComponent';
|
||||
import FloatingActionButtons from './components/FloatingActionButtons';
|
||||
import KnowledgeBreadcrumbs from './components/KnowledgeBreadcrumbs';
|
||||
import { useDocumentList, useDocumentOperations } from '@/hooks/document-hooks';
|
||||
import { RUNNING_STATUS_KEYS } from '@/constants/knowledge';
|
||||
|
||||
function KnowledgeBaseDetail() {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
@@ -42,6 +48,12 @@ function KnowledgeBaseDetail() {
|
||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||
const [testingDialogOpen, setTestingDialogOpen] = useState(false);
|
||||
const [configDialogOpen, setConfigDialogOpen] = useState(false);
|
||||
const [processDetailsDialogOpen, setProcessDetailsDialogOpen] = useState(false);
|
||||
const [selectedFileDetails, setSelectedFileDetails] = useState<IKnowledgeFile | null>(null);
|
||||
|
||||
// 轮询相关状态
|
||||
const pollingIntervalRef = useRef<any>(null);
|
||||
const [isPolling, setIsPolling] = useState(false);
|
||||
|
||||
// 使用新的document hooks
|
||||
const {
|
||||
@@ -61,6 +73,9 @@ function KnowledgeBaseDetail() {
|
||||
uploadDocuments,
|
||||
deleteDocuments,
|
||||
runDocuments,
|
||||
renameDocument,
|
||||
changeDocumentStatus,
|
||||
cancelRunDocuments,
|
||||
loading: operationLoading,
|
||||
error: operationError,
|
||||
} = useDocumentOperations();
|
||||
@@ -119,11 +134,98 @@ function KnowledgeBaseDetail() {
|
||||
try {
|
||||
await runDocuments(docIds);
|
||||
refreshFiles(); // 刷新列表
|
||||
startPolling(); // 开始轮询
|
||||
} catch (err: any) {
|
||||
setError(err.response?.data?.message || err.message || '重新解析失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 重命名文件
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
// 初始化数据
|
||||
useEffect(() => {
|
||||
fetchKnowledgeDetail();
|
||||
@@ -166,6 +268,10 @@ function KnowledgeBaseDetail() {
|
||||
);
|
||||
}
|
||||
|
||||
function fetchDocumentsFilter(): Promise<IDocumentInfoFilter | undefined> {
|
||||
throw new Error('Function not implemented.');
|
||||
}
|
||||
|
||||
return (
|
||||
<Box sx={{ p: 3 }}>
|
||||
{/* 面包屑导航 */}
|
||||
@@ -175,7 +281,7 @@ function KnowledgeBaseDetail() {
|
||||
<KnowledgeInfoCard knowledgeBase={knowledgeBase} />
|
||||
|
||||
{/* 文件列表组件 */}
|
||||
<FileListComponent
|
||||
<DocumentListComponent
|
||||
files={files}
|
||||
loading={filesLoading}
|
||||
searchKeyword={searchKeyword}
|
||||
@@ -183,7 +289,6 @@ function KnowledgeBaseDetail() {
|
||||
onReparse={(fileIds) => handleReparse(fileIds)}
|
||||
onDelete={(fileIds) => {
|
||||
console.log('删除文件:', fileIds);
|
||||
|
||||
setRowSelectionModel({
|
||||
type: 'include',
|
||||
ids: new Set(fileIds)
|
||||
@@ -205,6 +310,11 @@ function KnowledgeBaseDetail() {
|
||||
pageSize={pageSize}
|
||||
onPageChange={setCurrentPage}
|
||||
onPageSizeChange={setPageSize}
|
||||
onRename={handleRename}
|
||||
onChangeStatus={handleChangeStatus}
|
||||
onCancelRun={handleCancelRun}
|
||||
onViewDetails={handleViewDetails}
|
||||
onViewProcessDetails={handleViewProcessDetails}
|
||||
/>
|
||||
|
||||
{/* 浮动操作按钮 */}
|
||||
@@ -319,6 +429,137 @@ function KnowledgeBaseDetail() {
|
||||
<Button variant="contained">保存</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
{/* 文档详情对话框 */}
|
||||
<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 }}>
|
||||
<Chip
|
||||
label={`${100*(selectedFileDetails.progress || 0)}%`}
|
||||
color="primary"
|
||||
size="small"
|
||||
/>
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={100*(selectedFileDetails.progress || 0)}
|
||||
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>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user