feat(knowledge): add knowledge graph visualization component
- Add @xyflow/react dependency for graph visualization - Create KnowledgeGraphView component with custom nodes and edges - Extend knowledge detail hook to fetch and display graph data - Add tabs in knowledge detail page to switch between documents and graph views
This commit is contained in:
@@ -18,9 +18,10 @@ import {
|
||||
CardContent,
|
||||
Divider,
|
||||
Chip,
|
||||
Tabs,
|
||||
Tab,
|
||||
} 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';
|
||||
@@ -28,16 +29,16 @@ 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 { RUNNING_STATUS_KEYS } from '@/constants/knowledge';
|
||||
import { useKnowledgeDetail } from '@/hooks/knowledge-hooks';
|
||||
|
||||
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('');
|
||||
const [rowSelectionModel, setRowSelectionModel] = useState<GridRowSelectionModel>({
|
||||
@@ -46,11 +47,13 @@ function KnowledgeBaseDetail() {
|
||||
});
|
||||
const [uploadDialogOpen, setUploadDialogOpen] = useState(false);
|
||||
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 [currentTab, setCurrentTab] = useState(0);
|
||||
|
||||
// 轮询相关状态
|
||||
const pollingIntervalRef = useRef<any>(null);
|
||||
const [isPolling, setIsPolling] = useState(false);
|
||||
@@ -80,25 +83,9 @@ function KnowledgeBaseDetail() {
|
||||
error: operationError,
|
||||
} = useDocumentOperations();
|
||||
|
||||
// 获取知识库详情
|
||||
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);
|
||||
}
|
||||
};
|
||||
const { knowledge: knowledgeBase, refresh: fetchKnowledgeDetail, loading, showKnowledgeGraph, knowledgeGraph } = useKnowledgeDetail(id || '');
|
||||
|
||||
console.log('showKnowledgeGraph:', showKnowledgeGraph, knowledgeGraph);
|
||||
|
||||
// 删除文件
|
||||
const handleDeleteFiles = async () => {
|
||||
@@ -291,42 +278,104 @@ function KnowledgeBaseDetail() {
|
||||
{/* 知识库信息卡片 */}
|
||||
<KnowledgeInfoCard knowledgeBase={knowledgeBase} />
|
||||
|
||||
{/* 文件列表组件 */}
|
||||
<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}
|
||||
onPageChange={setCurrentPage}
|
||||
onPageSizeChange={setPageSize}
|
||||
onRename={handleRename}
|
||||
onChangeStatus={handleChangeStatus}
|
||||
onCancelRun={handleCancelRun}
|
||||
onViewDetails={handleViewDetails}
|
||||
onViewProcessDetails={handleViewProcessDetails}
|
||||
/>
|
||||
{/* 标签页组件 - 仅在showKnowledgeGraph为true时显示 */}
|
||||
{showKnowledgeGraph ? (
|
||||
<Box sx={{ mt: 3 }}>
|
||||
<Tabs
|
||||
value={currentTab}
|
||||
onChange={(event, newValue) => setCurrentTab(newValue)}
|
||||
sx={{ borderBottom: 1, borderColor: 'divider' }}
|
||||
>
|
||||
<Tab label="Documents" />
|
||||
<Tab label="Graph" />
|
||||
</Tabs>
|
||||
|
||||
{/* Document List 标签页内容 */}
|
||||
{currentTab === 0 && (
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<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}
|
||||
onPageChange={setCurrentPage}
|
||||
onPageSizeChange={setPageSize}
|
||||
onRename={handleRename}
|
||||
onChangeStatus={handleChangeStatus}
|
||||
onCancelRun={handleCancelRun}
|
||||
onViewDetails={handleViewDetails}
|
||||
onViewProcessDetails={handleViewProcessDetails}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* 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}
|
||||
onPageChange={setCurrentPage}
|
||||
onPageSizeChange={setPageSize}
|
||||
onRename={handleRename}
|
||||
onChangeStatus={handleChangeStatus}
|
||||
onCancelRun={handleCancelRun}
|
||||
onViewDetails={handleViewDetails}
|
||||
onViewProcessDetails={handleViewProcessDetails}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 浮动操作按钮 */}
|
||||
<FloatingActionButtons
|
||||
@@ -359,88 +408,6 @@ function KnowledgeBaseDetail() {
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
{/* 检索测试对话框 */}
|
||||
<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>
|
||||
|
||||
{/* 文档详情对话框 */}
|
||||
<Dialog
|
||||
open={processDetailsDialogOpen}
|
||||
|
||||
Reference in New Issue
Block a user