refactor & fix frontend: typing chat (#861)

This commit is contained in:
808vita
2024-04-07 21:10:03 +05:30
committed by GitHub
parent e9121b78fe
commit 6f346b3789
4 changed files with 61 additions and 116 deletions

View File

@@ -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 && (
<Card className="bg-success-100">
<CardBody>{messageContent}</CardBody>
</Card>
)
<Card className="bg-success-100">
<CardBody>{messageContent}</CardBody>
</Card>
);
}
@@ -72,14 +67,9 @@ function ChatBubble({ msg }: IChatBubbleProps): JSX.Element {
function MessageList(): JSX.Element {
const messagesEndRef = useRef<HTMLDivElement>(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 (
<div className="flex-1 overflow-y-auto">
{newChatSequence.map((msg, index) =>
// eslint-disable-next-line no-nested-ternary
msg.sender === "user" || msg.sender === "assistant" ? (
<ChatBubble key={index} msg={msg} />
) : (
<div key={index} />
),
)}
{newChatSequence.map((msg, index) => (
<ChatBubble key={index} msg={msg} />
))}
{typingActive && (
<div className="flex mb-2.5 pr-5 pl-5 bg-s">

View File

@@ -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";

View File

@@ -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());
}

View File

@@ -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;