KnowledgeBase_frontend/src/pages/KnowledgeBase/KnowledgeBase.jsx

543 lines
19 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState, useEffect, useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
import { showNotification } from '../../store/notification.slice';
import {
fetchKnowledgeBases,
searchKnowledgeBases,
createKnowledgeBase,
deleteKnowledgeBase,
requestKnowledgeBaseAccess,
} from '../../store/knowledgeBase/knowledgeBase.thunks';
import { clearSearchResults } from '../../store/knowledgeBase/knowledgeBase.slice';
import SvgIcon from '../../components/SvgIcon';
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';
// 导入拆分的组件
import KnowledgeBaseList from './components/KnowledgeBaseList';
export default function KnowledgeBase() {
const dispatch = useDispatch();
const navigate = useNavigate();
const [showCreateModal, setShowCreateModal] = useState(false);
const [showAccessRequestModal, setShowAccessRequestModal] = useState(false);
const [formErrors, setFormErrors] = useState({});
const [accessRequestKnowledgeBase, setAccessRequestKnowledgeBase] = useState({
id: '',
title: '',
});
const [isSubmittingRequest, setIsSubmittingRequest] = useState(false);
const [createdKnowledgeBaseId, setCreatedKnowledgeBaseId] = useState(null);
// 获取当前用户信息
const currentUser = useSelector((state) => state.auth.user);
const [newKnowledgeBase, setNewKnowledgeBase] = useState({
name: '',
desc: '',
type: 'private', // 默认为私有知识库
department: currentUser?.department || '',
group: currentUser?.group || '',
});
// Search state
const [searchKeyword, setSearchKeyword] = useState('');
const [isSearchDropdownOpen, setIsSearchDropdownOpen] = useState(false);
// 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);
// Fetch knowledge bases when component mounts or pagination changes
useEffect(() => {
// 无论是否在搜索,都正常获取知识库列表
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) {
dispatch(
showNotification({
message: `获取知识库列表失败: ${error.message || error}`,
type: 'danger',
})
);
}
}, [isLoading, error, dispatch]);
// 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]);
// Handle search input change
const handleSearchInputChange = (e) => {
const value = e.target.value;
setSearchKeyword(value);
// 如果搜索框清空,关闭下拉框
if (!value.trim()) {
dispatch(clearSearchResults());
setIsSearchDropdownOpen(false);
}
};
// Handle search submit - 只影响下拉框,不影响主列表
const handleSearch = (e) => {
e.preventDefault();
if (searchKeyword.trim()) {
// 只设置下拉框搜索状态不设置全局isSearching状态
dispatch(
searchKnowledgeBases({
keyword: searchKeyword,
page: 1,
page_size: 5, // 下拉框只显示少量结果
})
);
setIsSearchDropdownOpen(true);
} else {
// 清空搜索及关闭下拉框
handleClearSearch();
}
};
// Handle clear search
const handleClearSearch = () => {
setSearchKeyword('');
// 不影响主列表显示,只关闭下拉框
setIsSearchDropdownOpen(false);
dispatch(clearSearchResults());
};
// 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,
});
};
const handleInputChange = (e) => {
const { name, value } = e.target;
const isAdmin = currentUser?.role === 'admin';
const isLeader = currentUser?.role === 'leader';
// 根据用户角色允许修改部门和组别字段
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: '', // 部门变更时重置组别
}));
}
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;
}
}
setNewKnowledgeBase((prev) => ({
...prev,
[name]: value,
}));
// Clear error when user types
if (formErrors[name]) {
setFormErrors((prev) => ({
...prev,
[name]: '',
}));
}
};
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';
if (!newKnowledgeBase.name.trim()) {
errors.name = '请输入知识库名称';
}
if (!newKnowledgeBase.desc.trim()) {
errors.desc = '请输入知识库描述';
}
if (!newKnowledgeBase.type) {
errors.type = '请选择知识库类型';
}
// 对于member级别的知识库检查是否选择了部门和组别
if (needSelectGroup && !isPrivate) {
// 管理员必须选择部门
if (isAdmin && !newKnowledgeBase.department) {
errors.department = '创建member级别知识库时必须选择部门';
}
// 所有用户创建member级别知识库时必须选择组别
if (!newKnowledgeBase.group) {
errors.group = '创建member级别知识库时必须选择组别';
}
}
setFormErrors(errors);
return Object.keys(errors).length === 0;
};
const handleCreateKnowledgeBase = async () => {
// Validate form
if (!validateCreateForm()) {
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',
})
);
}
// Reset form and close modal
setNewKnowledgeBase({ name: '', desc: '', type: 'private', department: '', group: '' });
setFormErrors({});
setShowCreateModal(false);
};
// Handle card click to navigate to knowledge base detail
const handleCardClick = (id, permissions) => {
// 检查用户是否有读取权限
if (!permissions || permissions.can_read === false) {
dispatch(
showNotification({
message: '您没有访问此知识库的权限,请先申请权限',
type: 'warning',
})
);
return;
}
// 有权限则跳转到详情页
navigate(`/knowledge-base/${id}/datasets`);
};
const handleRequestAccess = (id, title) => {
setAccessRequestKnowledgeBase({
id,
title,
});
setShowAccessRequestModal(true);
};
const handleSubmitAccessRequest = async (requestData) => {
setIsSubmittingRequest(true);
try {
// 使用权限服务发送请求
await requestKnowledgeBaseAccess(requestData);
dispatch(
showNotification({
message: '权限申请已提交',
type: 'success',
})
);
// Close modal
setShowAccessRequestModal(false);
} catch (error) {
dispatch(
showNotification({
message: `权限申请失败: ${error.response?.data?.message || '请稍后重试'}`,
type: 'danger',
})
);
} finally {
setIsSubmittingRequest(false);
}
};
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',
})
);
});
};
// Calculate total pages
const totalPages = Math.ceil(paginationData.total / pagination.page_size);
// 打开创建知识库弹窗
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({});
setShowCreateModal(true);
};
// 处理点击搜索结果
const handleSearchResultClick = (id, permissions) => {
if (permissions?.can_read) {
navigate(`/knowledge-base/${id}/datasets`);
}
};
return (
<div className='knowledge-base container my-4'>
<div className='api-mode-control mb-3'>
<ApiModeSwitch />
</div>
<div className='d-flex justify-content-between align-items-center mb-3'>
<SearchBar
searchKeyword={searchKeyword}
isSearching={isSearchDropdownOpen} // 用于控制下拉框显示
onSearchChange={handleSearchInputChange}
onSearch={handleSearch}
onClearSearch={handleClearSearch}
placeholder='搜索知识库...'
searchResults={searchResults}
isSearchLoading={searchLoading}
onResultClick={handleSearchResultClick}
onRequestAccess={handleRequestAccess}
/>
<button className='btn btn-dark d-flex align-items-center gap-1' onClick={handleOpenCreateModal}>
<SvgIcon className={'plus'} />
新建知识库
</button>
</div>
{isLoading ? (
<div className='d-flex justify-content-center my-5'>
<div className='spinner-border' role='status'>
<span className='visually-hidden'>加载中...</span>
</div>
</div>
) : (
<>
<KnowledgeBaseList
knowledgeBases={knowledgeBases}
isSearching={false} // 始终为false因为搜索不影响主列表
onCardClick={handleCardClick}
onRequestAccess={handleRequestAccess}
onDelete={handleDelete}
/>
{/* Pagination - 始终显示 */}
{totalPages > 1 && (
<Pagination
currentPage={pagination.page}
totalPages={totalPages}
pageSize={pagination.page_size}
onPageChange={handlePageChange}
onPageSizeChange={handlePageSizeChange}
/>
)}
</>
)}
{/* 新建知识库弹窗 */}
<CreateKnowledgeBaseModal
show={showCreateModal}
formData={newKnowledgeBase}
formErrors={formErrors}
isSubmitting={loading}
onClose={() => setShowCreateModal(false)}
onChange={handleInputChange}
onSubmit={handleCreateKnowledgeBase}
currentUser={currentUser}
/>
{/* 申请权限弹窗 */}
<AccessRequestModal
show={showAccessRequestModal}
knowledgeBaseId={accessRequestKnowledgeBase.id}
knowledgeBaseTitle={accessRequestKnowledgeBase.title}
onClose={() => setShowAccessRequestModal(false)}
onSubmit={handleSubmitAccessRequest}
isSubmitting={isSubmittingRequest}
/>
</div>
);
}