/** * 教师端 - 知识库管理页面 * 对应原始 index.html 的 knowledge-base section * 复用原始HTML的知识库创建和管理逻辑 */ import { useState, useEffect } from 'react'; import { Card, Form, Input, Upload, Button, Row, Col, message, Progress, Space, Modal, List, Tag } from 'antd'; import { UploadOutlined, DeleteOutlined, FileTextOutlined, FilePdfOutlined, FileWordOutlined, FileImageOutlined, PlusOutlined, ExclamationCircleOutlined, } from '@ant-design/icons'; import { motion } from 'framer-motion'; import knowledgeService from '../../services/knowledgeService'; const { confirm } = Modal; const KnowledgeBase = () => { const [form] = Form.useForm(); const [loading, setLoading] = useState(false); const [knowledgeBases, setKnowledgeBases] = useState([]); const [uploadProgress, setUploadProgress] = useState(0); const [uploading, setUploading] = useState(false); const [fileList, setFileList] = useState([]); useEffect(() => { loadKnowledgeBases(); }, []); // 加载知识库列表 const loadKnowledgeBases = async () => { try { setLoading(true); const res = await knowledgeService.getKnowledgeBases(); setKnowledgeBases(res.data || []); } catch (error) { console.error('加载知识库失败:', error); message.error('加载知识库失败'); } finally { setLoading(false); } }; // 创建知识库 const handleCreateKnowledgeBase = async (values) => { if (fileList.length === 0) { message.warning('请选择要上传的文件'); return; } try { setUploading(true); // 后端只支持单文件上传,所以需要逐个上传 const firstFile = fileList[0]; const formData = new FormData(); formData.append('name', values.name); formData.append('file', firstFile.originFileObj); // 注意:使用 'file' 不是 'files' const res = await knowledgeService.createKnowledgeBase(formData); if (res.success) { message.success('知识库创建成功!'); // 如果有taskId,轮询进度 if (res.data?.task_id) { pollProgress(res.data.task_id); } // 如果还有其他文件,需要逐个添加到知识库 if (fileList.length > 1) { message.info(`正在上传剩余 ${fileList.length - 1} 个文件...`); // 获取创建的知识库ID const kbId = `rag_${values.name}`; // 上传剩余文件 for (let i = 1; i < fileList.length; i++) { const additionalFormData = new FormData(); additionalFormData.append('file', fileList[i].originFileObj); try { const addRes = await knowledgeService.addFileToKnowledgeBase(kbId, additionalFormData); if (addRes.success && addRes.data?.task_id) { pollProgress(addRes.data.task_id); } } catch (error) { console.error(`上传文件 ${fileList[i].name} 失败:`, error); message.error(`文件 ${fileList[i].name} 上传失败`); } } } form.resetFields(); setFileList([]); setUploadProgress(0); loadKnowledgeBases(); } } catch (error) { message.error('创建知识库失败'); console.error('创建知识库失败:', error); } finally { setUploading(false); } }; // 轮询文件处理进度 const pollProgress = async (taskId) => { const maxAttempts = 60; // 最多轮询60次(约1分钟) let attempts = 0; const poll = async () => { try { const res = await knowledgeService.getProgress(taskId); if (res.data?.status === 'completed') { setUploadProgress(100); message.success('文件处理完成'); loadKnowledgeBases(); return; } if (res.data?.status === 'failed') { message.error('文件处理失败'); return; } if (res.data?.progress) { setUploadProgress(res.data.progress); } attempts++; if (attempts < maxAttempts) { setTimeout(poll, 1000); // 每秒轮询一次 } } catch (error) { console.error('获取进度失败:', error); } }; poll(); }; // 删除知识库 const handleDeleteKnowledgeBase = (kbId, kbName) => { confirm({ title: '确认删除', icon: , content: `确定要删除知识库 "${kbName}" 吗?此操作不可恢复。`, okText: '删除', okType: 'danger', cancelText: '取消', onOk: async () => { try { await knowledgeService.deleteKnowledgeBase(kbId); message.success('知识库已删除'); loadKnowledgeBases(); } catch (error) { message.error('删除失败'); } }, }); }; // 删除知识库中的文件 const handleDeleteFile = (indexId, fileName, kbName) => { confirm({ title: '确认删除文件', icon: , content: `确定要从 "${kbName}" 中删除文件 "${fileName}" 吗?`, okText: '删除', okType: 'danger', cancelText: '取消', onOk: async () => { try { await knowledgeService.deleteFile(indexId, fileName); message.success('文件已删除'); loadKnowledgeBases(); } catch (error) { message.error('删除文件失败'); } }, }); }; // 添加文件到现有知识库 const handleAddFiles = async (kbId) => { // TODO: 实现文件添加功能 message.info('添加文件功能开发中'); }; // 获取文件图标 const getFileIcon = (fileName) => { if (!fileName || typeof fileName !== 'string') { return ; } const ext = fileName.split('.').pop().toLowerCase(); if (['txt', 'md'].includes(ext)) { return ; } if (['pdf'].includes(ext)) { return ; } if (['doc', 'docx'].includes(ext)) { return ; } if (['jpg', 'jpeg', 'png', 'gif', 'bmp'].includes(ext)) { return ; } return ; }; // 上传配置 const uploadProps = { fileList, onChange: ({ fileList: newFileList }) => setFileList(newFileList), beforeUpload: (file) => { const isValidType = [ 'text/plain', 'text/markdown', 'application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'image/jpeg', 'image/png', 'image/gif', 'image/bmp', ].includes(file.type); if (!isValidType) { message.error('不支持的文件类型'); return Upload.LIST_IGNORE; } const isLt16M = file.size / 1024 / 1024 < 16; if (!isLt16M) { message.error('文件大小不能超过 16MB'); return Upload.LIST_IGNORE; } return false; // 阻止自动上传 }, multiple: true, }; return (

知识库管理

创建和管理您的教学资料知识库

{/* 左侧:创建知识库 */} 创建新知识库 } style={{ border: '1px solid #e5e7eb', borderRadius: '12px' }} >
支持格式:TXT, MD, PDF, DOC, DOCX, JPG, PNG, GIF, BMP
单个文件最大 16MB
{uploading && uploadProgress > 0 && ( )}
{/* 右侧:现有知识库列表 */} 现有知识库 } style={{ border: '1px solid #e5e7eb', borderRadius: '12px' }} loading={loading} > {knowledgeBases.length > 0 ? ( {knowledgeBases.map((kb) => ( {/* 知识库头部 */}

{kb.name}

知识库ID: {kb.id}
{/* 文件列表 */} {kb.files && kb.files.length > 0 ? (
( } onClick={() => handleDeleteFile(kb.id, file.name, kb.name)} > 删除 , ]} > {file.name} } description={ {file.size || '未知大小'} } /> )} />
) : (
暂无文件
)} {/* 知识库元数据 */}
{kb.fileCount || 0} 个文件 索引: {kb.id}
))}
) : (
还没有创建任何知识库
在左侧创建您的第一个知识库
)}
); }; export default KnowledgeBase;