/** * 系统知识库详情页 * * 管理知识库中的文档 */ import React, { useState, useEffect } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { Card, Button, Table, Space, Upload, message, Popconfirm, Tag, Typography, Empty, Spin, Progress, Breadcrumb, Descriptions, Tooltip, } from 'antd'; import { ArrowLeftOutlined, UploadOutlined, DeleteOutlined, DownloadOutlined, FileTextOutlined, FilePdfOutlined, FileWordOutlined, FileUnknownOutlined, ClockCircleOutlined, DatabaseOutlined, } from '@ant-design/icons'; import type { ColumnsType } from 'antd/es/table'; import * as systemKbApi from '../api/systemKbApi'; import type { SystemKb, SystemKbDocument } from '../types/systemKb'; const { Title, Text } = Typography; /** 文件图标映射 */ const getFileIcon = (fileType: string | null) => { switch (fileType) { case 'pdf': return ; case 'doc': case 'docx': return ; case 'txt': case 'md': return ; default: return ; } }; /** 格式化文件大小 */ const formatFileSize = (bytes: number | null) => { if (!bytes) return '-'; if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; }; /** 格式化日期 */ const formatDate = (dateStr: string) => { const date = new Date(dateStr); return date.toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', }); }; const SystemKbDetailPage: React.FC = () => { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); const [kb, setKb] = useState(null); const [documents, setDocuments] = useState([]); const [loading, setLoading] = useState(true); const [docsLoading, setDocsLoading] = useState(false); const [uploadProgress, setUploadProgress] = useState(null); const [selectedRowKeys, setSelectedRowKeys] = useState([]); // 加载知识库详情 const loadKnowledgeBase = async () => { if (!id) return; setLoading(true); try { const data = await systemKbApi.getKnowledgeBase(id); setKb(data); } catch (error) { message.error('加载知识库失败'); navigate('/admin/system-kb'); } finally { setLoading(false); } }; // 加载文档列表 const loadDocuments = async () => { if (!id) return; setDocsLoading(true); try { const data = await systemKbApi.listDocuments(id); setDocuments(data); } catch (error) { message.error('加载文档列表失败'); } finally { setDocsLoading(false); } }; useEffect(() => { loadKnowledgeBase(); loadDocuments(); }, [id]); // 上传文档 const handleUpload = async (file: File) => { if (!id) return; setUploadProgress(0); try { const result = await systemKbApi.uploadDocument( id, file, (percent) => setUploadProgress(percent) ); message.success(`上传成功:${result.chunkCount} 个分块,${result.tokenCount.toLocaleString()} tokens`); await loadDocuments(); await loadKnowledgeBase(); // 刷新统计 } catch (error: any) { message.error(error.response?.data?.error || '上传失败'); } finally { setUploadProgress(null); } }; // 删除单个文档 const handleDeleteDoc = async (doc: SystemKbDocument) => { if (!id) return; try { await systemKbApi.deleteDocument(id, doc.id); message.success('删除成功'); await loadDocuments(); await loadKnowledgeBase(); } catch (error) { message.error('删除失败'); } }; // 批量删除 const handleBatchDelete = async () => { if (!id || selectedRowKeys.length === 0) return; try { for (const docId of selectedRowKeys) { await systemKbApi.deleteDocument(id, docId as string); } message.success(`成功删除 ${selectedRowKeys.length} 个文档`); setSelectedRowKeys([]); await loadDocuments(); await loadKnowledgeBase(); } catch (error) { message.error('删除失败'); } }; // 下载文档 const handleDownload = async (doc: SystemKbDocument) => { if (!id) return; try { const result = await systemKbApi.getDocumentDownloadUrl(id, doc.id); // 创建临时链接并触发下载 const link = document.createElement('a'); link.href = result.url; link.download = result.filename; link.target = '_blank'; document.body.appendChild(link); link.click(); document.body.removeChild(link); } catch (error) { message.error('获取下载链接失败'); } }; // 表格列定义 const columns: ColumnsType = [ { title: '文件名', dataIndex: 'filename', key: 'filename', render: (text, record) => ( {getFileIcon(record.fileType)} {text} ), }, { title: '大小', dataIndex: 'fileSize', key: 'fileSize', width: 100, render: (val) => formatFileSize(val), }, { title: 'Tokens', dataIndex: 'tokenCount', key: 'tokenCount', width: 100, align: 'right', render: (val) => val?.toLocaleString() || '-', }, { title: '状态', dataIndex: 'status', key: 'status', width: 100, render: (status, record) => { const statusConfig: Record = { ready: { color: 'success', text: '就绪' }, processing: { color: 'processing', text: '处理中' }, failed: { color: 'error', text: '失败' }, pending: { color: 'warning', text: '等待' }, }; const config = statusConfig[status] || { color: 'default', text: status }; return ( {config.text} ); }, }, { title: '上传时间', dataIndex: 'createdAt', key: 'createdAt', width: 160, render: (val) => formatDate(val), }, { title: '操作', key: 'action', width: 120, render: (_, record) => (
{kb.name} {kb.code}
{/* 知识库信息卡片 */} 文档数
), children: {kb.documentCount}, }, { key: 'tokens', label: ( 总 Tokens ), children: {kb.totalTokens.toLocaleString()}, }, { key: 'created', label: ( 创建时间 ), children: formatDate(kb.createdAt), }, { key: 'description', label: '描述', children: kb.description || , }, ]} /> {/* 文档管理卡片 */} 文档列表 {documents.length} 个 } extra={ {selectedRowKeys.length > 0 && ( )} { handleUpload(file); return false; }} > } > {/* 上传进度 */} {uploadProgress !== null && (
正在上传并处理文档(向量化)...
)} {/* 文档表格 */} {documents.length > 0 ? ( 10 ? { pageSize: 10 } : false} /> ) : ( { handleUpload(file); return false; }} > )} ); }; export default SystemKbDetailPage;