KnowledgeBase_frontend/src/pages/KnowledgeBase/KnowledgeBase.jsx

543 lines
19 KiB
React
Raw Normal View History

import React, { useState, useEffect, useCallback } from 'react';
2025-03-04 07:22:05 +08:00
import { useNavigate } from 'react-router-dom';
2025-03-07 23:59:53 +08:00
import { useDispatch, useSelector } from 'react-redux';
2025-03-01 08:34:56 +08:00
import { showNotification } from '../../store/notification.slice';
2025-03-07 23:59:53 +08:00
import {
fetchKnowledgeBases,
searchKnowledgeBases,
createKnowledgeBase,
deleteKnowledgeBase,
requestKnowledgeBaseAccess,
2025-03-07 23:59:53 +08:00
} from '../../store/knowledgeBase/knowledgeBase.thunks';
import { clearSearchResults } from '../../store/knowledgeBase/knowledgeBase.slice';
2025-03-01 08:34:56 +08:00
import SvgIcon from '../../components/SvgIcon';
2025-03-13 09:14:25 +08:00
import AccessRequestModal from '../../components/AccessRequestModal';
import CreateKnowledgeBaseModal from '../../components/CreateKnowledgeBaseModal';
import Pagination from '../../components/Pagination';
import SearchBar from '../../components/SearchBar';
import ApiModeSwitch from '../../components/ApiModeSwitch';
2025-02-27 06:54:19 +08:00
2025-03-07 23:59:53 +08:00
// 导入拆分的组件
import KnowledgeBaseList from './components/KnowledgeBaseList';
2025-02-27 06:54:19 +08:00
export default function KnowledgeBase() {
2025-03-01 05:48:24 +08:00
const dispatch = useDispatch();
2025-03-04 07:22:05 +08:00
const navigate = useNavigate();
const [showCreateModal, setShowCreateModal] = useState(false);
2025-03-04 08:37:47 +08:00
const [showAccessRequestModal, setShowAccessRequestModal] = useState(false);
const [formErrors, setFormErrors] = useState({});
2025-03-13 09:14:25 +08:00
const [accessRequestKnowledgeBase, setAccessRequestKnowledgeBase] = useState({
2025-03-04 08:37:47 +08:00
id: '',
title: '',
});
2025-03-13 09:14:25 +08:00
const [isSubmittingRequest, setIsSubmittingRequest] = useState(false);
const [createdKnowledgeBaseId, setCreatedKnowledgeBaseId] = useState(null);
2025-03-13 09:14:25 +08:00
// 获取当前用户信息
const currentUser = useSelector((state) => state.auth.user);
2025-03-04 07:22:05 +08:00
const [newKnowledgeBase, setNewKnowledgeBase] = useState({
2025-03-07 23:59:53 +08:00
name: '',
desc: '',
2025-03-13 09:14:25 +08:00
type: 'private', // 默认为私有知识库
department: currentUser?.department || '',
group: currentUser?.group || '',
2025-03-04 07:22:05 +08:00
});
2025-03-01 05:48:24 +08:00
2025-03-07 23:59:53 +08:00
// Search state
const [searchKeyword, setSearchKeyword] = useState('');
2025-03-23 10:53:37 +08:00
const [isSearchDropdownOpen, setIsSearchDropdownOpen] = useState(false);
2025-03-07 23:59:53 +08:00
// Pagination state
const [pagination, setPagination] = useState({
page: 1,
page_size: 10,
});
// Get knowledge bases from Redux store
// 更新为新的Redux状态结构
const knowledgeBases = useSelector((state) => state.knowledgeBase.knowledgeBases);
const loading = useSelector((state) => state.knowledgeBase.loading);
const paginationData = useSelector((state) => state.knowledgeBase.pagination);
const error = useSelector((state) => state.knowledgeBase.error);
const operationStatus = useSelector((state) => state.knowledgeBase.editStatus);
const operationError = useSelector((state) => state.knowledgeBase.error);
// 从Redux获取搜索结果和加载状态
const searchResults = useSelector((state) => state.knowledgeBase.searchResults);
const searchLoading = useSelector((state) => state.knowledgeBase.searchLoading);
2025-03-07 23:59:53 +08:00
// Fetch knowledge bases when component mounts or pagination changes
useEffect(() => {
2025-03-23 10:53:37 +08:00
// 无论是否在搜索,都正常获取知识库列表
dispatch(fetchKnowledgeBases(pagination));
}, [dispatch, pagination.page, pagination.page_size]);
// Show loading state while fetching data
const isLoading = loading;
// Show error notification if fetch fails
useEffect(() => {
if (!isLoading && error) {
2025-03-07 23:59:53 +08:00
dispatch(
2025-03-23 10:53:37 +08:00
showNotification({
message: `获取知识库列表失败: ${error.message || error}`,
type: 'danger',
2025-03-07 23:59:53 +08:00
})
);
}
2025-03-23 10:53:37 +08:00
}, [isLoading, error, dispatch]);
2025-03-07 23:59:53 +08:00
2025-03-23 10:53:37 +08:00
// Show notification for operation status
useEffect(() => {
if (operationStatus === 'successful') {
// 操作成功通知由具体函数处理,这里只刷新列表
// Refresh the list after successful operation
dispatch(fetchKnowledgeBases(pagination));
} else if (operationStatus === 'failed' && operationError) {
dispatch(
showNotification({
message: `操作失败: ${operationError.message || operationError}`,
type: 'danger',
})
);
}
}, [operationStatus, operationError, dispatch, pagination]);
2025-03-07 23:59:53 +08:00
// Handle search input change
const handleSearchInputChange = (e) => {
const value = e.target.value;
setSearchKeyword(value);
2025-03-23 10:53:37 +08:00
// 如果搜索框清空,关闭下拉框
if (!value.trim()) {
dispatch(clearSearchResults());
2025-03-23 10:53:37 +08:00
setIsSearchDropdownOpen(false);
}
2025-03-07 23:59:53 +08:00
};
2025-03-23 10:53:37 +08:00
// Handle search submit - 只影响下拉框,不影响主列表
2025-03-07 23:59:53 +08:00
const handleSearch = (e) => {
e.preventDefault();
if (searchKeyword.trim()) {
2025-03-23 10:53:37 +08:00
// 只设置下拉框搜索状态不设置全局isSearching状态
2025-03-07 23:59:53 +08:00
dispatch(
searchKnowledgeBases({
keyword: searchKeyword,
page: 1,
2025-03-23 10:53:37 +08:00
page_size: 5, // 下拉框只显示少量结果
2025-03-07 23:59:53 +08:00
})
);
2025-03-23 10:53:37 +08:00
setIsSearchDropdownOpen(true);
2025-03-07 23:59:53 +08:00
} else {
2025-03-23 10:53:37 +08:00
// 清空搜索及关闭下拉框
2025-03-07 23:59:53 +08:00
handleClearSearch();
}
};
// Handle clear search
const handleClearSearch = () => {
setSearchKeyword('');
2025-03-23 10:53:37 +08:00
// 不影响主列表显示,只关闭下拉框
setIsSearchDropdownOpen(false);
dispatch(clearSearchResults());
2025-03-07 23:59:53 +08:00
};
// Handle pagination change
const handlePageChange = (newPage) => {
setPagination((prev) => ({
...prev,
page: newPage,
}));
};
// Handle page size change
const handlePageSizeChange = (newPageSize) => {
setPagination({
page: 1, // Reset to first page when changing page size
page_size: newPageSize,
});
};
2025-02-27 06:54:19 +08:00
2025-03-04 07:22:05 +08:00
const handleInputChange = (e) => {
const { name, value } = e.target;
const isAdmin = currentUser?.role === 'admin';
const isLeader = currentUser?.role === 'leader';
// 根据用户角色允许修改部门和组别字段
2025-03-13 09:14:25 +08:00
if (name === 'department' || name === 'group') {
// 仅管理员可以修改部门
if (name === 'department' && !isAdmin) {
return;
}
// 仅管理员和组长可以修改组别
if (name === 'group' && !isAdmin && !isLeader) {
return;
}
// 更新字段值
setNewKnowledgeBase((prev) => ({
...prev,
[name]: value,
}));
// 如果更新部门,重置组别
if (name === 'department') {
setNewKnowledgeBase((prev) => ({
...prev,
group: '', // 部门变更时重置组别
}));
}
2025-03-13 09:14:25 +08:00
return;
}
// 检查用户是否有权限选择指定的知识库类型
if (name === 'type') {
const role = currentUser?.role;
let allowed = false;
// 根据角色判断可以选择的知识库类型
if (role === 'admin') {
// 管理员可以选择任何类型
allowed = ['admin', 'leader', 'member', 'private', 'secret'].includes(value);
} else if (role === 'leader') {
// 组长只能选择 member 和 private
allowed = ['member', 'private'].includes(value);
} else {
// 普通成员只能选择 private
allowed = value === 'private';
}
if (!allowed) {
dispatch(
showNotification({
message: '您没有权限创建此类型的知识库',
type: 'warning',
})
);
return;
}
}
2025-03-04 07:22:05 +08:00
setNewKnowledgeBase((prev) => ({
...prev,
[name]: value,
}));
2025-03-04 08:37:47 +08:00
// Clear error when user types
if (formErrors[name]) {
setFormErrors((prev) => ({
...prev,
[name]: '',
}));
}
2025-03-04 07:22:05 +08:00
};
2025-03-04 08:37:47 +08:00
const validateCreateForm = () => {
const errors = {};
const isAdmin = currentUser?.role === 'admin';
const isLeader = currentUser?.role === 'leader';
// 只有member类型知识库需要选择组别私有知识库不需要
const needSelectGroup = newKnowledgeBase.type === 'member';
// 私有知识库不需要选择部门和组别
const isPrivate = newKnowledgeBase.type === 'private';
2025-03-04 08:37:47 +08:00
2025-03-07 23:59:53 +08:00
if (!newKnowledgeBase.name.trim()) {
errors.name = '请输入知识库名称';
2025-03-04 08:37:47 +08:00
}
2025-03-07 23:59:53 +08:00
if (!newKnowledgeBase.desc.trim()) {
errors.desc = '请输入知识库描述';
2025-03-04 08:37:47 +08:00
}
2025-03-13 09:14:25 +08:00
if (!newKnowledgeBase.type) {
errors.type = '请选择知识库类型';
2025-03-04 08:37:47 +08:00
}
// 对于member级别的知识库检查是否选择了部门和组别
if (needSelectGroup && !isPrivate) {
// 管理员必须选择部门
if (isAdmin && !newKnowledgeBase.department) {
errors.department = '创建member级别知识库时必须选择部门';
}
// 所有用户创建member级别知识库时必须选择组别
if (!newKnowledgeBase.group) {
errors.group = '创建member级别知识库时必须选择组别';
}
}
2025-03-13 09:14:25 +08:00
setFormErrors(errors);
2025-03-04 08:37:47 +08:00
return Object.keys(errors).length === 0;
};
const handleCreateKnowledgeBase = async () => {
2025-03-04 08:37:47 +08:00
// Validate form
if (!validateCreateForm()) {
2025-03-04 07:22:05 +08:00
return;
}
try {
// 私有知识库不需要部门和组别信息
const isPrivate = newKnowledgeBase.type === 'private';
// Dispatch create knowledge base action
const resultAction = await dispatch(
createKnowledgeBase({
name: newKnowledgeBase.name,
desc: newKnowledgeBase.desc,
description: newKnowledgeBase.desc,
type: newKnowledgeBase.type,
department: !isPrivate ? newKnowledgeBase.department : '',
group: !isPrivate ? newKnowledgeBase.group : '',
})
);
console.log('创建知识库返回数据:', resultAction);
// Check if the action was successful
if (createKnowledgeBase.fulfilled.match(resultAction)) {
console.log('创建成功payload:', resultAction.payload);
const { knowledge_base } = resultAction.payload;
const { id } = knowledge_base;
// Get ID from payload and navigate
if (id) {
console.log('新知识库ID:', id);
// 显示成功通知
dispatch(
showNotification({
message: '知识库创建成功',
type: 'success',
})
);
// 直接导航到新创建的知识库详情页
navigate(`/knowledge-base/${id}`);
} else {
console.error('无法获取新知识库ID:', resultAction.payload);
dispatch(
showNotification({
message: '创建成功但无法获取知识库ID',
type: 'warning',
})
);
}
} else {
console.error('创建知识库失败:', resultAction.error);
dispatch(
showNotification({
message: `创建知识库失败: ${resultAction.error?.message || '未知错误'}`,
type: 'danger',
})
);
}
} catch (error) {
console.error('创建知识库出错:', error);
dispatch(
showNotification({
message: `创建知识库出错: ${error.message || '未知错误'}`,
type: 'danger',
})
);
}
2025-03-04 07:22:05 +08:00
// Reset form and close modal
2025-03-13 09:14:25 +08:00
setNewKnowledgeBase({ name: '', desc: '', type: 'private', department: '', group: '' });
2025-03-04 08:37:47 +08:00
setFormErrors({});
2025-03-04 07:22:05 +08:00
setShowCreateModal(false);
};
2025-03-07 23:59:53 +08:00
// Handle card click to navigate to knowledge base detail
2025-03-13 09:14:25 +08:00
const handleCardClick = (id, permissions) => {
// 检查用户是否有读取权限
if (!permissions || permissions.can_read === false) {
dispatch(
showNotification({
message: '您没有访问此知识库的权限,请先申请权限',
type: 'warning',
})
);
return;
}
// 有权限则跳转到详情页
2025-03-04 07:22:05 +08:00
navigate(`/knowledge-base/${id}/datasets`);
};
2025-03-04 08:37:47 +08:00
const handleRequestAccess = (id, title) => {
2025-03-13 09:14:25 +08:00
setAccessRequestKnowledgeBase({
2025-03-04 08:37:47 +08:00
id,
title,
2025-03-13 09:14:25 +08:00
});
2025-03-04 08:37:47 +08:00
setShowAccessRequestModal(true);
};
2025-03-13 09:14:25 +08:00
const handleSubmitAccessRequest = async (requestData) => {
setIsSubmittingRequest(true);
2025-03-04 08:37:47 +08:00
2025-03-13 09:14:25 +08:00
try {
// 使用权限服务发送请求
await requestKnowledgeBaseAccess(requestData);
2025-03-04 08:37:47 +08:00
2025-03-13 09:14:25 +08:00
dispatch(
showNotification({
message: '权限申请已提交',
type: 'success',
})
);
// Close modal
setShowAccessRequestModal(false);
} catch (error) {
dispatch(
showNotification({
message: `权限申请失败: ${error.response?.data?.message || '请稍后重试'}`,
type: 'danger',
})
);
} finally {
setIsSubmittingRequest(false);
}
2025-03-04 08:37:47 +08:00
};
const handleDelete = (e, id) => {
e.preventDefault();
e.stopPropagation();
// Dispatch delete knowledge base action
dispatch(deleteKnowledgeBase(id))
.unwrap()
.then(() => {
dispatch(
showNotification({
message: '知识库已删除',
type: 'success',
})
);
})
.catch((error) => {
dispatch(
showNotification({
message: `删除失败: ${error.message || '未知错误'}`,
type: 'danger',
})
);
});
};
2025-03-07 23:59:53 +08:00
// Calculate total pages
2025-03-23 10:53:37 +08:00
const totalPages = Math.ceil(paginationData.total / pagination.page_size);
2025-03-07 23:59:53 +08:00
2025-03-13 09:14:25 +08:00
// 打开创建知识库弹窗
const handleOpenCreateModal = () => {
const isAdmin = currentUser?.role === 'admin';
const isLeader = currentUser?.role === 'leader';
// 默认知识库类型基于用户角色
let defaultType = 'private';
// 初始部门和组别
let department = currentUser?.department || '';
let group = currentUser?.group || '';
setNewKnowledgeBase({
name: '',
desc: '',
type: defaultType,
department: department,
group: group,
});
setFormErrors({});
2025-03-13 09:14:25 +08:00
setShowCreateModal(true);
};
// 处理点击搜索结果
const handleSearchResultClick = (id, permissions) => {
if (permissions?.can_read) {
navigate(`/knowledge-base/${id}/datasets`);
}
};
2025-02-27 06:54:19 +08:00
return (
<div className='knowledge-base container my-4'>
<div className='api-mode-control mb-3'>
<ApiModeSwitch />
</div>
2025-02-27 06:54:19 +08:00
<div className='d-flex justify-content-between align-items-center mb-3'>
2025-03-07 23:59:53 +08:00
<SearchBar
searchKeyword={searchKeyword}
2025-03-23 10:53:37 +08:00
isSearching={isSearchDropdownOpen} // 用于控制下拉框显示
2025-03-07 23:59:53 +08:00
onSearchChange={handleSearchInputChange}
onSearch={handleSearch}
onClearSearch={handleClearSearch}
2025-03-13 09:14:25 +08:00
placeholder='搜索知识库...'
searchResults={searchResults}
isSearchLoading={searchLoading}
onResultClick={handleSearchResultClick}
onRequestAccess={handleRequestAccess}
2025-03-07 23:59:53 +08:00
/>
2025-03-13 09:14:25 +08:00
<button className='btn btn-dark d-flex align-items-center gap-1' onClick={handleOpenCreateModal}>
2025-03-01 08:34:56 +08:00
<SvgIcon className={'plus'} />
2025-02-27 06:54:19 +08:00
新建知识库
</button>
</div>
2025-03-07 23:59:53 +08:00
{isLoading ? (
<div className='d-flex justify-content-center my-5'>
<div className='spinner-border' role='status'>
<span className='visually-hidden'>加载中...</span>
2025-03-04 07:22:05 +08:00
</div>
</div>
2025-03-07 23:59:53 +08:00
) : (
<>
<KnowledgeBaseList
2025-03-23 10:53:37 +08:00
knowledgeBases={knowledgeBases}
isSearching={false} // 始终为false因为搜索不影响主列表
2025-03-07 23:59:53 +08:00
onCardClick={handleCardClick}
onRequestAccess={handleRequestAccess}
onDelete={handleDelete}
2025-03-07 23:59:53 +08:00
/>
2025-03-23 10:53:37 +08:00
{/* Pagination - 始终显示 */}
2025-03-07 23:59:53 +08:00
{totalPages > 1 && (
<Pagination
currentPage={pagination.page}
totalPages={totalPages}
pageSize={pagination.page_size}
onPageChange={handlePageChange}
onPageSizeChange={handlePageSizeChange}
/>
)}
</>
2025-03-04 07:22:05 +08:00
)}
2025-03-04 08:37:47 +08:00
2025-03-07 23:59:53 +08:00
{/* 新建知识库弹窗 */}
<CreateKnowledgeBaseModal
show={showCreateModal}
formData={newKnowledgeBase}
formErrors={formErrors}
isSubmitting={loading}
2025-03-07 23:59:53 +08:00
onClose={() => setShowCreateModal(false)}
onChange={handleInputChange}
onSubmit={handleCreateKnowledgeBase}
currentUser={currentUser}
2025-03-07 23:59:53 +08:00
/>
{/* 申请权限弹窗 */}
2025-03-13 09:14:25 +08:00
<AccessRequestModal
show={showAccessRequestModal}
knowledgeBaseId={accessRequestKnowledgeBase.id}
knowledgeBaseTitle={accessRequestKnowledgeBase.title}
onClose={() => setShowAccessRequestModal(false)}
onSubmit={handleSubmitAccessRequest}
isSubmitting={isSubmittingRequest}
/>
2025-02-27 06:54:19 +08:00
</div>
);
}