chat history performance improvements with memo

This commit is contained in:
timothycarambat 2024-05-10 14:09:25 -07:00
parent d36c3ff8b2
commit 8f068b80d7
3 changed files with 45 additions and 11 deletions

View File

@ -23,9 +23,8 @@ const HistoricalMessage = ({
return ( return (
<div <div
key={uuid} key={uuid}
className={`flex justify-center items-end w-full ${ className={`flex justify-center items-end w-full ${role === "user" ? USER_BACKGROUND_COLOR : AI_BACKGROUND_COLOR
role === "user" ? USER_BACKGROUND_COLOR : AI_BACKGROUND_COLOR }`}
}`}
> >
<div <div
className={`py-8 px-4 w-full flex gap-x-5 md:max-w-[800px] flex-col`} className={`py-8 px-4 w-full flex gap-x-5 md:max-w-[800px] flex-col`}
@ -92,4 +91,17 @@ function ProfileImage({ role, workspace }) {
); );
} }
export default memo(HistoricalMessage); export default memo(
HistoricalMessage,
// Skip re-render the historical message:
// if the content is the exact same AND (not streaming)
// the lastMessage status is the same (regen icon)
// and the chatID matches between renders. (feedback icons)
(prevProps, nextProps) => {
return (
(prevProps.message === nextProps.message) &&
(prevProps.isLastMessage === nextProps.isLastMessage) &&
(prevProps.chatId === nextProps.chatId)
);
}
);

View File

@ -12,20 +12,33 @@ import AvailableAgentsButton, {
useAvailableAgents, useAvailableAgents,
} from "./AgentMenu"; } from "./AgentMenu";
import TextSizeButton from "./TextSizeMenu"; import TextSizeButton from "./TextSizeMenu";
export const PROMPT_INPUT_EVENT = 'set_prompt_input';
export default function PromptInput({ export default function PromptInput({
message,
submit, submit,
onChange, onChange,
inputDisabled, inputDisabled,
buttonDisabled, buttonDisabled,
sendCommand, sendCommand,
}) { }) {
const [promptInput, setPromptInput] = useState('');
const { showAgents, setShowAgents } = useAvailableAgents(); const { showAgents, setShowAgents } = useAvailableAgents();
const { showSlashCommand, setShowSlashCommand } = useSlashCommands(); const { showSlashCommand, setShowSlashCommand } = useSlashCommands();
const formRef = useRef(null); const formRef = useRef(null);
const textareaRef = useRef(null); const textareaRef = useRef(null);
const [_, setFocused] = useState(false); const [_, setFocused] = useState(false);
// To prevent too many re-renders we remotely listen for updates from the parent
// via an event cycle. Otherwise, using message as a prop leads to a re-render every
// change on the input.
function handlePromptUpdate(e) { setPromptInput(e?.detail ?? ''); }
useEffect(() => {
if (!!window) window.addEventListener(PROMPT_INPUT_EVENT, handlePromptUpdate);
return () => (
window?.removeEventListener(PROMPT_INPUT_EVENT, handlePromptUpdate)
)
}, []);
useEffect(() => { useEffect(() => {
if (!inputDisabled && textareaRef.current) { if (!inputDisabled && textareaRef.current) {
textareaRef.current.focus(); textareaRef.current.focus();
@ -102,6 +115,7 @@ export default function PromptInput({
watchForSlash(e); watchForSlash(e);
watchForAt(e); watchForAt(e);
adjustTextArea(e); adjustTextArea(e);
setPromptInput(e.target.value)
}} }}
onKeyDown={captureEnter} onKeyDown={captureEnter}
required={true} required={true}
@ -111,7 +125,7 @@ export default function PromptInput({
setFocused(false); setFocused(false);
adjustTextArea(e); adjustTextArea(e);
}} }}
value={message} value={promptInput}
className="cursor-text max-h-[100px] md:min-h-[40px] mx-2 md:mx-0 py-2 w-full text-[16px] md:text-md text-white bg-transparent placeholder:text-white/60 resize-none active:outline-none focus:outline-none flex-grow" className="cursor-text max-h-[100px] md:min-h-[40px] mx-2 md:mx-0 py-2 w-full text-[16px] md:text-md text-white bg-transparent placeholder:text-white/60 resize-none active:outline-none focus:outline-none flex-grow"
placeholder={"Send a message"} placeholder={"Send a message"}
/> />

View File

@ -1,6 +1,6 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import ChatHistory from "./ChatHistory"; import ChatHistory from "./ChatHistory";
import PromptInput from "./PromptInput"; import PromptInput, { PROMPT_INPUT_EVENT } from "./PromptInput";
import Workspace from "@/models/workspace"; import Workspace from "@/models/workspace";
import handleChat, { ABORT_STREAM_EVENT } from "@/utils/chat"; import handleChat, { ABORT_STREAM_EVENT } from "@/utils/chat";
import { isMobile } from "react-device-detect"; import { isMobile } from "react-device-detect";
@ -20,10 +20,19 @@ export default function ChatContainer({ workspace, knownHistory = [] }) {
const [chatHistory, setChatHistory] = useState(knownHistory); const [chatHistory, setChatHistory] = useState(knownHistory);
const [socketId, setSocketId] = useState(null); const [socketId, setSocketId] = useState(null);
const [websocket, setWebsocket] = useState(null); const [websocket, setWebsocket] = useState(null);
// Maintain state of message from whatever is in PromptInput
const handleMessageChange = (event) => { const handleMessageChange = (event) => {
setMessage(event.target.value); setMessage(event.target.value);
}; };
// Emit an update to the sate of the prompt input without directly
// passing a prop in so that it does not re-render constantly.
function setMessageEmit(messageContent = '') {
setMessage(messageContent);
window.dispatchEvent(new CustomEvent(PROMPT_INPUT_EVENT, { detail: messageContent }))
}
const handleSubmit = async (event) => { const handleSubmit = async (event) => {
event.preventDefault(); event.preventDefault();
if (!message || message === "") return false; if (!message || message === "") return false;
@ -41,14 +50,14 @@ export default function ChatContainer({ workspace, knownHistory = [] }) {
]; ];
setChatHistory(prevChatHistory); setChatHistory(prevChatHistory);
setMessage(""); setMessageEmit("");
setLoadingResponse(true); setLoadingResponse(true);
}; };
const sendCommand = async (command, submit = false) => { const sendCommand = async (command, submit = false) => {
if (!command || command === "") return false; if (!command || command === "") return false;
if (!submit) { if (!submit) {
setMessage(command); setMessageEmit(command);
return; return;
} }
@ -65,7 +74,7 @@ export default function ChatContainer({ workspace, knownHistory = [] }) {
]; ];
setChatHistory(prevChatHistory); setChatHistory(prevChatHistory);
setMessage(""); setMessageEmit("");
setLoadingResponse(true); setLoadingResponse(true);
}; };
@ -208,7 +217,6 @@ export default function ChatContainer({ workspace, knownHistory = [] }) {
sendCommand={sendCommand} sendCommand={sendCommand}
/> />
<PromptInput <PromptInput
message={message}
submit={handleSubmit} submit={handleSubmit}
onChange={handleMessageChange} onChange={handleMessageChange}
inputDisabled={loadingResponse} inputDisabled={loadingResponse}