feat(frontend): Day 21 knowledge base management frontend completed
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Card, Typography, Space, Alert, Spin, message } from 'antd'
|
||||
import { Space, Alert, Spin, message } from 'antd'
|
||||
import { RobotOutlined } from '@ant-design/icons'
|
||||
import { agentApi, type AgentConfig } from '../api/agentApi'
|
||||
import conversationApi, { type Conversation, type Message } from '../api/conversationApi'
|
||||
@@ -9,8 +9,6 @@ import MessageInput from '../components/chat/MessageInput'
|
||||
import ModelSelector, { type ModelType } from '../components/chat/ModelSelector'
|
||||
import { useProjectStore } from '../stores/useProjectStore'
|
||||
|
||||
const { Title, Paragraph } = Typography
|
||||
|
||||
const AgentChatPage = () => {
|
||||
const { agentId } = useParams()
|
||||
const { currentProject } = useProjectStore()
|
||||
@@ -71,10 +69,8 @@ const AgentChatPage = () => {
|
||||
title: `与${agent.name}的对话`,
|
||||
})
|
||||
|
||||
if (response.success && response.data) {
|
||||
setConversation(response.data)
|
||||
setMessages([])
|
||||
}
|
||||
setConversation(response.data.data || null)
|
||||
setMessages([])
|
||||
} catch (err) {
|
||||
console.error('Failed to create conversation:', err)
|
||||
message.error('创建对话失败')
|
||||
|
||||
@@ -1,225 +1,281 @@
|
||||
import {
|
||||
Card,
|
||||
Button,
|
||||
Space,
|
||||
Table,
|
||||
Tag,
|
||||
Progress,
|
||||
Alert,
|
||||
} from 'antd'
|
||||
import {
|
||||
PlusOutlined,
|
||||
FolderOutlined,
|
||||
FileTextOutlined,
|
||||
UploadOutlined,
|
||||
DeleteOutlined,
|
||||
SyncOutlined,
|
||||
} from '@ant-design/icons'
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Card, Tabs, Button, message } from 'antd';
|
||||
import { ArrowLeftOutlined } from '@ant-design/icons';
|
||||
import { useKnowledgeBaseStore } from '../stores/useKnowledgeBaseStore';
|
||||
import KnowledgeBaseList from '../components/knowledge/KnowledgeBaseList';
|
||||
import CreateKBDialog from '../components/knowledge/CreateKBDialog';
|
||||
import EditKBDialog from '../components/knowledge/EditKBDialog';
|
||||
import DocumentUpload from '../components/knowledge/DocumentUpload';
|
||||
import DocumentList from '../components/knowledge/DocumentList';
|
||||
import type { KnowledgeBase, Document } from '../api/knowledgeBaseApi';
|
||||
|
||||
const KnowledgePage = () => {
|
||||
// 模拟知识库数据
|
||||
const mockKnowledgeBases = [
|
||||
{
|
||||
id: '1',
|
||||
name: '心血管疾病研究文献库',
|
||||
fileCount: 15,
|
||||
totalSize: '45MB',
|
||||
createdAt: '2025-10-05',
|
||||
status: 'ready',
|
||||
},
|
||||
]
|
||||
const { TabPane } = Tabs;
|
||||
|
||||
// 模拟文档数据
|
||||
const mockDocuments = [
|
||||
{
|
||||
id: '1',
|
||||
name: '高血压治疗指南2024.pdf',
|
||||
size: '3.2MB',
|
||||
uploadedAt: '2025-10-05 14:30',
|
||||
status: 'processed',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: '心血管疾病流行病学研究.docx',
|
||||
size: '1.8MB',
|
||||
uploadedAt: '2025-10-05 15:20',
|
||||
status: 'processing',
|
||||
},
|
||||
]
|
||||
const KnowledgePage: React.FC = () => {
|
||||
const {
|
||||
knowledgeBases,
|
||||
currentKb,
|
||||
documents,
|
||||
loading,
|
||||
error,
|
||||
fetchKnowledgeBases,
|
||||
fetchKnowledgeBaseById,
|
||||
createKnowledgeBase,
|
||||
updateKnowledgeBase,
|
||||
deleteKnowledgeBase,
|
||||
fetchDocuments,
|
||||
deleteDocument,
|
||||
reprocessDocument,
|
||||
setCurrentKb,
|
||||
clearError,
|
||||
} = useKnowledgeBaseStore();
|
||||
|
||||
const kbColumns = [
|
||||
{
|
||||
title: '知识库名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
render: (text: string) => (
|
||||
<Space>
|
||||
<FolderOutlined style={{ color: '#1890ff' }} />
|
||||
<span>{text}</span>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '文档数量',
|
||||
dataIndex: 'fileCount',
|
||||
key: 'fileCount',
|
||||
render: (count: number) => `${count} / 50`,
|
||||
},
|
||||
{
|
||||
title: '总大小',
|
||||
dataIndex: 'totalSize',
|
||||
key: 'totalSize',
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createdAt',
|
||||
key: 'createdAt',
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
render: (status: string) => (
|
||||
<Tag color={status === 'ready' ? 'green' : 'orange'}>
|
||||
{status === 'ready' ? '就绪' : '处理中'}
|
||||
</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
render: () => (
|
||||
<Space>
|
||||
<Button size="small" icon={<UploadOutlined />} disabled>
|
||||
上传
|
||||
</Button>
|
||||
<Button size="small" danger icon={<DeleteOutlined />} disabled>
|
||||
删除
|
||||
</Button>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
]
|
||||
const [createDialogOpen, setCreateDialogOpen] = useState(false);
|
||||
const [editDialogOpen, setEditDialogOpen] = useState(false);
|
||||
const [editingKb, setEditingKb] = useState<KnowledgeBase | null>(null);
|
||||
|
||||
const docColumns = [
|
||||
{
|
||||
title: '文件名',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
render: (text: string) => (
|
||||
<Space>
|
||||
<FileTextOutlined />
|
||||
<span>{text}</span>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '大小',
|
||||
dataIndex: 'size',
|
||||
key: 'size',
|
||||
},
|
||||
{
|
||||
title: '上传时间',
|
||||
dataIndex: 'uploadedAt',
|
||||
key: 'uploadedAt',
|
||||
},
|
||||
{
|
||||
title: '处理状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
render: (status: string) => {
|
||||
if (status === 'processed') {
|
||||
return <Tag color="green">已完成</Tag>
|
||||
}
|
||||
return (
|
||||
<Space>
|
||||
<Tag color="processing" icon={<SyncOutlined spin />}>
|
||||
处理中
|
||||
</Tag>
|
||||
<Progress percent={65} size="small" style={{ width: 100 }} />
|
||||
</Space>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
render: () => (
|
||||
<Button size="small" danger icon={<DeleteOutlined />} disabled>
|
||||
删除
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
]
|
||||
// 初始加载知识库列表
|
||||
useEffect(() => {
|
||||
fetchKnowledgeBases();
|
||||
}, []);
|
||||
|
||||
// 显示错误提示
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
message.error(error);
|
||||
clearError();
|
||||
}
|
||||
}, [error]);
|
||||
|
||||
// 创建知识库
|
||||
const handleCreate = async (name: string, description?: string) => {
|
||||
await createKnowledgeBase(name, description);
|
||||
};
|
||||
|
||||
// 编辑知识库
|
||||
const handleEdit = (kb: KnowledgeBase) => {
|
||||
setEditingKb(kb);
|
||||
setEditDialogOpen(true);
|
||||
};
|
||||
|
||||
// 更新知识库
|
||||
const handleUpdate = async (id: string, name: string, description?: string) => {
|
||||
await updateKnowledgeBase(id, name, description);
|
||||
};
|
||||
|
||||
// 删除知识库
|
||||
const handleDelete = async (kb: KnowledgeBase) => {
|
||||
try {
|
||||
await deleteKnowledgeBase(kb.id);
|
||||
message.success('知识库删除成功');
|
||||
|
||||
// 如果删除的是当前打开的知识库,返回列表
|
||||
if (currentKb?.id === kb.id) {
|
||||
setCurrentKb(null);
|
||||
}
|
||||
} catch (error: any) {
|
||||
message.error(error.message || '删除失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 选择知识库,查看详情
|
||||
const handleSelectKb = async (kb: KnowledgeBase) => {
|
||||
setCurrentKb(kb);
|
||||
await fetchKnowledgeBaseById(kb.id);
|
||||
};
|
||||
|
||||
// 返回知识库列表
|
||||
const handleBackToList = () => {
|
||||
setCurrentKb(null);
|
||||
};
|
||||
|
||||
// 上传成功后刷新文档列表
|
||||
const handleUploadSuccess = async () => {
|
||||
if (currentKb) {
|
||||
await fetchDocuments(currentKb.id);
|
||||
}
|
||||
};
|
||||
|
||||
// 删除文档
|
||||
const handleDeleteDocument = async (doc: Document) => {
|
||||
try {
|
||||
await deleteDocument(doc.id);
|
||||
message.success('文档删除成功');
|
||||
} catch (error: any) {
|
||||
message.error(error.message || '删除失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 重新处理文档
|
||||
const handleReprocessDocument = async (doc: Document) => {
|
||||
try {
|
||||
await reprocessDocument(doc.id);
|
||||
message.success('已开始重新处理');
|
||||
} catch (error: any) {
|
||||
message.error(error.message || '操作失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 轮询文档状态(每5秒)
|
||||
useEffect(() => {
|
||||
if (!currentKb) return;
|
||||
|
||||
const hasProcessing = documents.some(doc =>
|
||||
['uploading', 'parsing', 'indexing'].includes(doc.status)
|
||||
);
|
||||
|
||||
if (!hasProcessing) return;
|
||||
|
||||
const interval = setInterval(() => {
|
||||
fetchDocuments(currentKb.id);
|
||||
}, 5000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [currentKb, documents]);
|
||||
|
||||
return (
|
||||
<div style={{ padding: '24px' }}>
|
||||
{/* 提示信息 */}
|
||||
<Alert
|
||||
message="知识库功能说明"
|
||||
description="每个用户最多可创建3个知识库,每个知识库最多上传50个文件(支持PDF、DOCX格式)。在对话时可以@知识库,让AI基于您的文献进行回答。"
|
||||
type="info"
|
||||
showIcon
|
||||
closable
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
<div style={{ padding: 24, height: '100%', overflow: 'auto' }}>
|
||||
{!currentKb ? (
|
||||
// 知识库列表视图
|
||||
<>
|
||||
<KnowledgeBaseList
|
||||
knowledgeBases={knowledgeBases}
|
||||
loading={loading}
|
||||
onCreateClick={() => setCreateDialogOpen(true)}
|
||||
onEditClick={handleEdit}
|
||||
onDeleteClick={handleDelete}
|
||||
onSelectClick={handleSelectKb}
|
||||
/>
|
||||
|
||||
<Alert
|
||||
message="功能开发中"
|
||||
description="知识库管理功能正在开发中,敬请期待..."
|
||||
type="warning"
|
||||
showIcon
|
||||
icon={<SyncOutlined spin />}
|
||||
style={{ marginBottom: 24 }}
|
||||
/>
|
||||
<CreateKBDialog
|
||||
open={createDialogOpen}
|
||||
onCancel={() => setCreateDialogOpen(false)}
|
||||
onCreate={handleCreate}
|
||||
/>
|
||||
|
||||
{/* 知识库列表 */}
|
||||
<Card
|
||||
title={
|
||||
<Space>
|
||||
<FolderOutlined />
|
||||
<span>我的知识库 (1/3)</span>
|
||||
</Space>
|
||||
}
|
||||
extra={
|
||||
<Button type="primary" icon={<PlusOutlined />} disabled>
|
||||
创建知识库
|
||||
</Button>
|
||||
}
|
||||
style={{ marginBottom: 24 }}
|
||||
>
|
||||
<Table
|
||||
columns={kbColumns}
|
||||
dataSource={mockKnowledgeBases}
|
||||
rowKey="id"
|
||||
pagination={false}
|
||||
/>
|
||||
</Card>
|
||||
<EditKBDialog
|
||||
open={editDialogOpen}
|
||||
knowledgeBase={editingKb}
|
||||
onCancel={() => {
|
||||
setEditDialogOpen(false);
|
||||
setEditingKb(null);
|
||||
}}
|
||||
onUpdate={handleUpdate}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
// 知识库详情视图
|
||||
<div>
|
||||
{/* 返回按钮和标题 */}
|
||||
<div style={{ marginBottom: 24 }}>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<ArrowLeftOutlined />}
|
||||
onClick={handleBackToList}
|
||||
style={{ marginBottom: 16 }}
|
||||
>
|
||||
返回知识库列表
|
||||
</Button>
|
||||
|
||||
<Card style={{ marginBottom: 16 }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<div>
|
||||
<h2 style={{ margin: 0, marginBottom: 8 }}>{currentKb.name}</h2>
|
||||
<p style={{ margin: 0, color: '#8c8c8c' }}>
|
||||
{currentKb.description || '暂无描述'}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => handleEdit(currentKb)}
|
||||
>
|
||||
编辑知识库
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* 文档列表 */}
|
||||
<Card
|
||||
title={
|
||||
<Space>
|
||||
<FileTextOutlined />
|
||||
<span>文档列表:心血管疾病研究文献库</span>
|
||||
</Space>
|
||||
}
|
||||
extra={
|
||||
<Button icon={<UploadOutlined />} disabled>
|
||||
上传文件
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Table
|
||||
columns={docColumns}
|
||||
dataSource={mockDocuments}
|
||||
rowKey="id"
|
||||
pagination={false}
|
||||
/>
|
||||
</Card>
|
||||
{/* 文档管理标签页 */}
|
||||
<Tabs defaultActiveKey="documents">
|
||||
<TabPane tab={`文档管理 (${documents.length})`} key="documents">
|
||||
<div style={{ marginBottom: 24 }}>
|
||||
<DocumentUpload
|
||||
kbId={currentKb.id}
|
||||
onUploadSuccess={handleUploadSuccess}
|
||||
disabled={loading}
|
||||
maxDocuments={50}
|
||||
currentDocumentCount={documents.length}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Card title="文档列表">
|
||||
<DocumentList
|
||||
documents={documents}
|
||||
loading={loading}
|
||||
onDelete={handleDeleteDocument}
|
||||
onReprocess={handleReprocessDocument}
|
||||
/>
|
||||
</Card>
|
||||
</TabPane>
|
||||
|
||||
<TabPane tab="统计信息" key="stats">
|
||||
<Card>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', gap: 16 }}>
|
||||
<div style={{ textAlign: 'center', padding: 16, background: '#f0f5ff', borderRadius: 8 }}>
|
||||
<div style={{ fontSize: 32, fontWeight: 'bold', color: '#1890ff' }}>
|
||||
{documents.length}
|
||||
</div>
|
||||
<div style={{ marginTop: 8, color: '#595959' }}>总文档数</div>
|
||||
</div>
|
||||
|
||||
<div style={{ textAlign: 'center', padding: 16, background: '#f6ffed', borderRadius: 8 }}>
|
||||
<div style={{ fontSize: 32, fontWeight: 'bold', color: '#52c41a' }}>
|
||||
{documents.filter(d => d.status === 'completed').length}
|
||||
</div>
|
||||
<div style={{ marginTop: 8, color: '#595959' }}>已就绪</div>
|
||||
</div>
|
||||
|
||||
<div style={{ textAlign: 'center', padding: 16, background: '#fffbe6', borderRadius: 8 }}>
|
||||
<div style={{ fontSize: 32, fontWeight: 'bold', color: '#faad14' }}>
|
||||
{documents.filter(d => ['uploading', 'parsing', 'indexing'].includes(d.status)).length}
|
||||
</div>
|
||||
<div style={{ marginTop: 8, color: '#595959' }}>处理中</div>
|
||||
</div>
|
||||
|
||||
<div style={{ textAlign: 'center', padding: 16, background: '#fff1f0', borderRadius: 8 }}>
|
||||
<div style={{ fontSize: 32, fontWeight: 'bold', color: '#ff4d4f' }}>
|
||||
{documents.filter(d => d.status === 'error').length}
|
||||
</div>
|
||||
<div style={{ marginTop: 8, color: '#595959' }}>失败</div>
|
||||
</div>
|
||||
|
||||
<div style={{ textAlign: 'center', padding: 16, background: '#f0f0f0', borderRadius: 8 }}>
|
||||
<div style={{ fontSize: 24, fontWeight: 'bold', color: '#262626' }}>
|
||||
{documents.reduce((sum, d) => sum + (d.tokensCount || 0), 0).toLocaleString()}
|
||||
</div>
|
||||
<div style={{ marginTop: 8, color: '#595959' }}>总Token数</div>
|
||||
</div>
|
||||
|
||||
<div style={{ textAlign: 'center', padding: 16, background: '#f0f0f0', borderRadius: 8 }}>
|
||||
<div style={{ fontSize: 24, fontWeight: 'bold', color: '#262626' }}>
|
||||
{documents.reduce((sum, d) => sum + (d.segmentsCount || 0), 0).toLocaleString()}
|
||||
</div>
|
||||
<div style={{ marginTop: 8, color: '#595959' }}>总段落数</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
|
||||
<EditKBDialog
|
||||
open={editDialogOpen}
|
||||
knowledgeBase={currentKb}
|
||||
onCancel={() => setEditDialogOpen(false)}
|
||||
onUpdate={handleUpdate}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default KnowledgePage
|
||||
);
|
||||
};
|
||||
|
||||
export default KnowledgePage;
|
||||
|
||||
Reference in New Issue
Block a user