diff --git a/src/pages/KnowledgeBase/Detail/SettingsTab.jsx b/src/pages/KnowledgeBase/Detail/SettingsTab.jsx index 98454f6..8dba641 100644 --- a/src/pages/KnowledgeBase/Detail/SettingsTab.jsx +++ b/src/pages/KnowledgeBase/Detail/SettingsTab.jsx @@ -13,6 +13,7 @@ import Breadcrumb from './components/Breadcrumb'; import KnowledgeBaseForm from './components/KnowledgeBaseForm'; import DeleteConfirmModal from './components/DeleteConfirmModal'; import UserPermissionsManager from './components/UserPermissionsManager'; +import FileUploadModal from './components/FileUploadModal'; // 部门和组别的映射关系 const departmentGroups = { @@ -47,6 +48,7 @@ export default function SettingsTab({ knowledgeBase }) { const [isSubmitting, setIsSubmitting] = useState(false); const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); const [availableGroups, setAvailableGroups] = useState([]); + const [showUploadModal, setShowUploadModal] = useState(false); // 当部门变化时,更新可选的组别 useEffect(() => { @@ -93,7 +95,7 @@ export default function SettingsTab({ knowledgeBase }) { allowed = ['admin', 'member', 'private'].includes(value); } else { // 普通成员只能选择公共和private - allowed = ['admin', 'private'].includes(value); + allowed = ['admin', 'private'].includes(value); } if (!allowed) { @@ -334,6 +336,26 @@ export default function SettingsTab({ knowledgeBase }) { availableGroups={availableGroups} /> + {/* Document Upload Section */} +
+
+
文档管理
+

+ 上传文档到知识库,支持PDF、Word、Excel、TXT、Markdown和CSV等格式。 +

+ +
+
+ + {/* File Upload Modal */} + setShowUploadModal(false)} + knowledgeBaseId={knowledgeBase.id} + /> + {/* User Permissions Manager */} {/* */} diff --git a/src/pages/KnowledgeBase/Detail/components/FileUploadModal.jsx b/src/pages/KnowledgeBase/Detail/components/FileUploadModal.jsx index 915f85e..0839ceb 100644 --- a/src/pages/KnowledgeBase/Detail/components/FileUploadModal.jsx +++ b/src/pages/KnowledgeBase/Detail/components/FileUploadModal.jsx @@ -1,23 +1,17 @@ -import React, { useRef, useEffect } from 'react'; +import React, { useRef, useState, useEffect } from 'react'; +import { useDispatch } from 'react-redux'; +import { uploadDocument, getKnowledgeBaseById } from '../../../../store/knowledgeBase/knowledgeBase.thunks'; /** * 文件上传模态框组件 */ -const FileUploadModal = ({ - show, - newFile, - fileErrors, - isSubmitting, - onClose, - onDescriptionChange, - onFileChange, - onFileDrop, - onDragOver, - onUploadAreaClick, - onUpload, -}) => { +const FileUploadModal = ({ show, knowledgeBaseId, onClose }) => { + const dispatch = useDispatch(); const fileInputRef = useRef(null); const modalRef = useRef(null); + const [selectedFile, setSelectedFile] = useState(null); + const [isUploading, setIsUploading] = useState(false); + const [fileError, setFileError] = useState(''); // 处理上传区域点击事件 const handleUploadAreaClick = () => { @@ -28,13 +22,76 @@ const FileUploadModal = ({ const handleDragOver = (e) => { e.preventDefault(); e.stopPropagation(); - onDragOver?.(e); }; const handleDrop = (e) => { e.preventDefault(); e.stopPropagation(); - onFileDrop?.(e); + + if (e.dataTransfer.files && e.dataTransfer.files.length > 0) { + handleFileSelected(e.dataTransfer.files[0]); + } + }; + + const handleFileChange = (e) => { + if (e.target.files && e.target.files.length > 0) { + handleFileSelected(e.target.files[0]); + } + }; + + const handleFileSelected = (file) => { + setFileError(''); + setSelectedFile(file); + }; + + const resetFileInput = () => { + setSelectedFile(null); + setFileError(''); + if (fileInputRef.current) { + fileInputRef.current.value = ''; + } + }; + + const handleUpload = async () => { + if (!selectedFile) { + setFileError('请选择要上传的文件'); + return; + } + + setIsUploading(true); + + try { + await dispatch( + uploadDocument({ + knowledge_base_id: knowledgeBaseId, + file: selectedFile, + }) + ).unwrap(); + + // 成功上传后刷新知识库信息以更新文件列表 + dispatch(getKnowledgeBaseById(knowledgeBaseId)); + + // Reset the file input + resetFileInput(); + + // 不要在上传成功后关闭模态框,允许用户继续上传或手动关闭 + } catch (error) { + console.error('Upload failed:', error); + setFileError('文件上传失败: ' + (error?.message || '未知错误')); + + // 清空选中的文件 + resetFileInput(); + } finally { + setIsUploading(false); + } + }; + + const handleClose = () => { + // 只有在非上传状态才允许关闭 + if (!isUploading) { + resetFileInput(); + onClose(); + } }; // 清理函数 @@ -78,64 +135,60 @@ const FileUploadModal = ({ }} >
-
上传文件
- +
上传文档
+
- {newFile.file ? ( + {selectedFile ? (

已选择文件:

-

{newFile.file.name}

+

{selectedFile.name}

) : (

点击或拖拽文件到此处上传

-

支持 PDF, DOCX, TXT, CSV 等格式

+

+ 支持 PDF, Word, Excel, TXT, Markdown, CSV 等格式 +

)} - {fileErrors.file &&
{fileErrors.file}
} -
-
- - + {fileError &&
{fileError}
}
-
diff --git a/src/services/mockApi.js b/src/services/mockApi.js index 46c9355..5c758da 100644 --- a/src/services/mockApi.js +++ b/src/services/mockApi.js @@ -674,7 +674,7 @@ export const mockGet = async (url, config = {}) => { throw { response: { status: 404, data: { message: 'Not found' } } }; }; -export const mockPost = async (url, data) => { +export const mockPost = async (url, data, isMultipart = false) => { console.log(`[MOCK API] POST ${url}`, data); // Simulate network delay @@ -857,6 +857,26 @@ export const mockPost = async (url, data) => { }; } + // 上传知识库文档 + if (url.match(/\/knowledge-bases\/([^/]+)\/upload_document\//)) { + const knowledge_base_id = url.match(/\/knowledge-bases\/([^/]+)\/upload_document\//)[1]; + const file = isMultipart ? data.get('file') : null; + + return { + data: { + code: 200, + message: 'Document uploaded successfully', + data: { + id: `doc-${Date.now()}`, + knowledge_base_id: knowledge_base_id, + filename: file ? file.name : 'mock-document.pdf', + status: 'processing', + created_at: new Date().toISOString(), + }, + }, + }; + } + throw { response: { status: 404, data: { message: 'Not found' } } }; }; diff --git a/src/store/knowledgeBase/knowledgeBase.slice.js b/src/store/knowledgeBase/knowledgeBase.slice.js index c95515c..65defd3 100644 --- a/src/store/knowledgeBase/knowledgeBase.slice.js +++ b/src/store/knowledgeBase/knowledgeBase.slice.js @@ -8,6 +8,7 @@ import { searchKnowledgeBases, requestKnowledgeBaseAccess, getKnowledgeBaseById, + uploadDocument, } from './knowledgeBase.thunks'; const initialState = { @@ -27,6 +28,7 @@ const initialState = { batchLoading: false, editStatus: 'idle', requestAccessStatus: 'idle', + uploadStatus: 'idle', }; const knowledgeBaseSlice = createSlice({ @@ -180,6 +182,18 @@ const knowledgeBaseSlice = createSlice({ .addCase(getKnowledgeBaseById.rejected, (state, action) => { state.loading = false; state.error = action.payload || 'Failed to get knowledge base details'; + }) + + // 上传文档 + .addCase(uploadDocument.pending, (state) => { + state.uploadStatus = 'loading'; + }) + .addCase(uploadDocument.fulfilled, (state) => { + state.uploadStatus = 'successful'; + }) + .addCase(uploadDocument.rejected, (state, action) => { + state.uploadStatus = 'failed'; + state.error = action.payload || 'Failed to upload document'; }); }, }); diff --git a/src/store/knowledgeBase/knowledgeBase.thunks.js b/src/store/knowledgeBase/knowledgeBase.thunks.js index 187af7f..663f68c 100644 --- a/src/store/knowledgeBase/knowledgeBase.thunks.js +++ b/src/store/knowledgeBase/knowledgeBase.thunks.js @@ -1,5 +1,5 @@ import { createAsyncThunk } from '@reduxjs/toolkit'; -import { get, post, put, del } from '../../services/api'; +import { get, post, put, del, upload } from '../../services/api'; import { showNotification } from '../notification.slice'; /** @@ -20,7 +20,7 @@ export const fetchKnowledgeBases = createAsyncThunk( return response.data; } catch (error) { console.log(error); - + return rejectWithValue(error.response?.data.error.message || 'Failed to fetch knowledge bases'); } } @@ -196,3 +196,44 @@ export const requestKnowledgeBaseAccess = createAsyncThunk( } } ); + +/** + * Upload a document to a knowledge base + * @param {Object} params - Upload parameters + * @param {string} params.knowledge_base_id - Knowledge base ID + * @param {File} params.file - File to upload + */ +export const uploadDocument = createAsyncThunk( + 'knowledgeBase/uploadDocument', + async ({ knowledge_base_id, file }, { rejectWithValue, dispatch }) => { + try { + const formData = new FormData(); + formData.append('file', file); + + const response = await post(`/knowledge-bases/${knowledge_base_id}/upload_document/`, formData, true); + + dispatch( + showNotification({ + type: 'success', + message: `文档 ${file.name} 上传成功`, + }) + ); + + // 处理新的返回格式 + if (response.data && response.data.code === 200) { + return response.data.data; + } + + return response.data; + } catch (error) { + const errorMessage = error.response?.data?.message || error.message || '文档上传失败'; + dispatch( + showNotification({ + type: 'danger', + message: errorMessage, + }) + ); + return rejectWithValue(errorMessage); + } + } +);