Compare commits

..

No commits in common. "a85154fbe1b1b719f8f6a642e1e21826449cd5ac" and "b2df4ebfc8b0f93b384019d39ee2352047aab1bf" have entirely different histories.

12 changed files with 68 additions and 186 deletions

View File

@ -61,15 +61,15 @@ const CreateKnowledgeBaseModal = ({
if (isAdmin) { if (isAdmin) {
return [ return [
{ value: 'admin', label: '公共知识库' }, { value: 'admin', label: '公共知识库' },
{ value: 'leader', label: '组长级知识库' }, { value: 'leader', label: 'Leader 级知识库' },
{ value: 'member', label: '组内知识库' }, { value: 'member', label: 'Member 级知识库' },
{ value: 'private', label: '私有知识库' }, { value: 'private', label: '私有知识库' },
{ value: 'secret', label: '密知识库' }, { value: 'secret', label: '密知识库' },
]; ];
} else if (isLeader) { } else if (isLeader) {
return [ return [
{ value: 'admin', label: '公共知识库' }, { value: 'admin', label: '公共知识库' },
{ value: 'member', label: '组内知识库' }, { value: 'member', label: 'Member 级知识库' },
{ value: 'private', label: '私有知识库' }, { value: 'private', label: '私有知识库' },
]; ];
} else { } else {
@ -180,7 +180,7 @@ const CreateKnowledgeBaseModal = ({
</div> </div>
{/* 仅当不是私有知识库时才显示部门选项 */} {/* 仅当不是私有知识库时才显示部门选项 */}
{formData.type === 'member' && ( {formData.type !== 'private' && (
<div className='mb-3'> <div className='mb-3'>
<label htmlFor='department' className='form-label'> <label htmlFor='department' className='form-label'>
部门 {isAdmin && needSelectGroup && <span className='text-danger'>*</span>} 部门 {isAdmin && needSelectGroup && <span className='text-danger'>*</span>}
@ -220,7 +220,7 @@ const CreateKnowledgeBaseModal = ({
)} )}
{/* 仅当不是私有知识库时才显示组别选项 */} {/* 仅当不是私有知识库时才显示组别选项 */}
{formData.type === 'member' && ( {formData.type !== 'private' && (
<div className='mb-3'> <div className='mb-3'>
<label htmlFor='group' className='form-label'> <label htmlFor='group' className='form-label'>
组别 {needSelectGroup && <span className='text-danger'>*</span>} 组别 {needSelectGroup && <span className='text-danger'>*</span>}

View File

@ -47,10 +47,7 @@ export default function Chat() {
// If we have a knowledgeBaseId but no chatId, check if we have an existing chat or create a new one // If we have a knowledgeBaseId but no chatId, check if we have an existing chat or create a new one
useEffect(() => { useEffect(() => {
// knowledgeBaseId chatId if (knowledgeBaseId && !chatId) {
if (knowledgeBaseId && !chatId && status === 'succeeded' && !status.includes('loading')) {
console.log('Chat.jsx: 检查是否需要创建聊天...');
// //
const existingChat = chatHistory.find((chat) => { const existingChat = chatHistory.find((chat) => {
// ID // ID
@ -63,16 +60,12 @@ export default function Chat() {
} }
return false; return false;
}); });
console.log('Chat.jsx: existingChat', existingChat); console.log('existingChat', existingChat);
if (existingChat) { if (existingChat) {
console.log(
`Chat.jsx: 找到现有聊天记录,导航到 /chat/${knowledgeBaseId}/${existingChat.conversation_id}`
);
// //
navigate(`/chat/${knowledgeBaseId}/${existingChat.conversation_id}`); navigate(`/chat/${knowledgeBaseId}/${existingChat.conversation_id}`);
} else { } else {
console.log('Chat.jsx: 创建新聊天...');
// //
dispatch( dispatch(
createChatRecord({ createChatRecord({
@ -84,13 +77,9 @@ export default function Chat() {
.then((response) => { .then((response) => {
// 使conversation_id // 使conversation_id
if (response && response.conversation_id) { if (response && response.conversation_id) {
console.log(
`Chat.jsx: 创建成功,导航到 /chat/${knowledgeBaseId}/${response.conversation_id}`
);
navigate(`/chat/${knowledgeBaseId}/${response.conversation_id}`); navigate(`/chat/${knowledgeBaseId}/${response.conversation_id}`);
} else { } else {
// //
console.error('Chat.jsx: 创建失败未能获取会话ID');
dispatch( dispatch(
showNotification({ showNotification({
message: '创建聊天失败未能获取会话ID', message: '创建聊天失败未能获取会话ID',
@ -100,7 +89,6 @@ export default function Chat() {
} }
}) })
.catch((error) => { .catch((error) => {
console.error('Chat.jsx: 创建失败', error);
dispatch( dispatch(
showNotification({ showNotification({
message: `创建聊天失败: ${error}`, message: `创建聊天失败: ${error}`,
@ -110,7 +98,7 @@ export default function Chat() {
}); });
} }
} }
}, [knowledgeBaseId, chatId, chatHistory, status, navigate, dispatch]); }, [knowledgeBaseId, chatId, chatHistory, navigate, dispatch]);
const handleDeleteChat = (id) => { const handleDeleteChat = (id) => {
// Redux action // Redux action

View File

@ -97,6 +97,16 @@ export default function ChatSidebar({ chatHistory = [], onDeleteChat, isLoading
<div className='text-truncate fw-medium'> <div className='text-truncate fw-medium'>
{chat.datasets?.map((ds) => ds.name).join(', ') || '未命名知识库'} {chat.datasets?.map((ds) => ds.name).join(', ') || '未命名知识库'}
</div> </div>
<div className='small text-muted text-truncate' style={{ maxWidth: '160px' }}>
{chat.last_message
? chat.last_message.length > 30
? chat.last_message.substring(0, 30) + '...'
: chat.last_message
: '新对话'}
</div>
<div className='x-small text-muted mt-1'>
{chat.last_time && new Date(chat.last_time).toLocaleDateString()}
</div>
</div> </div>
</Link> </Link>
<div <div

View File

@ -235,7 +235,7 @@ export default function ChatWindow({ chatId, knowledgeBaseId }) {
<div <div
key={message.id} key={message.id}
className={`d-flex ${ className={`d-flex ${
message.role === 'user' ? 'align-items-end' : 'align-items-start' message.role === 'user' ? 'align-item-end' : 'align-item-start'
} mb-3 flex-column`} } mb-3 flex-column`}
> >
<div <div

View File

@ -8,8 +8,6 @@ import SvgIcon from '../../components/SvgIcon';
export default function NewChat() { export default function NewChat() {
const navigate = useNavigate(); const navigate = useNavigate();
const dispatch = useDispatch(); const dispatch = useDispatch();
const [selectedDatasetId, setSelectedDatasetId] = useState(null);
const [isNavigating, setIsNavigating] = useState(false);
// Redux store // Redux store
const datasets = useSelector((state) => state.chat.availableDatasets.items || []); const datasets = useSelector((state) => state.chat.availableDatasets.items || []);
@ -19,7 +17,6 @@ export default function NewChat() {
// //
const chatHistory = useSelector((state) => state.chat.history.items || []); const chatHistory = useSelector((state) => state.chat.history.items || []);
const chatHistoryLoading = useSelector((state) => state.chat.history.status === 'loading'); const chatHistoryLoading = useSelector((state) => state.chat.history.status === 'loading');
const chatCreationStatus = useSelector((state) => state.chat.sendMessage?.status);
// //
useEffect(() => { useEffect(() => {
@ -40,59 +37,26 @@ export default function NewChat() {
}, [error, dispatch]); }, [error, dispatch]);
// //
const handleSelectKnowledgeBase = async (dataset) => { const handleSelectKnowledgeBase = (dataset) => {
if (isNavigating) return; // //
const existingChat = chatHistory.find((chat) => {
try { // ID
setSelectedDatasetId(dataset.id); if (chat.datasets && Array.isArray(chat.datasets)) {
setIsNavigating(true); return chat.datasets.some((ds) => ds.id === dataset.id);
//
const existingChat = chatHistory.find((chat) => {
// ID
if (chat.datasets && Array.isArray(chat.datasets)) {
return chat.datasets.some((ds) => ds.id === dataset.id);
}
//
if (chat.dataset_id_list && Array.isArray(chat.dataset_id_list)) {
return chat.dataset_id_list.includes(dataset.id.replace(/-/g, ''));
}
return false;
});
if (existingChat) {
//
console.log(`找到现有聊天记录,直接导航到 /chat/${dataset.id}/${existingChat.conversation_id}`);
navigate(`/chat/${dataset.id}/${existingChat.conversation_id}`);
} else {
// /chat/${dataset.id}
console.log(`未找到现有聊天记录直接创建新的聊天知识库ID: ${dataset.id}`);
// Chat.jsx
const response = await dispatch(
createChatRecord({
dataset_id_list: [dataset.id.replace(/-/g, '')],
question: '选择当前知识库,创建聊天',
})
).unwrap();
if (response && response.conversation_id) {
console.log(`创建成功,导航到 /chat/${dataset.id}/${response.conversation_id}`);
navigate(`/chat/${dataset.id}/${response.conversation_id}`);
} else {
throw new Error('未能获取会话ID');
}
} }
} catch (error) { //
console.error('导航或创建聊天失败:', error); if (chat.dataset_id_list && Array.isArray(chat.dataset_id_list)) {
dispatch( return chat.dataset_id_list.includes(dataset.id.replace(/-/g, ''));
showNotification({ }
message: `创建聊天失败: ${error.message || '请重试'}`, return false;
type: 'danger', });
})
); if (existingChat) {
setIsNavigating(false); //
setSelectedDatasetId(null); navigate(`/chat/${dataset.id}/${existingChat.conversation_id}`);
} else {
//
navigate(`/chat/${dataset.id}`);
} }
}; };
@ -109,48 +73,17 @@ export default function NewChat() {
return ( return (
<div className='container-fluid px-4 py-5'> <div className='container-fluid px-4 py-5'>
{/* 导航中的遮罩层 */}
{isNavigating && (
<div
className='position-fixed top-0 start-0 w-100 h-100 d-flex justify-content-center align-items-center'
style={{
backgroundColor: 'rgba(255, 255, 255, 0.7)',
zIndex: 1050,
}}
>
<div className='text-center'>
<div className='spinner-border mb-2' role='status'>
<span className='visually-hidden'>加载中...</span>
</div>
<div>正在加载聊天界面...</div>
</div>
</div>
)}
<h4 className='mb-4'>选择知识库开始聊天</h4> <h4 className='mb-4'>选择知识库开始聊天</h4>
<div className='row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4 justify-content-center'> <div className='row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4 justify-content-center'>
{datasets.length > 0 ? ( {datasets.length > 0 ? (
datasets.map((dataset) => ( datasets.map((dataset) => (
<div key={dataset.id} className='col'> <div key={dataset.id} className='col'>
<div <div
className={`card h-100 shadow-sm border-0 ${!isNavigating ? 'cursor-pointer' : ''} ${ className='card h-100 shadow-sm border-0 cursor-pointer'
selectedDatasetId === dataset.id ? 'border-primary' : ''
}`}
onClick={() => handleSelectKnowledgeBase(dataset)} onClick={() => handleSelectKnowledgeBase(dataset)}
style={{ opacity: isNavigating && selectedDatasetId !== dataset.id ? 0.6 : 1 }}
> >
<div className='card-body'> <div className='card-body'>
<h5 className='card-title d-flex justify-content-between align-items-center'> <h5 className='card-title'>{dataset.name}</h5>
{dataset.name}
{selectedDatasetId === dataset.id && isNavigating && (
<div
className='spinner-border spinner-border-sm text-primary'
role='status'
>
<span className='visually-hidden'>加载中...</span>
</div>
)}
</h5>
<p className='card-text text-muted'>{dataset.desc || dataset.description || ''}</p> <p className='card-text text-muted'>{dataset.desc || dataset.description || ''}</p>
<div className='text-muted small d-flex align-items-center gap-2'> <div className='text-muted small d-flex align-items-center gap-2'>
<span className='d-flex align-items-center gap-1'> <span className='d-flex align-items-center gap-1'>

View File

@ -89,10 +89,10 @@ export default function SettingsTab({ knowledgeBase }) {
allowed = ['admin', 'leader', 'member', 'private', 'secret'].includes(value); allowed = ['admin', 'leader', 'member', 'private', 'secret'].includes(value);
} else if (role === 'leader') { } else if (role === 'leader') {
// member private // member private
allowed = ['admin', 'member', 'private'].includes(value); allowed = ['member', 'private'].includes(value);
} else { } else {
// private // private
allowed = ['admin', 'private'].includes(value); allowed = value === 'private';
} }
if (!allowed) { if (!allowed) {
@ -299,7 +299,6 @@ export default function SettingsTab({ knowledgeBase }) {
}) })
); );
// Navigate back to knowledge base list // Navigate back to knowledge base list
// Redux store reducer
navigate('/knowledge-base'); navigate('/knowledge-base');
}) })
.catch((error) => { .catch((error) => {

View File

@ -27,15 +27,15 @@ const KnowledgeBaseForm = ({
if (isAdmin) { if (isAdmin) {
return [ return [
{ value: 'admin', label: '公共知识库' }, { value: 'admin', label: '公共知识库' },
{ value: 'leader', label: '组长级知识库' }, { value: 'leader', label: 'Leader 级知识库' },
{ value: 'member', label: '组内知识库' }, { value: 'member', label: 'Member 级知识库' },
{ value: 'private', label: '私有知识库' }, { value: 'private', label: '私有知识库' },
{ value: 'secret', label: '密知识库' }, { value: 'secret', label: '密知识库' },
]; ];
} else if (isLeader) { } else if (isLeader) {
return [ return [
{ value: 'admin', label: '公共知识库' }, { value: 'admin', label: '公共知识库' },
{ value: 'member', label: '组内知识库' }, { value: 'member', label: 'Member 级知识库' },
{ value: 'private', label: '私有知识库' }, { value: 'private', label: '私有知识库' },
]; ];
} else { } else {
@ -125,7 +125,7 @@ const KnowledgeBaseForm = ({
</div> </div>
{/* 仅当不是私有知识库时才显示部门选项 */} {/* 仅当不是私有知识库时才显示部门选项 */}
{formData.type === 'member' && ( {formData.type !== 'private' && (
<div className='mb-3'> <div className='mb-3'>
<label htmlFor='department' className='form-label'> <label htmlFor='department' className='form-label'>
部门 {isAdmin && <span className='text-danger'>*</span>} 部门 {isAdmin && <span className='text-danger'>*</span>}
@ -161,13 +161,14 @@ const KnowledgeBaseForm = ({
value={formData.department || ''} value={formData.department || ''}
readOnly readOnly
/> />
<small className='text-muted'>部门信息根据知识库创建者自动填写</small>
</> </>
)} )}
</div> </div>
)} )}
{/* 仅当不是私有知识库时才显示组别选项 */} {/* 仅当不是私有知识库时才显示组别选项 */}
{formData.type === 'member' && ( {formData.type !== 'private' && (
<div className='mb-3'> <div className='mb-3'>
<label htmlFor='group' className='form-label'> <label htmlFor='group' className='form-label'>
组别 {isAdmin && <span className='text-danger'>*</span>} 组别 {isAdmin && <span className='text-danger'>*</span>}
@ -204,6 +205,7 @@ const KnowledgeBaseForm = ({
value={formData.group || ''} value={formData.group || ''}
readOnly readOnly
/> />
<small className='text-muted'>组别信息根据知识库创建者自动填写</small>
</> </>
)} )}
</div> </div>

View File

@ -108,7 +108,6 @@ export default function KnowledgeBase() {
// Handle search input change // Handle search input change
const handleSearchInputChange = (e) => { const handleSearchInputChange = (e) => {
const value = e.target.value; const value = e.target.value;
console.log('搜索框输入值:', value);
setSearchKeyword(value); setSearchKeyword(value);
// //
@ -207,10 +206,10 @@ export default function KnowledgeBase() {
allowed = ['admin', 'leader', 'member', 'private', 'secret'].includes(value); allowed = ['admin', 'leader', 'member', 'private', 'secret'].includes(value);
} else if (role === 'leader') { } else if (role === 'leader') {
// member private // member private
allowed = ['admin', 'member', 'private'].includes(value); allowed = ['member', 'private'].includes(value);
} else { } else {
// private // private
allowed = ['admin', 'private'].includes(value); allowed = value === 'private';
} }
if (!allowed) { if (!allowed) {
@ -402,7 +401,6 @@ export default function KnowledgeBase() {
const handleDelete = (e, id) => { const handleDelete = (e, id) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
// Dispatch delete knowledge base action // Dispatch delete knowledge base action
dispatch(deleteKnowledgeBase(id)) dispatch(deleteKnowledgeBase(id))
.unwrap() .unwrap()
@ -413,7 +411,6 @@ export default function KnowledgeBase() {
type: 'success', type: 'success',
}) })
); );
// Redux store reducer
}) })
.catch((error) => { .catch((error) => {
dispatch( dispatch(

View File

@ -94,14 +94,13 @@ export default function KnowledgeCard({
</span> </span>
)} )}
{access === 'full' || access === 'read' ? ( {access === 'full' || access === 'read' ? (
<></> <button
// <button className='btn btn-outline-dark btn-sm d-flex align-items-center gap-1'
// className='btn btn-outline-dark btn-sm d-flex align-items-center gap-1' onClick={handleNewChat}
// onClick={handleNewChat} >
// > <SvgIcon className={'chat-dot'} />
// <SvgIcon className={'chat-dot'} /> 新聊天
// </button>
// </button>
) : ( ) : (
<button <button
className='btn btn-outline-dark btn-sm d-flex align-items-center gap-1' className='btn btn-outline-dark btn-sm d-flex align-items-center gap-1'

View File

@ -222,24 +222,6 @@ export default function PendingRequests() {
} }
}; };
//
const getKnowledgeBaseTypeText = (type) => {
switch (type) {
case 'admin':
return '公共知识库';
case 'member':
return '组内知识库';
case 'private':
return '私人知识库';
case 'leader':
return '组长级知识库';
case 'secret':
return '私密知识库';
default:
return '未知类型';
}
};
// //
if (fetchStatus === 'loading' && permissionRequests.length === 0) { if (fetchStatus === 'loading' && permissionRequests.length === 0) {
return ( return (
@ -293,13 +275,7 @@ export default function PendingRequests() {
<div className='request-content'> <div className='request-content'>
<div className='d-flex justify-content-between align-items-start mb-2'> <div className='d-flex justify-content-between align-items-start mb-2'>
<div> <p className='mb-0'>申请访问{request.knowledge_base.name}</p>
<p className='mb-0'>申请访问{request.knowledge_base.name}</p>
<small className='text-muted'>
类型{getKnowledgeBaseTypeText(request.knowledge_base.type)}
</small>
</div>
{renderStatusBadge(request.status)}
</div> </div>
{request.permissions.can_edit ? ( {request.permissions.can_edit ? (

View File

@ -21,24 +21,6 @@ export default function RequestDetailSlideOver({
const knowledgeBaseId = request.knowledge_base?.id || ''; const knowledgeBaseId = request.knowledge_base?.id || '';
const knowledgeBaseType = request.knowledge_base?.type || ''; const knowledgeBaseType = request.knowledge_base?.type || '';
//
const getKnowledgeBaseTypeText = (type) => {
switch (type) {
case 'admin':
return '公共知识库';
case 'member':
return '组内知识库';
case 'private':
return '私人知识库';
case 'leader':
return '组长级知识库';
case 'secret':
return '私密知识库';
default:
return '未知类型';
}
};
return ( return (
<> <>
<div className={`slide-over-backdrop ${show ? 'show' : ''}`} onClick={onClose}></div> <div className={`slide-over-backdrop ${show ? 'show' : ''}`} onClick={onClose}></div>
@ -70,7 +52,7 @@ export default function RequestDetailSlideOver({
</p> </p>
{knowledgeBaseType && ( {knowledgeBaseType && (
<p className='mb-1'> <p className='mb-1'>
<strong>类型</strong> {getKnowledgeBaseTypeText(knowledgeBaseType)} <strong>类型</strong> {knowledgeBaseType}
</p> </p>
)} )}
</div> </div>
@ -130,8 +112,7 @@ export default function RequestDetailSlideOver({
<div className='mb-4'> <div className='mb-4'>
<h6 className='text-muted mb-2'>审批人</h6> <h6 className='text-muted mb-2'>审批人</h6>
<p className='mb-1'> <p className='mb-1'>
{request.approver.name || request.approver.username} ( {request.approver.name || request.approver.username} ({request.approver.department || '未分配部门'})
{request.approver.department || '未分配部门'})
</p> </p>
</div> </div>
)} )}
@ -145,7 +126,9 @@ export default function RequestDetailSlideOver({
<div className='mb-4'> <div className='mb-4'>
<h6 className='text-muted mb-2'>申请理由</h6> <h6 className='text-muted mb-2'>申请理由</h6>
<div className='p-3 bg-light rounded'>{request.reason || '无申请理由'}</div> <div className='p-3 bg-light rounded'>
{request.reason || '无申请理由'}
</div>
</div> </div>
</div> </div>
{request.status === 'pending' && ( {request.status === 'pending' && (

View File

@ -104,12 +104,7 @@ const knowledgeBaseSlice = createSlice({
}) })
.addCase(deleteKnowledgeBase.fulfilled, (state, action) => { .addCase(deleteKnowledgeBase.fulfilled, (state, action) => {
state.loading = false; state.loading = false;
const deletedId = action.payload; state.knowledgeBases = state.knowledgeBases.filter((kb) => kb.id !== action.meta.arg.knowledgeBaseId);
state.knowledgeBases = state.knowledgeBases.filter((kb) => kb.id !== deletedId);
if (state.pagination.total > 0) {
state.pagination.total -= 1;
state.pagination.total_pages = Math.ceil(state.pagination.total / state.pagination.page_size);
}
}) })
.addCase(deleteKnowledgeBase.rejected, (state, action) => { .addCase(deleteKnowledgeBase.rejected, (state, action) => {
state.loading = false; state.loading = false;