From 6f346b37894a373f5fcc9aa1ca4d219db8e4908d Mon Sep 17 00:00:00 2001 From: 808vita <97225946+808vita@users.noreply.github.com> Date: Sun, 7 Apr 2024 21:10:03 +0530 Subject: [PATCH] refactor & fix frontend: typing chat (#861) --- frontend/src/components/ChatInterface.tsx | 85 +++++------------------ frontend/src/hooks/useTypingEffect.ts | 19 +++-- frontend/src/services/chatService.ts | 18 ++--- frontend/src/state/chatSlice.ts | 55 ++++++++------- 4 files changed, 61 insertions(+), 116 deletions(-) diff --git a/frontend/src/components/ChatInterface.tsx b/frontend/src/components/ChatInterface.tsx index 2f52285dfb..95fad0a5c3 100644 --- a/frontend/src/components/ChatInterface.tsx +++ b/frontend/src/components/ChatInterface.tsx @@ -8,9 +8,8 @@ import { useTypingEffect } from "../hooks/useTypingEffect"; import { I18nKey } from "../i18n/declaration"; import { addAssistantMessageToChat, - setCurrentQueueMarkerState, - setCurrentTypingMsgState, setTypingActive, + takeOneAndType, } from "../services/chatService"; import { Message } from "../state/chatSlice"; import { RootState } from "../store"; @@ -29,25 +28,21 @@ interface IChatBubbleProps { * */ function TypingChat() { - const { currentTypingMessage, currentQueueMarker, queuedTyping, messages } = - useSelector((state: RootState) => state.chat); + const { typeThis } = useSelector((state: RootState) => state.chat); - const messageContent = useTypingEffect([currentTypingMessage], { + const messageContent = useTypingEffect([typeThis?.content], { loop: false, setTypingActive, - setCurrentQueueMarkerState, - currentQueueMarker, - playbackRate: 0.1, + playbackRate: 0.099, addAssistantMessageToChat, - assistantMessageObj: messages?.[queuedTyping[currentQueueMarker]], + takeOneAndType, + typeThis, }); return ( - currentQueueMarker !== null && ( - - {messageContent} - - ) + + {messageContent} + ); } @@ -72,14 +67,9 @@ function ChatBubble({ msg }: IChatBubbleProps): JSX.Element { function MessageList(): JSX.Element { const messagesEndRef = useRef(null); - const { - messages, - queuedTyping, - typingActive, - currentQueueMarker, - currentTypingMessage, - newChatSequence, - } = useSelector((state: RootState) => state.chat); + const { typingActive, newChatSequence, typeThis } = useSelector( + (state: RootState) => state.chat, + ); const messageScroll = () => { messagesEndRef.current?.scrollIntoView({ @@ -101,56 +91,17 @@ function MessageList(): JSX.Element { }, [newChatSequence, typingActive]); useEffect(() => { - const newMessage = messages?.[queuedTyping[currentQueueMarker]]?.content; - - if ( - currentQueueMarker !== null && - currentQueueMarker !== 0 && - currentTypingMessage !== newMessage - ) { - setCurrentTypingMsgState( - messages?.[queuedTyping?.[currentQueueMarker]]?.content, - ); - } - }, [queuedTyping]); - - useEffect(() => { - if (currentTypingMessage === "") return; + if (typeThis.content === "") return; if (!typingActive) setTypingActive(true); - }, [currentTypingMessage]); - - useEffect(() => { - const newMessage = messages?.[queuedTyping[currentQueueMarker]]?.content; - if ( - newMessage && - typingActive === false && - currentTypingMessage !== newMessage - ) { - if (currentQueueMarker !== 0) { - setCurrentTypingMsgState( - messages?.[queuedTyping?.[currentQueueMarker]]?.content, - ); - } - } - }, [typingActive]); - - useEffect(() => { - if (currentQueueMarker === 0) { - setCurrentTypingMsgState(messages?.[queuedTyping?.[0]]?.content); - } - }, [currentQueueMarker]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [typeThis]); return (
- {newChatSequence.map((msg, index) => - // eslint-disable-next-line no-nested-ternary - msg.sender === "user" || msg.sender === "assistant" ? ( - - ) : ( -
- ), - )} + {newChatSequence.map((msg, index) => ( + + ))} {typingActive && (
diff --git a/frontend/src/hooks/useTypingEffect.ts b/frontend/src/hooks/useTypingEffect.ts index 4cd1f80e8b..996b3bdba0 100644 --- a/frontend/src/hooks/useTypingEffect.ts +++ b/frontend/src/hooks/useTypingEffect.ts @@ -9,25 +9,23 @@ export const useTypingEffect = ( loop = false, playbackRate = 0.1, setTypingActive = () => {}, - setCurrentQueueMarkerState = () => {}, - currentQueueMarker = 0, addAssistantMessageToChat = () => {}, - assistantMessageObj = { content: "", sender: "assistant" }, + takeOneAndType = () => {}, + typeThis = { content: "", sender: "assistant" }, }: { loop?: boolean; playbackRate?: number; setTypingActive?: (bool: boolean) => void; - setCurrentQueueMarkerState?: (marker: number) => void; - currentQueueMarker?: number; addAssistantMessageToChat?: (msg: Message) => void; - assistantMessageObj?: Message; + takeOneAndType?: () => void; + typeThis?: Message; } = { loop: false, playbackRate: 0.1, setTypingActive: () => {}, - currentQueueMarker: 0, addAssistantMessageToChat: () => {}, - assistantMessageObj: { content: "", sender: "assistant" }, + takeOneAndType: () => {}, + typeThis: { content: "", sender: "assistant" }, }, ) => { // eslint-disable-next-line prefer-const @@ -50,8 +48,8 @@ export const useTypingEffect = ( if (stringIndex === strings.length) { if (!loop) { setTypingActive(false); - setCurrentQueueMarkerState(currentQueueMarker + 1); - addAssistantMessageToChat(assistantMessageObj); + addAssistantMessageToChat(typeThis); + takeOneAndType(); return; } stringIndex = 0; @@ -73,6 +71,7 @@ export const useTypingEffect = ( return () => { window.clearTimeout(timeoutId); }; + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const nonBreakingSpace = "\u00A0"; diff --git a/frontend/src/services/chatService.ts b/frontend/src/services/chatService.ts index 4250032d48..a735e6b88e 100644 --- a/frontend/src/services/chatService.ts +++ b/frontend/src/services/chatService.ts @@ -2,9 +2,7 @@ import { Message, appendToNewChatSequence, appendUserMessage, - emptyOutQueuedTyping, - setCurrentQueueMarker, - setCurrentTypingMessage, + takeOneTypeIt, toggleTypingActive, } from "../state/chatSlice"; import Socket from "./socket"; @@ -39,17 +37,9 @@ export function sendChatMessageFromEvent(event: string | SocketMessage): void { export function setTypingActive(bool: boolean): void { store.dispatch(toggleTypingActive(bool)); } - -export function resetQueuedTyping(): void { - store.dispatch(emptyOutQueuedTyping()); -} - -export function setCurrentTypingMsgState(msg: string): void { - store.dispatch(setCurrentTypingMessage(msg)); -} -export function setCurrentQueueMarkerState(index: number): void { - store.dispatch(setCurrentQueueMarker(index)); -} export function addAssistantMessageToChat(msg: Message): void { store.dispatch(appendToNewChatSequence(msg)); } +export function takeOneAndType(): void { + store.dispatch(takeOneTypeIt()); +} diff --git a/frontend/src/state/chatSlice.ts b/frontend/src/state/chatSlice.ts index 6dafd14e26..9582811174 100644 --- a/frontend/src/state/chatSlice.ts +++ b/frontend/src/state/chatSlice.ts @@ -6,19 +6,16 @@ export type Message = { }; const initialMessages: Message[] = []; -const queuedMessages: number[] = []; -const currentQueueMarker: number = 0; export const chatSlice = createSlice({ name: "chat", initialState: { messages: initialMessages, - queuedTyping: queuedMessages, typingActive: false, - currentTypingMessage: "", - currentQueueMarker, userMessages: initialMessages, assistantMessages: initialMessages, + assistantMessagesTypingQueue: initialMessages, newChatSequence: initialMessages, + typeThis: { content: "", sender: "assistant" } as Message, }, reducers: { appendUserMessage: (state, action) => { @@ -28,30 +25,40 @@ export const chatSlice = createSlice({ }, appendAssistantMessage: (state, action) => { state.messages.push({ content: action.payload, sender: "assistant" }); - state.assistantMessages.push({ - content: action.payload, - sender: "assistant", - }); - // state.queuedTyping.push(action.payload); - const assistantMessageIndex = state.messages.length - 1; - state.queuedTyping.push(assistantMessageIndex); - }, - setCurrentQueueMarker: (state, action) => { - state.currentQueueMarker = action.payload; + + if ( + state.assistantMessagesTypingQueue.length > 0 || + state.typingActive === true + ) { + state.assistantMessagesTypingQueue.push({ + content: action.payload, + sender: "assistant", + }); + } else if ( + state.assistantMessagesTypingQueue.length === 0 && + state.typingActive === false + ) { + state.typeThis = { + content: action.payload, + sender: "assistant", + }; + state.typingActive = true; + } }, + toggleTypingActive: (state, action) => { state.typingActive = action.payload; }, - emptyOutQueuedTyping: (state) => { - state.queuedTyping = []; - }, - setCurrentTypingMessage: (state, action) => { - state.currentTypingMessage = action.payload; - // state.currentQueueMarker += 1; - }, + appendToNewChatSequence: (state, action) => { state.newChatSequence.push(action.payload); }, + + takeOneTypeIt: (state) => { + if (state.assistantMessagesTypingQueue.length > 0) { + state.typeThis = state.assistantMessagesTypingQueue.shift() as Message; + } + }, }, }); @@ -59,10 +66,8 @@ export const { appendUserMessage, appendAssistantMessage, toggleTypingActive, - emptyOutQueuedTyping, - setCurrentTypingMessage, - setCurrentQueueMarker, appendToNewChatSequence, + takeOneTypeIt, } = chatSlice.actions; export default chatSlice.reducer;