2025-10-13 12:26:10 +08:00
|
|
|
|
import React, { useState, useEffect } from 'react';
|
|
|
|
|
|
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-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 knowledgeService from '@/services/knowledge_service';
|
|
|
|
|
|
import type { IKnowledge, IKnowledgeFile } from '@/interfaces/database/knowledge';
|
2025-10-14 15:42:40 +08:00
|
|
|
|
import FileUploadDialog from '@/components/FileUploadDialog';
|
|
|
|
|
|
import KnowledgeInfoCard from './components/KnowledgeInfoCard';
|
|
|
|
|
|
import FileListComponent from './components/FileListComponent';
|
|
|
|
|
|
import FloatingActionButtons from './components/FloatingActionButtons';
|
|
|
|
|
|
import { useDocumentList, useDocumentOperations } from '@/hooks/document-hooks';
|
2025-10-13 12:26:10 +08:00
|
|
|
|
|
|
|
|
|
|
function KnowledgeBaseDetail() {
|
|
|
|
|
|
const { id } = useParams<{ id: string }>();
|
|
|
|
|
|
const navigate = useNavigate();
|
|
|
|
|
|
|
|
|
|
|
|
// 状态管理
|
|
|
|
|
|
const [knowledgeBase, setKnowledgeBase] = useState<IKnowledge | null>(null);
|
|
|
|
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
|
|
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-14 15:42:40 +08:00
|
|
|
|
const [testingDialogOpen, setTestingDialogOpen] = useState(false);
|
|
|
|
|
|
const [configDialogOpen, setConfigDialogOpen] = useState(false);
|
|
|
|
|
|
|
|
|
|
|
|
// 使用新的document hooks
|
|
|
|
|
|
const {
|
|
|
|
|
|
documents: files,
|
|
|
|
|
|
loading: filesLoading,
|
|
|
|
|
|
error: filesError,
|
|
|
|
|
|
refresh: refreshFiles,
|
|
|
|
|
|
setKeywords,
|
|
|
|
|
|
} = useDocumentList(id || '');
|
|
|
|
|
|
|
|
|
|
|
|
const {
|
|
|
|
|
|
uploadDocuments,
|
|
|
|
|
|
deleteDocuments,
|
|
|
|
|
|
runDocuments,
|
|
|
|
|
|
loading: operationLoading,
|
|
|
|
|
|
error: operationError,
|
|
|
|
|
|
} = useDocumentOperations();
|
2025-10-13 12:26:10 +08:00
|
|
|
|
|
|
|
|
|
|
// 获取知识库详情
|
|
|
|
|
|
const fetchKnowledgeDetail = async () => {
|
|
|
|
|
|
if (!id) return;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
setLoading(true);
|
|
|
|
|
|
const response = await knowledgeService.getKnowledgeDetail({ kb_id: id });
|
|
|
|
|
|
|
|
|
|
|
|
if (response.data.code === 0) {
|
|
|
|
|
|
setKnowledgeBase(response.data.data);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
setError(response.data.message || '获取知识库详情失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (err: any) {
|
|
|
|
|
|
setError(err.response?.data?.message || err.message || '获取知识库详情失败');
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setLoading(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-10-14 15:42:40 +08:00
|
|
|
|
// 删除文件
|
|
|
|
|
|
const handleDeleteFiles = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await deleteDocuments(Array.from(rowSelectionModel.ids) as string[]);
|
|
|
|
|
|
setDeleteDialogOpen(false);
|
|
|
|
|
|
setRowSelectionModel({
|
|
|
|
|
|
type: 'include',
|
|
|
|
|
|
ids: new Set()
|
|
|
|
|
|
});
|
|
|
|
|
|
refreshFiles();
|
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-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-13 12:26:10 +08:00
|
|
|
|
} catch (err: any) {
|
|
|
|
|
|
setError(err.response?.data?.message || err.message || '重新解析失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化数据
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
fetchKnowledgeDetail();
|
|
|
|
|
|
}, [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
|
|
|
|
|
|
|
|
|
|
if (loading) {
|
|
|
|
|
|
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>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!knowledgeBase) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<Box sx={{ p: 3 }}>
|
|
|
|
|
|
<Alert severity="warning">知识库不存在</Alert>
|
|
|
|
|
|
</Box>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<Box sx={{ p: 3 }}>
|
|
|
|
|
|
{/* 面包屑导航 */}
|
|
|
|
|
|
<Breadcrumbs sx={{ mb: 2 }}>
|
|
|
|
|
|
<Link
|
|
|
|
|
|
component="button"
|
|
|
|
|
|
variant="body1"
|
|
|
|
|
|
onClick={() => navigate('/knowledge')}
|
|
|
|
|
|
sx={{ textDecoration: 'none' }}
|
|
|
|
|
|
>
|
|
|
|
|
|
知识库
|
|
|
|
|
|
</Link>
|
|
|
|
|
|
<Typography color="text.primary">{knowledgeBase.name}</Typography>
|
|
|
|
|
|
</Breadcrumbs>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 知识库信息卡片 */}
|
2025-10-14 15:42:40 +08:00
|
|
|
|
<KnowledgeInfoCard knowledgeBase={knowledgeBase} />
|
2025-10-13 12:26:10 +08:00
|
|
|
|
|
2025-10-14 15:42:40 +08:00
|
|
|
|
{/* 文件列表组件 */}
|
|
|
|
|
|
<FileListComponent
|
|
|
|
|
|
files={files}
|
|
|
|
|
|
loading={filesLoading}
|
|
|
|
|
|
searchKeyword={searchKeyword}
|
|
|
|
|
|
onSearchChange={setSearchKeyword}
|
|
|
|
|
|
onReparse={(fileIds) => handleReparse(fileIds)}
|
|
|
|
|
|
onDelete={(fileIds) => {
|
|
|
|
|
|
console.log('删除文件:', fileIds);
|
2025-10-13 12:26:10 +08:00
|
|
|
|
|
2025-10-14 15:42:40 +08:00
|
|
|
|
setRowSelectionModel({
|
|
|
|
|
|
type: 'include',
|
|
|
|
|
|
ids: new Set(fileIds)
|
|
|
|
|
|
});
|
|
|
|
|
|
setDeleteDialogOpen(true);
|
|
|
|
|
|
}}
|
|
|
|
|
|
onUpload={() => setUploadDialogOpen(true)}
|
|
|
|
|
|
onRefresh={() => {
|
|
|
|
|
|
refreshFiles();
|
|
|
|
|
|
fetchKnowledgeDetail();
|
|
|
|
|
|
}}
|
|
|
|
|
|
rowSelectionModel={rowSelectionModel}
|
|
|
|
|
|
onRowSelectionModelChange={(newModel) => {
|
|
|
|
|
|
console.log('新的选择模型:', newModel);
|
|
|
|
|
|
setRowSelectionModel(newModel);
|
|
|
|
|
|
}}
|
|
|
|
|
|
/>
|
2025-10-13 12:26:10 +08:00
|
|
|
|
|
2025-10-14 15:42:40 +08:00
|
|
|
|
{/* 浮动操作按钮 */}
|
|
|
|
|
|
<FloatingActionButtons
|
|
|
|
|
|
onTestClick={() => setTestingDialogOpen(true)}
|
|
|
|
|
|
onConfigClick={() => setConfigDialogOpen(true)}
|
|
|
|
|
|
/>
|
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
|
|
|
|
|
|
|
|
|
|
{/* 检索测试对话框 */}
|
|
|
|
|
|
<Dialog open={testingDialogOpen} onClose={() => setTestingDialogOpen(false)} maxWidth="md" fullWidth>
|
|
|
|
|
|
<DialogTitle>检索测试</DialogTitle>
|
|
|
|
|
|
<DialogContent>
|
|
|
|
|
|
<Stack spacing={3} sx={{ mt: 1 }}>
|
|
|
|
|
|
<TextField
|
|
|
|
|
|
fullWidth
|
|
|
|
|
|
label="测试查询"
|
|
|
|
|
|
placeholder="输入要测试的查询内容..."
|
|
|
|
|
|
multiline
|
|
|
|
|
|
rows={3}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<Stack direction="row" spacing={2}>
|
|
|
|
|
|
<TextField
|
|
|
|
|
|
label="返回结果数量"
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
defaultValue={5}
|
|
|
|
|
|
sx={{ width: 150 }}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<TextField
|
|
|
|
|
|
label="相似度阈值"
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
defaultValue={0.7}
|
|
|
|
|
|
inputProps={{ min: 0, max: 1, step: 0.1 }}
|
|
|
|
|
|
sx={{ width: 150 }}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Stack>
|
|
|
|
|
|
<Box sx={{ minHeight: 200, border: '1px solid #e0e0e0', borderRadius: 1, p: 2 }}>
|
|
|
|
|
|
<Typography variant="body2" color="text.secondary">
|
|
|
|
|
|
测试结果将在这里显示...
|
|
|
|
|
|
</Typography>
|
|
|
|
|
|
</Box>
|
|
|
|
|
|
</Stack>
|
|
|
|
|
|
</DialogContent>
|
|
|
|
|
|
<DialogActions>
|
|
|
|
|
|
<Button onClick={() => setTestingDialogOpen(false)}>关闭</Button>
|
|
|
|
|
|
<Button variant="contained">开始测试</Button>
|
|
|
|
|
|
</DialogActions>
|
|
|
|
|
|
</Dialog>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 配置设置对话框 */}
|
|
|
|
|
|
<Dialog open={configDialogOpen} onClose={() => setConfigDialogOpen(false)} maxWidth="sm" fullWidth>
|
|
|
|
|
|
<DialogTitle>配置设置</DialogTitle>
|
|
|
|
|
|
<DialogContent>
|
|
|
|
|
|
<Stack spacing={3} sx={{ mt: 1 }}>
|
|
|
|
|
|
<TextField
|
|
|
|
|
|
fullWidth
|
|
|
|
|
|
label="知识库名称"
|
|
|
|
|
|
defaultValue={knowledgeBase?.name}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<TextField
|
|
|
|
|
|
fullWidth
|
|
|
|
|
|
label="描述"
|
|
|
|
|
|
multiline
|
|
|
|
|
|
rows={3}
|
|
|
|
|
|
defaultValue={knowledgeBase?.description}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<TextField
|
|
|
|
|
|
fullWidth
|
|
|
|
|
|
label="语言"
|
|
|
|
|
|
defaultValue={knowledgeBase?.language}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<TextField
|
|
|
|
|
|
fullWidth
|
|
|
|
|
|
label="嵌入模型"
|
|
|
|
|
|
defaultValue={knowledgeBase?.embd_id}
|
|
|
|
|
|
disabled
|
|
|
|
|
|
/>
|
|
|
|
|
|
<TextField
|
|
|
|
|
|
fullWidth
|
|
|
|
|
|
label="解析器"
|
|
|
|
|
|
defaultValue={knowledgeBase?.parser_id}
|
|
|
|
|
|
disabled
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Stack>
|
|
|
|
|
|
</DialogContent>
|
|
|
|
|
|
<DialogActions>
|
|
|
|
|
|
<Button onClick={() => setConfigDialogOpen(false)}>取消</Button>
|
|
|
|
|
|
<Button variant="contained">保存</Button>
|
|
|
|
|
|
</DialogActions>
|
|
|
|
|
|
</Dialog>
|
2025-10-13 12:26:10 +08:00
|
|
|
|
</Box>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default KnowledgeBaseDetail;
|