Files
supersonic/webapp/packages/supersonic-fe/src/pages/Chat/index.tsx
2023-06-20 15:03:48 +08:00

245 lines
7.6 KiB
TypeScript

import { updateMessageContainerScroll, isMobile, uuid } from '@/utils/utils';
import { useEffect, useRef, useState } from 'react';
import { Helmet } from 'umi';
import MessageContainer from './MessageContainer';
import styles from './style.less';
import { ConversationDetailType, MessageItem, MessageTypeEnum } from './type';
import { updateConversationName } from './service';
import { useThrottleFn } from 'ahooks';
import Conversation from './Conversation';
import RightSection from './RightSection';
import ChatFooter from './ChatFooter';
import classNames from 'classnames';
import { AUTH_TOKEN_KEY, DEFAULT_CONVERSATION_NAME, WEB_TITLE } from '@/common/constants';
import { HistoryMsgItemType, MsgDataType, getHistoryMsg, queryContext } from 'supersonic-chat-sdk';
import { getConversationContext } from './utils';
import { setToken as setChatSdkToken } from 'supersonic-chat-sdk';
import 'supersonic-chat-sdk/dist/index.css';
const Chat = () => {
const [messageList, setMessageList] = useState<MessageItem[]>([]);
const [inputMsg, setInputMsg] = useState('');
const [pageNo, setPageNo] = useState(1);
const [hasNextPage, setHasNextPage] = useState(false);
const [historyInited, setHistoryInited] = useState(false);
const [currentConversation, setCurrentConversation] = useState<
ConversationDetailType | undefined
>(isMobile ? { chatId: 0, chatName: '问答对话' } : undefined);
const [currentEntity, setCurrentEntity] = useState<MsgDataType>();
const conversationRef = useRef<any>();
const chatFooterRef = useRef<any>();
useEffect(() => {
setChatSdkToken(localStorage.getItem(AUTH_TOKEN_KEY) || '');
}, []);
const sendHelloRsp = () => {
setMessageList([
{
id: uuid(),
type: MessageTypeEnum.TEXT,
msg: '您好,请问有什么我能帮您吗?',
},
]);
};
const updateHistoryMsg = async (page: number) => {
const res = await getHistoryMsg(page, currentConversation!.chatId);
const { hasNextPage, list } = res.data.data;
setMessageList([
...list.map((item: HistoryMsgItemType) => ({
id: item.questionId,
type: MessageTypeEnum.QUESTION,
msg: item.queryText,
msgData: item.queryResponse,
})),
...(page === 1 ? [] : messageList),
]);
setHasNextPage(hasNextPage);
if (page === 1) {
if (list.length === 0) {
sendHelloRsp();
} else {
setCurrentEntity(list[list.length - 1].queryResponse);
}
updateMessageContainerScroll();
setHistoryInited(true);
}
if (page > 1) {
const msgEle = document.getElementById(`${messageList[0]?.id}`);
msgEle?.scrollIntoView();
}
};
const { run: handleScroll } = useThrottleFn(
(e) => {
if (e.target.scrollTop === 0 && hasNextPage) {
updateHistoryMsg(pageNo + 1);
setPageNo(pageNo + 1);
}
},
{
leading: true,
trailing: true,
wait: 200,
},
);
useEffect(() => {
if (historyInited) {
const messageContainerEle = document.getElementById('messageContainer');
messageContainerEle?.addEventListener('scroll', handleScroll);
}
return () => {
const messageContainerEle = document.getElementById('messageContainer');
messageContainerEle?.removeEventListener('scroll', handleScroll);
};
}, [historyInited]);
const inputFocus = () => {
if (!isMobile) {
chatFooterRef.current?.inputFocus();
}
};
const inputBlur = () => {
chatFooterRef.current?.inputBlur();
};
useEffect(() => {
if (!currentConversation) {
return;
}
setCurrentEntity(undefined);
const { initMsg, domainId } = currentConversation;
if (initMsg) {
inputFocus();
if (initMsg === DEFAULT_CONVERSATION_NAME) {
sendHelloRsp();
return;
}
onSendMsg(currentConversation.initMsg, [], domainId, true);
return;
}
updateHistoryMsg(1);
setPageNo(1);
}, [currentConversation]);
const modifyConversationName = async (name: string) => {
await updateConversationName(name, currentConversation!.chatId);
conversationRef?.current?.updateData();
window.history.replaceState('', '', `?q=${name}&cid=${currentConversation!.chatId}`);
};
const onSendMsg = async (
msg?: string,
list?: MessageItem[],
domainId?: number,
firstMsg?: boolean,
) => {
const currentMsg = msg || inputMsg;
if (currentMsg.trim() === '') {
setInputMsg('');
return;
}
let quote = '';
if (currentEntity && !firstMsg) {
const { data } = await queryContext(currentMsg, currentConversation!.chatId);
if (data.code === 200 && data.data.domainId === currentEntity.chatContext?.domainId) {
quote = getConversationContext(data.data);
}
}
setMessageList([
...(list || messageList),
{ id: uuid(), msg: currentMsg, domainId, type: MessageTypeEnum.QUESTION, quote },
]);
updateMessageContainerScroll();
setInputMsg('');
modifyConversationName(currentMsg);
};
const onInputMsgChange = (value: string) => {
const inputMsgValue = value || '';
setInputMsg(inputMsgValue);
};
const saveConversationToLocal = (conversation: ConversationDetailType) => {
if (conversation) {
if (conversation.chatId !== -1) {
localStorage.setItem('CONVERSATION_ID', `${conversation.chatId}`);
}
} else {
localStorage.removeItem('CONVERSATION_ID');
}
};
const onSelectConversation = (conversation: ConversationDetailType, name?: string) => {
window.history.replaceState('', '', `?q=${conversation.chatName}&cid=${conversation.chatId}`);
setCurrentConversation({
...conversation,
initMsg: name,
});
saveConversationToLocal(conversation);
};
const onMsgDataLoaded = (data: MsgDataType) => {
setCurrentEntity(data);
updateMessageContainerScroll();
};
const chatClass = classNames(styles.chat, {
[styles.external]: true,
[styles.mobile]: isMobile,
});
return (
<div className={chatClass}>
<Helmet title={WEB_TITLE} />
<div className={styles.topSection} />
<div className={styles.chatSection}>
{!isMobile && (
<Conversation
currentConversation={currentConversation}
onSelectConversation={onSelectConversation}
ref={conversationRef}
/>
)}
<div className={styles.chatApp}>
{currentConversation && (
<div className={styles.chatBody}>
<div className={styles.chatContent}>
<MessageContainer
id="messageContainer"
messageList={messageList}
chatId={currentConversation?.chatId}
onClickMessageContainer={() => {
inputFocus();
}}
onMsgDataLoaded={onMsgDataLoaded}
onSelectSuggestion={onSendMsg}
onUpdateMessageScroll={updateMessageContainerScroll}
/>
<ChatFooter
inputMsg={inputMsg}
chatId={currentConversation?.chatId}
onInputMsgChange={onInputMsgChange}
onSendMsg={(msg: string, domainId?: number) => {
onSendMsg(msg, messageList, domainId);
if (isMobile) {
inputBlur();
}
}}
ref={chatFooterRef}
/>
</div>
</div>
)}
</div>
{!isMobile && <RightSection currentEntity={currentEntity} />}
</div>
</div>
);
};
export default Chat;