2025-03-05 03:46:45 +08:00
|
|
|
|
import React, { useState, useEffect } from 'react';
|
|
|
|
|
import { useParams, useNavigate } from 'react-router-dom';
|
2025-03-13 09:14:25 +08:00
|
|
|
|
import { useDispatch, useSelector } from 'react-redux';
|
2025-03-25 08:04:35 +08:00
|
|
|
|
import { fetchChats, deleteChat, createChatRecord } from '../../store/chat/chat.thunks';
|
2025-03-13 09:14:25 +08:00
|
|
|
|
import { showNotification } from '../../store/notification.slice';
|
2025-03-05 03:46:45 +08:00
|
|
|
|
import ChatSidebar from './ChatSidebar';
|
|
|
|
|
import NewChat from './NewChat';
|
|
|
|
|
import ChatWindow from './ChatWindow';
|
|
|
|
|
|
|
|
|
|
export default function Chat() {
|
|
|
|
|
const { knowledgeBaseId, chatId } = useParams();
|
|
|
|
|
const navigate = useNavigate();
|
2025-03-13 09:14:25 +08:00
|
|
|
|
const dispatch = useDispatch();
|
|
|
|
|
|
|
|
|
|
// 从 Redux store 获取聊天记录列表
|
2025-03-25 08:04:35 +08:00
|
|
|
|
const {
|
|
|
|
|
items: chatHistory,
|
|
|
|
|
status,
|
|
|
|
|
error,
|
|
|
|
|
} = useSelector((state) => state.chat.history || { items: [], status: 'idle', error: null });
|
|
|
|
|
const operationStatus = useSelector((state) => state.chat.createSession?.status);
|
|
|
|
|
const operationError = useSelector((state) => state.chat.createSession?.error);
|
2025-03-13 09:14:25 +08:00
|
|
|
|
|
|
|
|
|
// 获取聊天记录列表
|
|
|
|
|
useEffect(() => {
|
2025-03-25 08:04:35 +08:00
|
|
|
|
dispatch(fetchChats({ page: 1, page_size: 20 }));
|
2025-03-13 09:14:25 +08:00
|
|
|
|
}, [dispatch]);
|
|
|
|
|
|
|
|
|
|
// 监听操作状态,显示通知
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (operationStatus === 'succeeded') {
|
|
|
|
|
dispatch(
|
|
|
|
|
showNotification({
|
|
|
|
|
message: '操作成功',
|
|
|
|
|
type: 'success',
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
} else if (operationStatus === 'failed' && operationError) {
|
|
|
|
|
dispatch(
|
|
|
|
|
showNotification({
|
|
|
|
|
message: `操作失败: ${operationError}`,
|
|
|
|
|
type: 'danger',
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}, [operationStatus, operationError, dispatch]);
|
2025-03-05 03:46:45 +08:00
|
|
|
|
|
2025-03-25 08:04:35 +08:00
|
|
|
|
// If we have a knowledgeBaseId but no chatId, check if we have an existing chat or create a new one
|
2025-03-05 03:46:45 +08:00
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (knowledgeBaseId && !chatId) {
|
2025-03-25 08:04:35 +08:00
|
|
|
|
// 检查是否存在包含此知识库的聊天记录
|
|
|
|
|
const existingChat = chatHistory.find((chat) => {
|
|
|
|
|
// 检查知识库ID是否匹配
|
|
|
|
|
if (chat.datasets && Array.isArray(chat.datasets)) {
|
|
|
|
|
return chat.datasets.some((ds) => ds.id === knowledgeBaseId);
|
|
|
|
|
}
|
|
|
|
|
// 兼容旧格式
|
|
|
|
|
if (chat.dataset_id_list && Array.isArray(chat.dataset_id_list)) {
|
|
|
|
|
return chat.dataset_id_list.includes(knowledgeBaseId.replace(/-/g, ''));
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
});
|
|
|
|
|
console.log('existingChat', existingChat);
|
|
|
|
|
|
|
|
|
|
if (existingChat) {
|
|
|
|
|
// 找到现有聊天记录,导航到该聊天页面
|
|
|
|
|
navigate(`/chat/${knowledgeBaseId}/${existingChat.conversation_id}`);
|
|
|
|
|
} else {
|
|
|
|
|
// 创建新聊天
|
|
|
|
|
dispatch(
|
|
|
|
|
createChatRecord({
|
|
|
|
|
dataset_id_list: [knowledgeBaseId.replace(/-/g, '')],
|
|
|
|
|
question: '选择当前知识库,创建聊天',
|
|
|
|
|
})
|
|
|
|
|
)
|
|
|
|
|
.unwrap()
|
|
|
|
|
.then((response) => {
|
|
|
|
|
// 创建成功,使用返回的conversation_id导航
|
|
|
|
|
if (response && response.conversation_id) {
|
|
|
|
|
navigate(`/chat/${knowledgeBaseId}/${response.conversation_id}`);
|
|
|
|
|
} else {
|
|
|
|
|
// 错误处理
|
|
|
|
|
dispatch(
|
|
|
|
|
showNotification({
|
|
|
|
|
message: '创建聊天失败:未能获取会话ID',
|
|
|
|
|
type: 'danger',
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.catch((error) => {
|
|
|
|
|
dispatch(
|
|
|
|
|
showNotification({
|
|
|
|
|
message: `创建聊天失败: ${error}`,
|
|
|
|
|
type: 'danger',
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-03-05 03:46:45 +08:00
|
|
|
|
}
|
2025-03-25 08:04:35 +08:00
|
|
|
|
}, [knowledgeBaseId, chatId, chatHistory, navigate, dispatch]);
|
2025-03-05 03:46:45 +08:00
|
|
|
|
|
|
|
|
|
const handleDeleteChat = (id) => {
|
2025-03-13 09:14:25 +08:00
|
|
|
|
// 调用 Redux action 删除聊天
|
2025-03-25 08:04:35 +08:00
|
|
|
|
dispatch(deleteChat(id))
|
|
|
|
|
.unwrap()
|
|
|
|
|
.then(() => {
|
|
|
|
|
// 删除成功后显示通知
|
|
|
|
|
dispatch(
|
|
|
|
|
showNotification({
|
|
|
|
|
message: '聊天记录已删除',
|
|
|
|
|
type: 'success',
|
|
|
|
|
})
|
|
|
|
|
);
|
2025-03-05 03:46:45 +08:00
|
|
|
|
|
2025-03-25 08:04:35 +08:00
|
|
|
|
// If the deleted chat is the current one, navigate to the chat list
|
|
|
|
|
if (chatId === id) {
|
|
|
|
|
navigate('/chat');
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.catch((error) => {
|
|
|
|
|
// 删除失败显示错误通知
|
|
|
|
|
dispatch(
|
|
|
|
|
showNotification({
|
|
|
|
|
message: `删除失败: ${error}`,
|
|
|
|
|
type: 'danger',
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
});
|
2025-03-05 03:46:45 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className='chat-container container-fluid h-100'>
|
|
|
|
|
<div className='row h-100'>
|
|
|
|
|
{/* Sidebar */}
|
|
|
|
|
<div
|
|
|
|
|
className='col-md-3 col-lg-2 p-0 border-end'
|
|
|
|
|
style={{ height: 'calc(100vh - 73px)', overflowY: 'auto' }}
|
|
|
|
|
>
|
2025-03-13 09:14:25 +08:00
|
|
|
|
<ChatSidebar
|
|
|
|
|
chatHistory={chatHistory}
|
|
|
|
|
onDeleteChat={handleDeleteChat}
|
|
|
|
|
isLoading={status === 'loading'}
|
|
|
|
|
hasError={status === 'failed'}
|
|
|
|
|
/>
|
2025-03-05 03:46:45 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Main Content */}
|
|
|
|
|
<div
|
|
|
|
|
className='chat-main col-md-9 col-lg-10 p-0'
|
|
|
|
|
style={{ height: 'calc(100vh - 73px)', overflowY: 'auto' }}
|
|
|
|
|
>
|
|
|
|
|
{!chatId ? <NewChat /> : <ChatWindow chatId={chatId} knowledgeBaseId={knowledgeBaseId} />}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|