KnowledgeBase_frontend/src/pages/Chat/ChatWindow.jsx

193 lines
7.5 KiB
React
Raw Normal View History

2025-03-05 03:46:45 +08:00
import React, { useState, useEffect, useRef } from 'react';
2025-03-13 09:14:25 +08:00
import { useDispatch, useSelector } from 'react-redux';
import { fetchMessages, sendMessage } from '../../store/chat/chat.messages.thunks';
2025-03-20 10:01:09 +08:00
import { resetMessages, resetSendMessageStatus, addMessage } from '../../store/chat/chat.slice';
2025-03-13 09:14:25 +08:00
import { showNotification } from '../../store/notification.slice';
2025-03-05 03:46:45 +08:00
import SvgIcon from '../../components/SvgIcon';
2025-03-20 08:22:02 +08:00
import { fetchKnowledgeBases } from '../../store/knowledgeBase/knowledgeBase.thunks';
2025-03-05 03:46:45 +08:00
export default function ChatWindow({ chatId, knowledgeBaseId }) {
2025-03-13 09:14:25 +08:00
const dispatch = useDispatch();
2025-03-05 03:46:45 +08:00
const [inputMessage, setInputMessage] = useState('');
const messagesEndRef = useRef(null);
2025-03-13 09:14:25 +08:00
// 从 Redux store 获取数据
const {
items: messages,
status: messagesStatus,
error: messagesError,
} = useSelector((state) => state.chat.messages);
const { status: sendStatus, error: sendError } = useSelector((state) => state.chat.sendMessage);
2025-03-23 10:53:37 +08:00
// 使用新的Redux状态结构
const knowledgeBases = useSelector((state) => state.knowledgeBase.knowledgeBases || []);
const knowledgeBase = knowledgeBases.find((kb) => kb.id === knowledgeBaseId);
const isLoadingKnowledgeBases = useSelector((state) => state.knowledgeBase.loading);
2025-03-13 09:14:25 +08:00
// 获取聊天消息
useEffect(() => {
if (chatId) {
dispatch(fetchMessages(chatId));
}
// 组件卸载时重置消息状态
return () => {
dispatch(resetMessages());
};
}, [chatId, dispatch]);
// 监听发送消息状态
2025-03-05 03:46:45 +08:00
useEffect(() => {
2025-03-13 09:14:25 +08:00
if (sendStatus === 'failed' && sendError) {
dispatch(
showNotification({
message: `发送失败: ${sendError}`,
type: 'danger',
})
);
dispatch(resetSendMessageStatus());
2025-03-05 03:46:45 +08:00
}
2025-03-13 09:14:25 +08:00
}, [sendStatus, sendError, dispatch]);
2025-03-05 03:46:45 +08:00
2025-03-13 09:14:25 +08:00
// 滚动到底部
2025-03-05 03:46:45 +08:00
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]);
2025-03-20 08:22:02 +08:00
// 从 Redux store 获取知识库信息
useEffect(() => {
if (!knowledgeBase && !isLoadingKnowledgeBases) {
2025-03-23 10:53:37 +08:00
dispatch(fetchKnowledgeBases({ page: 1, page_size: 50 }));
2025-03-20 08:22:02 +08:00
}
}, [dispatch, knowledgeBase, isLoadingKnowledgeBases]);
2025-03-05 03:46:45 +08:00
const handleSendMessage = (e) => {
e.preventDefault();
2025-03-13 09:14:25 +08:00
if (!inputMessage.trim() || sendStatus === 'loading') return;
2025-03-05 03:46:45 +08:00
2025-03-20 10:01:09 +08:00
// 立即添加用户消息到界面
const userMessage = {
id: Date.now(),
content: inputMessage,
sender: 'user',
timestamp: new Date().toISOString(),
};
dispatch(addMessage(userMessage));
// 清空输入框
2025-03-05 03:46:45 +08:00
setInputMessage('');
2025-03-20 10:01:09 +08:00
// 发送消息到服务器
dispatch(sendMessage({ chatId, content: inputMessage }));
2025-03-05 03:46:45 +08:00
};
2025-03-13 09:14:25 +08:00
// 渲染加载状态
const renderLoading = () => (
<div className='p-5 text-center'>
<div className='spinner-border text-secondary' role='status'>
<span className='visually-hidden'>加载中...</span>
</div>
<div className='mt-3 text-muted'>加载聊天记录...</div>
</div>
);
// 渲染错误状态
const renderError = () => (
<div className='p-5 text-center'>
<div className='text-danger mb-3'>
<SvgIcon className='error' width='48' height='48' />
</div>
<div className='text-muted'>加载聊天记录失败请重试</div>
<button className='btn btn-outline-secondary mt-3' onClick={() => dispatch(fetchMessages(chatId))}>
重新加载
</button>
</div>
);
// 渲染空状态
const renderEmpty = () => (
<div className='p-5 text-center'>
<div className='text-muted'>暂无聊天记录发送一条消息开始聊天吧</div>
</div>
);
2025-03-05 03:46:45 +08:00
return (
<div className='chat-window d-flex flex-column h-100'>
{/* Chat header */}
<div className='p-3 border-bottom'>
2025-03-13 09:14:25 +08:00
<h5 className='mb-0'>{knowledgeBase?.name || '加载中...'}</h5>
2025-03-05 03:46:45 +08:00
<small className='text-muted'>{knowledgeBase?.description}</small>
</div>
{/* Chat messages */}
2025-03-13 09:14:25 +08:00
<div className='flex-grow-1 p-3 overflow-auto'>
2025-03-05 03:46:45 +08:00
<div className='container'>
2025-03-13 09:14:25 +08:00
{messagesStatus === 'loading'
? renderLoading()
: messagesStatus === 'failed'
? renderError()
: messages.length === 0
? renderEmpty()
: messages.map((message) => (
<div
key={message.id}
className={`d-flex ${
message.sender === 'user' ? 'justify-content-end' : 'justify-content-start'
} mb-3`}
>
<div
2025-03-20 10:01:09 +08:00
className={`chat-message p-3 rounded-3 ${
message.sender === 'user' ? 'bg-dark text-white' : 'bg-light'
2025-03-13 09:14:25 +08:00
}`}
2025-03-20 10:01:09 +08:00
style={{
maxWidth: '75%',
position: 'relative',
}}
2025-03-13 09:14:25 +08:00
>
2025-03-20 10:01:09 +08:00
<div className='message-content'>{message.content}</div>
2025-03-13 09:14:25 +08:00
</div>
</div>
))}
{sendStatus === 'loading' && (
2025-03-05 03:46:45 +08:00
<div className='d-flex justify-content-start mb-3'>
2025-03-20 10:01:09 +08:00
<div className='chat-message p-3 rounded-3 bg-light'>
2025-03-05 03:46:45 +08:00
<div className='spinner-border spinner-border-sm text-secondary' role='status'>
2025-03-13 09:14:25 +08:00
<span className='visually-hidden'>加载中...</span>
2025-03-05 03:46:45 +08:00
</div>
</div>
</div>
)}
<div ref={messagesEndRef} />
</div>
</div>
{/* Chat input */}
<div className='p-3 border-top'>
<form onSubmit={handleSendMessage} className='d-flex gap-2'>
<input
type='text'
className='form-control'
placeholder='输入你的问题...'
value={inputMessage}
onChange={(e) => setInputMessage(e.target.value)}
2025-03-13 09:14:25 +08:00
disabled={sendStatus === 'loading'}
2025-03-05 03:46:45 +08:00
/>
<button
type='submit'
className='btn btn-dark d-flex align-items-center justify-content-center gap-2'
2025-03-13 09:14:25 +08:00
disabled={sendStatus === 'loading' || !inputMessage.trim()}
2025-03-05 03:46:45 +08:00
>
<SvgIcon className='send' color='#ffffff' />
2025-03-13 09:14:25 +08:00
<span className='ms-1' style={{ minWidth: 'fit-content' }}>
发送
</span>
2025-03-05 03:46:45 +08:00
</button>
</form>
</div>
</div>
);
}