KnowledgeBase_frontend/src/pages/KnowledgeBase/KnowledgeBase.jsx

484 lines
18 KiB
JavaScript

import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
import { showNotification } from '../../store/notification.slice';
import {
fetchKnowledgeBases,
searchKnowledgeBases,
createKnowledgeBase,
} from '../../store/knowledgeBase/knowledgeBase.thunks';
import { resetSearchState } from '../../store/knowledgeBase/knowledgeBase.slice';
import SvgIcon from '../../components/SvgIcon';
// 导入拆分的组件
import SearchBar from './components/SearchBar';
import Pagination from './components/Pagination';
import CreateKnowledgeBaseModal from './components/CreateKnowledgeBaseModal';
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 [accessRequestErrors, setAccessRequestErrors] = useState({});
const [accessRequestData, setAccessRequestData] = useState({
id: '',
title: '',
accessType: '只读访问',
duration: '一周',
projectInfo: '',
reason: '',
});
const [newKnowledgeBase, setNewKnowledgeBase] = useState({
name: '',
desc: '',
});
// Search state
const [searchKeyword, setSearchKeyword] = useState('');
const [isSearching, setIsSearching] = useState(false);
// Pagination state
const [pagination, setPagination] = useState({
page: 1,
page_size: 10,
});
// Get knowledge bases from Redux store
const { items: knowledgeBases, total, status, error } = useSelector((state) => state.knowledgeBase.list);
const {
items: searchResults,
total: searchTotal,
status: searchStatus,
error: searchError,
keyword: storeKeyword,
} = useSelector((state) => state.knowledgeBase.search);
const { status: operationStatus, error: operationError } = useSelector((state) => state.knowledgeBase.operations);
// Determine which data to display based on search state
const displayData = isSearching ? searchResults : knowledgeBases;
const displayTotal = isSearching ? searchTotal : total;
const displayStatus = isSearching ? searchStatus : status;
const displayError = isSearching ? searchError : error;
// Fetch knowledge bases when component mounts or pagination changes
useEffect(() => {
if (!isSearching) {
dispatch(fetchKnowledgeBases(pagination));
} else if (searchKeyword.trim()) {
dispatch(
searchKnowledgeBases({
keyword: searchKeyword,
page: pagination.page,
page_size: pagination.page_size,
})
);
}
}, [dispatch, pagination.page, pagination.page_size, isSearching, searchKeyword]);
// Handle search input change
const handleSearchInputChange = (e) => {
setSearchKeyword(e.target.value);
};
// Handle search submit
const handleSearch = (e) => {
e.preventDefault();
if (searchKeyword.trim()) {
setIsSearching(true);
setPagination((prev) => ({ ...prev, page: 1 })); // Reset to first page
dispatch(
searchKnowledgeBases({
keyword: searchKeyword,
page: 1,
page_size: pagination.page_size,
})
);
} else {
// If search is empty, reset to normal list view
handleClearSearch();
}
};
// Handle clear search
const handleClearSearch = () => {
setSearchKeyword('');
setIsSearching(false);
setPagination((prev) => ({ ...prev, page: 1 })); // Reset to first page
dispatch(resetSearchState());
};
// Show loading state while fetching data
const isLoading = displayStatus === 'loading';
// Show error notification if fetch fails
useEffect(() => {
if (displayStatus === 'failed' && displayError) {
dispatch(
showNotification({
message: `获取知识库列表失败: ${displayError.message || displayError}`,
type: 'danger',
})
);
}
}, [displayStatus, displayError, dispatch]);
// Show notification for operation status
useEffect(() => {
if (operationStatus === 'succeeded') {
dispatch(
showNotification({
message: '操作成功',
type: 'success',
})
);
// Refresh the list after successful operation
if (isSearching && searchKeyword.trim()) {
dispatch(
searchKnowledgeBases({
keyword: searchKeyword,
page: pagination.page,
page_size: pagination.page_size,
})
);
} else {
dispatch(fetchKnowledgeBases(pagination));
}
} else if (operationStatus === 'failed' && operationError) {
dispatch(
showNotification({
message: `操作失败: ${operationError.message || operationError}`,
type: 'danger',
})
);
}
}, [operationStatus, operationError, dispatch, pagination, isSearching, searchKeyword]);
// 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;
setNewKnowledgeBase((prev) => ({
...prev,
[name]: value,
}));
// Clear error when user types
if (formErrors[name]) {
setFormErrors((prev) => ({
...prev,
[name]: '',
}));
}
};
const handleAccessRequestInputChange = (e) => {
const { name, value } = e.target;
setAccessRequestData((prev) => ({
...prev,
[name]: value,
}));
// Clear error when user types
if (accessRequestErrors[name]) {
setAccessRequestErrors((prev) => ({
...prev,
[name]: '',
}));
}
};
const validateCreateForm = () => {
const errors = {};
if (!newKnowledgeBase.name.trim()) {
errors.name = '请输入知识库名称';
}
if (!newKnowledgeBase.desc.trim()) {
errors.desc = '请输入知识库描述';
}
setFormErrors(errors);
return Object.keys(errors).length === 0;
};
const validateAccessRequestForm = () => {
const errors = {};
if (!accessRequestData.projectInfo.trim()) {
errors.projectInfo = '请输入项目信息';
}
if (!accessRequestData.reason.trim()) {
errors.reason = '请输入申请原因';
}
setAccessRequestErrors(errors);
return Object.keys(errors).length === 0;
};
const handleCreateKnowledgeBase = () => {
// Validate form
if (!validateCreateForm()) {
return;
}
// Dispatch create knowledge base action
dispatch(
createKnowledgeBase({
name: newKnowledgeBase.name,
desc: newKnowledgeBase.desc,
type: 'private', // Default type
})
);
// Reset form and close modal
setNewKnowledgeBase({ name: '', desc: '' });
setFormErrors({});
setShowCreateModal(false);
};
// Handle card click to navigate to knowledge base detail
const handleCardClick = (id) => {
navigate(`/knowledge-base/${id}/datasets`);
};
const handleRequestAccess = (id, title) => {
setAccessRequestData((prev) => ({
...prev,
id,
title,
}));
setAccessRequestErrors({});
setShowAccessRequestModal(true);
};
const handleSubmitAccessRequest = () => {
// Validate form
if (!validateAccessRequestForm()) {
return;
}
// Here you would typically call an API to submit the access request
dispatch(
showNotification({
message: '权限申请已提交',
type: 'success',
})
);
// Reset form and close modal
setAccessRequestData((prev) => ({
...prev,
projectInfo: '',
reason: '',
}));
setAccessRequestErrors({});
setShowAccessRequestModal(false);
};
// Calculate total pages
const totalPages = Math.ceil(displayTotal / pagination.page_size);
return (
<div className='knowledge-base container mt-4'>
<div className='d-flex justify-content-between align-items-center mb-3'>
<SearchBar
searchKeyword={searchKeyword}
isSearching={isSearching}
onSearchChange={handleSearchInputChange}
onSearch={handleSearch}
onClearSearch={handleClearSearch}
/>
<button
className='btn btn-dark d-flex align-items-center gap-1'
onClick={() => setShowCreateModal(true)}
>
<SvgIcon className={'plus'} />
新建知识库
</button>
</div>
{isSearching && (
<div className='alert alert-info'>
搜索结果: "{storeKeyword}" - 找到 {displayTotal} 个知识库
</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={displayData}
isSearching={isSearching}
onCardClick={handleCardClick}
onRequestAccess={handleRequestAccess}
/>
{/* 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={operationStatus === 'loading'}
onClose={() => setShowCreateModal(false)}
onChange={handleInputChange}
onSubmit={handleCreateKnowledgeBase}
/>
{/* 申请权限弹窗 */}
{showAccessRequestModal && (
<div
className='modal-backdrop'
style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
zIndex: 1050,
}}
>
<div
className='modal-content bg-white rounded shadow'
style={{
width: '500px',
maxWidth: '90%',
padding: '20px',
}}
>
<div className='modal-header d-flex justify-content-between align-items-center mb-3'>
<h5 className='modal-title m-0'>申请访问权限</h5>
<button
type='button'
className='btn-close'
onClick={() => setShowAccessRequestModal(false)}
aria-label='Close'
></button>
</div>
<div className='modal-body'>
<div className='mb-3'>
<label className='form-label'>知识库名称</label>
<input type='text' className='form-control' value={accessRequestData.title} readOnly />
</div>
<div className='mb-3'>
<label className='form-label'>权限类型</label>
<select
className='form-select'
name='accessType'
value={accessRequestData.accessType}
onChange={handleAccessRequestInputChange}
>
<option value='只读访问'>只读访问</option>
<option value='编辑权限'>编辑权限</option>
</select>
</div>
<div className='mb-3'>
<label className='form-label'>访问时长</label>
<select
className='form-select'
name='duration'
value={accessRequestData.duration}
onChange={handleAccessRequestInputChange}
>
<option value='一周'>一周</option>
<option value='一个月'>一个月</option>
<option value='三个月'>三个月</option>
<option value='六个月'>六个月</option>
<option value='永久'>永久</option>
</select>
</div>
<div className='mb-3'>
<label htmlFor='projectInfo' className='form-label'>
项目信息 <span className='text-danger'>*</span>
</label>
<input
type='text'
className={`form-control ${accessRequestErrors.projectInfo ? 'is-invalid' : ''}`}
id='projectInfo'
name='projectInfo'
value={accessRequestData.projectInfo}
onChange={handleAccessRequestInputChange}
placeholder='请输入项目信息'
/>
{accessRequestErrors.projectInfo && (
<div className='invalid-feedback'>{accessRequestErrors.projectInfo}</div>
)}
</div>
<div className='mb-3'>
<label htmlFor='reason' className='form-label'>
申请原因 <span className='text-danger'>*</span>
</label>
<textarea
className={`form-control ${accessRequestErrors.reason ? 'is-invalid' : ''}`}
id='reason'
name='reason'
rows='3'
value={accessRequestData.reason}
onChange={handleAccessRequestInputChange}
placeholder='请输入申请原因'
></textarea>
{accessRequestErrors.reason && (
<div className='invalid-feedback'>{accessRequestErrors.reason}</div>
)}
</div>
</div>
<div className='modal-footer'>
<button
type='button'
className='btn btn-secondary'
onClick={() => setShowAccessRequestModal(false)}
>
取消
</button>
<button type='button' className='btn btn-primary' onClick={handleSubmitAccessRequest}>
提交申请
</button>
</div>
</div>
</div>
)}
</div>
);
}