From 09eaac387665c564fffe53f182d02732a30dc21c Mon Sep 17 00:00:00 2001 From: pablodanswer Date: Mon, 30 Sep 2024 18:30:51 -0700 Subject: [PATCH] (wip) fix memoization / memory leak --- backend/danswer/configs/app_configs.py | 2 +- .../docker_compose/docker-compose.dev.yml | 2 +- .../docker_compose/docker-compose.gpu-dev.yml | 2 +- .../docker-compose.search-testing.yml | 2 +- web/src/app/chat/ChatPage.tsx | 12 +- web/src/app/chat/message/CodeBlock.tsx | 64 +- .../chat/message/MemoizedTextComponents.tsx | 14 +- web/src/app/chat/message/Messages.tsx | 962 ++++++++++-------- .../app/chat/modal/SetDefaultModelModal.tsx | 2 +- 9 files changed, 603 insertions(+), 459 deletions(-) diff --git a/backend/danswer/configs/app_configs.py b/backend/danswer/configs/app_configs.py index c943bc7f603..4760edb0dff 100644 --- a/backend/danswer/configs/app_configs.py +++ b/backend/danswer/configs/app_configs.py @@ -135,7 +135,7 @@ os.environ.get("POSTGRES_PASSWORD") or "password" ) POSTGRES_HOST = os.environ.get("POSTGRES_HOST") or "localhost" -POSTGRES_PORT = os.environ.get("POSTGRES_PORT") or "5432" +POSTGRES_PORT = os.environ.get("POSTGRES_PORT") or "5433" POSTGRES_DB = os.environ.get("POSTGRES_DB") or "postgres" # defaults to False diff --git a/deployment/docker_compose/docker-compose.dev.yml b/deployment/docker_compose/docker-compose.dev.yml index 5893fc2960f..0b58c5f5b70 100644 --- a/deployment/docker_compose/docker-compose.dev.yml +++ b/deployment/docker_compose/docker-compose.dev.yml @@ -293,7 +293,7 @@ services: - POSTGRES_USER=${POSTGRES_USER:-postgres} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-password} ports: - - "5432:5432" + - "5433:5432" volumes: - db_volume:/var/lib/postgresql/data diff --git a/deployment/docker_compose/docker-compose.gpu-dev.yml b/deployment/docker_compose/docker-compose.gpu-dev.yml index bc8bf10dffc..9063a23bb21 100644 --- a/deployment/docker_compose/docker-compose.gpu-dev.yml +++ b/deployment/docker_compose/docker-compose.gpu-dev.yml @@ -303,7 +303,7 @@ services: - POSTGRES_USER=${POSTGRES_USER:-postgres} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-password} ports: - - "5432:5432" + - "5433:5432" volumes: - db_volume:/var/lib/postgresql/data diff --git a/deployment/docker_compose/docker-compose.search-testing.yml b/deployment/docker_compose/docker-compose.search-testing.yml index a64b30f09d7..36b093fbae9 100644 --- a/deployment/docker_compose/docker-compose.search-testing.yml +++ b/deployment/docker_compose/docker-compose.search-testing.yml @@ -154,7 +154,7 @@ services: - POSTGRES_USER=${POSTGRES_USER:-postgres} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-password} ports: - - "5432" + - "5433" volumes: - db_volume:/var/lib/postgresql/data diff --git a/web/src/app/chat/ChatPage.tsx b/web/src/app/chat/ChatPage.tsx index e9676d28246..5bd450c9d80 100644 --- a/web/src/app/chat/ChatPage.tsx +++ b/web/src/app/chat/ChatPage.tsx @@ -162,9 +162,6 @@ export function ChatPage({ user, availableAssistants ); - const finalAssistants = user - ? orderAssistantsForUser(visibleAssistants, user) - : visibleAssistants; const existingChatSessionAssistantId = selectedChatSession?.persona_id; const [selectedAssistant, setSelectedAssistant] = useState< @@ -219,7 +216,7 @@ export function ChatPage({ const liveAssistant = alternativeAssistant || selectedAssistant || - finalAssistants[0] || + visibleAssistants[0] || availableAssistants[0]; useEffect(() => { @@ -689,7 +686,7 @@ export function ChatPage({ useEffect(() => { if (messageHistory.length === 0 && chatSessionIdRef.current === null) { setSelectedAssistant( - finalAssistants.find((persona) => persona.id === defaultAssistantId) + visibleAssistants.find((persona) => persona.id === defaultAssistantId) ); } }, [defaultAssistantId]); @@ -2394,7 +2391,10 @@ export function ChatPage({ showDocs={() => setDocumentSelection(true)} selectedDocuments={selectedDocuments} // assistant stuff - assistantOptions={finalAssistants} + assistantOptions={orderAssistantsForUser( + visibleAssistants, + user + )} selectedAssistant={liveAssistant} setSelectedAssistant={onAssistantChange} setAlternativeAssistant={setAlternativeAssistant} diff --git a/web/src/app/chat/message/CodeBlock.tsx b/web/src/app/chat/message/CodeBlock.tsx index 55a6ea7be32..247ae2f352a 100644 --- a/web/src/app/chat/message/CodeBlock.tsx +++ b/web/src/app/chat/message/CodeBlock.tsx @@ -1,7 +1,61 @@ -import React, { useState, ReactNode, useCallback, useMemo, memo } from "react"; +// CodeBlock.tsx +import React, { + useState, + ReactNode, + useCallback, + useMemo, + memo, + useEffect, + CSSProperties, + useRef, +} from "react"; +import { isEqual } from "lodash"; import { FiCheck, FiCopy } from "react-icons/fi"; -const CODE_BLOCK_PADDING_TYPE = { padding: "1rem" }; +const CODE_BLOCK_PADDING_TYPE: CSSProperties = { padding: "1rem" }; + +interface CodeBlockProps { + className?: string; + children?: ReactNode; + content: string; + style?: CSSProperties; + // Add any other specific props you need to handle explicitly + // Example: + // onClick?: (event: React.MouseEvent) => void; + // title?: string; +} + +const arePropsEqual = ( + prevProps: CodeBlockProps, + nextProps: CodeBlockProps +) => { + const prevContent = + typeof prevProps.children === "string" + ? prevProps.children + : prevProps.content; + const nextContent = + typeof nextProps.children === "string" + ? nextProps.children + : nextProps.content; + + const isContentEqual = prevContent === nextContent; + const isClassNameEqual = prevProps.className === nextProps.className; + const isStyleEqual = isEqual(prevProps.style, nextProps.style); + // Compare other explicit props here if added + // Example: + // const isOnClickEqual = prevProps.onClick === nextProps.onClick; + + const areEqual = isContentEqual && isClassNameEqual && isStyleEqual; + + // console.log("Props comparison:", { + // isContentEqual, + // isClassNameEqual, + // isStyleEqual, + // areEqual, + // }); + + return areEqual; +}; interface CodeBlockProps { className?: string | undefined; @@ -10,12 +64,15 @@ interface CodeBlockProps { [key: string]: any; } -export const CodeBlock = memo(function CodeBlock({ +export const CodeBlockComponent = memo(function CodeBlock({ className = "", children, content, ...props }: CodeBlockProps) { + const renderCount = useRef(0); + renderCount.current += 1; + console.log("CodeBlockComponent render count:", renderCount.current); const [copied, setCopied] = useState(false); const language = useMemo(() => { @@ -155,3 +212,4 @@ export const CodeBlock = memo(function CodeBlock({ ); }); +export const CodeBlock = memo(CodeBlockComponent, arePropsEqual); diff --git a/web/src/app/chat/message/MemoizedTextComponents.tsx b/web/src/app/chat/message/MemoizedTextComponents.tsx index 4ab8bc810b2..9c2c6449c82 100644 --- a/web/src/app/chat/message/MemoizedTextComponents.tsx +++ b/web/src/app/chat/message/MemoizedTextComponents.tsx @@ -1,5 +1,5 @@ import { Citation } from "@/components/search/results/Citation"; -import React, { memo } from "react"; +import React, { memo, useRef } from "react"; export const MemoizedLink = memo((props: any) => { const { node, ...rest } = props; @@ -25,9 +25,15 @@ export const MemoizedLink = memo((props: any) => { } }); -export const MemoizedParagraph = memo(({ node, ...props }: any) => ( -

-)); +export const MemoizedParagraph = memo(({ content }: { content: string }) => { + const renderCount = useRef(0); + renderCount.current += 1; + + console.log("MemoizedParagraph render count:", renderCount.current); + console.log("MemoizedParagraph props:", content); + + return

{content}

; +}); MemoizedLink.displayName = "MemoizedLink"; MemoizedParagraph.displayName = "MemoizedParagraph"; diff --git a/web/src/app/chat/message/Messages.tsx b/web/src/app/chat/message/Messages.tsx index edb18138c79..4b0099edd46 100644 --- a/web/src/app/chat/message/Messages.tsx +++ b/web/src/app/chat/message/Messages.tsx @@ -8,7 +8,14 @@ import { FiGlobe, } from "react-icons/fi"; import { FeedbackType } from "../types"; -import React, { useContext, useEffect, useMemo, useRef, useState } from "react"; +import React, { + useContext, + useEffect, + useMemo, + useRef, + useState, + useCallback, +} from "react"; import ReactMarkdown from "react-markdown"; import { DanswerDocument, @@ -109,416 +116,458 @@ function FileDisplay({ ); } -export const AIMessage = ({ - regenerate, - overriddenModel, - continueGenerating, - shared, - isActive, - toggleDocumentSelection, - alternativeAssistant, - docs, - messageId, - content, - files, - selectedDocuments, - query, - personaName, - citedDocuments, - toolCall, - isComplete, - hasDocs, - handleFeedback, - isCurrentlyShowingRetrieved, - handleShowRetrieved, - handleSearchQueryEdit, - handleForceSearch, - retrievalDisabled, - currentPersona, - otherMessagesCanSwitchTo, - onMessageSelection, -}: { - shared?: boolean; - isActive?: boolean; - continueGenerating?: () => void; - otherMessagesCanSwitchTo?: number[]; - onMessageSelection?: (messageId: number) => void; - selectedDocuments?: DanswerDocument[] | null; - toggleDocumentSelection?: () => void; - docs?: DanswerDocument[] | null; - alternativeAssistant?: Persona | null; - currentPersona: Persona; - messageId: number | null; - content: string | JSX.Element; - files?: FileDescriptor[]; - query?: string; - personaName?: string; - citedDocuments?: [string, DanswerDocument][] | null; - toolCall?: ToolCallMetadata; - isComplete?: boolean; - hasDocs?: boolean; - handleFeedback?: (feedbackType: FeedbackType) => void; - isCurrentlyShowingRetrieved?: boolean; - handleShowRetrieved?: (messageNumber: number | null) => void; - handleSearchQueryEdit?: (query: string) => void; - handleForceSearch?: () => void; - retrievalDisabled?: boolean; - overriddenModel?: string; - regenerate?: (modelOverRide: LlmOverride) => Promise; -}) => { - const toolCallGenerating = toolCall && !toolCall.tool_result; - const processContent = (content: string | JSX.Element) => { - if (typeof content !== "string") { - return content; - } +export const AIMessage = React.memo( + ({ + regenerate, + overriddenModel, + continueGenerating, + shared, + isActive, + toggleDocumentSelection, + alternativeAssistant, + docs, + messageId, + content, + files, + selectedDocuments, + query, + personaName, + citedDocuments, + toolCall, + isComplete, + hasDocs, + handleFeedback, + isCurrentlyShowingRetrieved, + handleShowRetrieved, + handleSearchQueryEdit, + handleForceSearch, + retrievalDisabled, + currentPersona, + otherMessagesCanSwitchTo, + onMessageSelection, + }: { + shared?: boolean; + isActive?: boolean; + continueGenerating?: () => void; + otherMessagesCanSwitchTo?: number[]; + onMessageSelection?: (messageId: number) => void; + selectedDocuments?: DanswerDocument[] | null; + toggleDocumentSelection?: () => void; + docs?: DanswerDocument[] | null; + alternativeAssistant?: Persona | null; + currentPersona: Persona; + messageId: number | null; + content: string | JSX.Element; + files?: FileDescriptor[]; + query?: string; + personaName?: string; + citedDocuments?: [string, DanswerDocument][] | null; + toolCall?: ToolCallMetadata; + isComplete?: boolean; + hasDocs?: boolean; + handleFeedback?: (feedbackType: FeedbackType) => void; + isCurrentlyShowingRetrieved?: boolean; + handleShowRetrieved?: (messageNumber: number | null) => void; + handleSearchQueryEdit?: (query: string) => void; + handleForceSearch?: () => void; + retrievalDisabled?: boolean; + overriddenModel?: string; + regenerate?: (modelOverRide: LlmOverride) => Promise; + }) => { + const toolCallGenerating = toolCall && !toolCall.tool_result; + const processContent = (content: string | JSX.Element) => { + if (typeof content !== "string") { + return content; + } - const codeBlockRegex = /```(\w*)\n[\s\S]*?```|```[\s\S]*?$/g; - const matches = content.match(codeBlockRegex); + const codeBlockRegex = /```(\w*)\n[\s\S]*?```|```[\s\S]*?$/g; + const matches = content.match(codeBlockRegex); - if (matches) { - content = matches.reduce((acc, match) => { - if (!match.match(/```\w+/)) { - return acc.replace(match, match.replace("```", "```plaintext")); - } - return acc; - }, content); + if (matches) { + content = matches.reduce((acc, match) => { + if (!match.match(/```\w+/)) { + return acc.replace(match, match.replace("```", "```plaintext")); + } + return acc; + }, content); - const lastMatch = matches[matches.length - 1]; - if (!lastMatch.endsWith("```")) { - return content; + const lastMatch = matches[matches.length - 1]; + if (!lastMatch.endsWith("```")) { + return content; + } } - } - return content + (!isComplete && !toolCallGenerating ? " [*]() " : ""); - }; - const finalContent = processContent(content as string); - - const [isRegenerateHovered, setIsRegenerateHovered] = useState(false); - const { isHovering, trackedElementRef, hoverElementRef } = useMouseTracking(); - - const settings = useContext(SettingsContext); - // this is needed to give Prism a chance to load - - const selectedDocumentIds = - selectedDocuments?.map((document) => document.document_id) || []; - let citedDocumentIds: string[] = []; - - citedDocuments?.forEach((doc) => { - citedDocumentIds.push(doc[1].document_id); - }); - - if (!isComplete) { - const trimIncompleteCodeSection = ( - content: string | JSX.Element - ): string | JSX.Element => { - if (typeof content === "string") { - const pattern = /```[a-zA-Z]+[^\s]*$/; - const match = content.match(pattern); - if (match && match.index && match.index > 3) { - const newContent = content.slice(0, match.index - 3); - return newContent; + return content + (!isComplete && !toolCallGenerating ? " [*]() " : ""); + }; + const finalContent = processContent(content as string); + + const [isRegenerateHovered, setIsRegenerateHovered] = useState(false); + const { isHovering, trackedElementRef, hoverElementRef } = + useMouseTracking(); + + const settings = useContext(SettingsContext); + // this is needed to give Prism a chance to load + + const selectedDocumentIds = + selectedDocuments?.map((document) => document.document_id) || []; + let citedDocumentIds: string[] = []; + + citedDocuments?.forEach((doc) => { + citedDocumentIds.push(doc[1].document_id); + }); + + if (!isComplete) { + const trimIncompleteCodeSection = ( + content: string | JSX.Element + ): string | JSX.Element => { + if (typeof content === "string") { + const pattern = /```[a-zA-Z]+[^\s]*$/; + const match = content.match(pattern); + if (match && match.index && match.index > 3) { + const newContent = content.slice(0, match.index - 3); + return newContent; + } + return content; } return content; - } - return content; - }; - content = trimIncompleteCodeSection(content); - } - - let filteredDocs: FilteredDanswerDocument[] = []; - - if (docs) { - filteredDocs = docs - .filter( - (doc, index, self) => - doc.document_id && - doc.document_id !== "" && - index === self.findIndex((d) => d.document_id === doc.document_id) - ) - .filter((doc) => { - return citedDocumentIds.includes(doc.document_id); - }) - .map((doc: DanswerDocument, ind: number) => { - return { - ...doc, - included: selectedDocumentIds.includes(doc.document_id), - }; - }); - } - - const currentMessageInd = messageId - ? otherMessagesCanSwitchTo?.indexOf(messageId) - : undefined; - const uniqueSources: ValidSources[] = Array.from( - new Set((docs || []).map((doc) => doc.source_type)) - ).slice(0, 3); + }; + content = trimIncompleteCodeSection(content); + } - const includeMessageSwitcher = - currentMessageInd !== undefined && - onMessageSelection && - otherMessagesCanSwitchTo && - otherMessagesCanSwitchTo.length > 1; + // Memoize complex calculations or derived values + const filteredDocs = useMemo(() => { + let filteredDocs: FilteredDanswerDocument[] = []; + + if (docs) { + filteredDocs = docs + .filter( + (doc, index, self) => + doc.document_id && + doc.document_id !== "" && + index === self.findIndex((d) => d.document_id === doc.document_id) + ) + .filter((doc) => { + return citedDocumentIds.includes(doc.document_id); + }) + .map((doc: DanswerDocument, ind: number) => { + return { + ...doc, + included: selectedDocumentIds.includes(doc.document_id), + }; + }); + } - return ( -
+ return filteredDocs; + }, [docs, citedDocuments, selectedDocuments]); + + const uniqueSources = useMemo(() => { + return Array.from( + new Set((docs || []).map((doc) => doc.source_type)) + ).slice(0, 3); + }, [docs]); + + // Memoize callback functions + const memoizedHandleFeedback = useCallback( + (feedbackType: FeedbackType) => { + handleFeedback?.(feedbackType); + }, + [handleFeedback] + ); + + const memoizedToggleDocumentSelection = useCallback(() => { + toggleDocumentSelection?.(); + }, [toggleDocumentSelection]); + + const currentMessageInd = messageId + ? otherMessagesCanSwitchTo?.indexOf(messageId) + : undefined; + + const includeMessageSwitcher = + currentMessageInd !== undefined && + onMessageSelection && + otherMessagesCanSwitchTo && + otherMessagesCanSwitchTo.length > 1; + + const markdownComponents = useMemo( + () => ({ + a: MemoizedLink, + p: ({ children, ...props }: React.HTMLProps) => ( + + ), + code: (props: any) => ( + + ), + }), + [messageId, content] + ); + + return (
-
-
- - -
-
-
-
- {!toolCall || toolCall.tool_name === SEARCH_TOOL_NAME ? ( - <> - {query !== undefined && - handleShowRetrieved !== undefined && - !retrievalDisabled && ( -
- -
- )} - {handleForceSearch && - content && - query === undefined && - !hasDocs && - !retrievalDisabled && ( -
- +
+
+
+ + +
+
+
+
+ {!toolCall || toolCall.tool_name === SEARCH_TOOL_NAME ? ( + <> + {query !== undefined && + handleShowRetrieved !== undefined && + !retrievalDisabled && ( +
+ +
+ )} + {handleForceSearch && + content && + query === undefined && + !hasDocs && + !retrievalDisabled && ( +
+ +
+ )} + + ) : null} + + {toolCall && + !TOOLS_WITH_CUSTOM_HANDLING.includes( + toolCall.tool_name + ) && ( + + } + isRunning={!toolCall.tool_result || !content} + /> + )} + + {toolCall && + (!files || files.length == 0) && + toolCall.tool_name === IMAGE_GENERATION_TOOL_NAME && + !toolCall.tool_result && } + + {toolCall && + toolCall.tool_name === INTERNET_SEARCH_TOOL_NAME && ( + + } + isRunning={!toolCall.tool_result} + /> + )} + + {content || files ? ( + <> + + + {typeof content === "string" ? ( +
+ + {finalContent as string} +
+ ) : ( + content )} - - ) : null} - - {toolCall && - !TOOLS_WITH_CUSTOM_HANDLING.includes( - toolCall.tool_name - ) && ( - - } - isRunning={!toolCall.tool_result || !content} - /> + + ) : isComplete ? null : ( + <> )} - - {toolCall && - (!files || files.length == 0) && - toolCall.tool_name === IMAGE_GENERATION_TOOL_NAME && - !toolCall.tool_result && } - - {toolCall && - toolCall.tool_name === INTERNET_SEARCH_TOOL_NAME && ( - - } - isRunning={!toolCall.tool_result} - /> - )} - - {content || files ? ( - <> - - - {typeof content === "string" ? ( -
- ( - - ), - }} - remarkPlugins={[remarkGfm]} - rehypePlugins={[ - [rehypePrism, { ignoreMissing: true }], - ]} - > - {finalContent as string} - -
- ) : ( - content - )} - - ) : isComplete ? null : ( - <> - )} - {isComplete && docs && docs.length > 0 && ( -
-
-
- {!settings?.isMobile && - filteredDocs.length > 0 && - filteredDocs.slice(0, 2).map((doc, ind) => ( -
0 && ( +
+
+
+ {!settings?.isMobile && + filteredDocs.length > 0 && + filteredDocs.slice(0, 2).map((doc, ind) => ( +
- - -

- {doc.semantic_identifier || - doc.document_id} -

-
- {doc.is_internet ? ( - - ) : ( - - )} + + +

+ {doc.semantic_identifier || + doc.document_id} +

+
+ {doc.is_internet ? ( + + ) : ( + + )} +
+
+
+ +
+
+ {doc.blurb}
- -
-
-
- {doc.blurb} + ))} +
+
+

See context

+
+ {uniqueSources.map((sourceType, ind) => { + return ( +
+ +
+ ); + })}
- ))} -
{ - if (toggleDocumentSelection) { - toggleDocumentSelection(); - } - }} - key={-1} - className="cursor-pointer w-[200px] rounded-lg flex-none transition-all duration-500 hover:bg-background-125 bg-text-100 px-4 py-2 border-b" - > -
-

See context

-
- {uniqueSources.map((sourceType, ind) => { - return ( -
- -
- ); - })} +
+ See more
-
- See more -
-
- )} -
+ )} +
- {handleFeedback && - (isActive ? ( -
- -
- {includeMessageSwitcher && ( -
- { - onMessageSelection( - otherMessagesCanSwitchTo[ - currentMessageInd - 1 - ] - ); - }} - handleNext={() => { - onMessageSelection( - otherMessagesCanSwitchTo[ - currentMessageInd + 1 - ] - ); - }} - /> -
+ > + +
+ {includeMessageSwitcher && ( +
+ { + onMessageSelection( + otherMessagesCanSwitchTo[ + currentMessageInd - 1 + ] + ); + }} + handleNext={() => { + onMessageSelection( + otherMessagesCanSwitchTo[ + currentMessageInd + 1 + ] + ); + }} + /> +
+ )} +
+ + + + + } + onClick={() => memoizedHandleFeedback("like")} + /> + + + } + onClick={() => + memoizedHandleFeedback("dislike") + } + /> + + {regenerate && ( + )} -
- - - - - } - onClick={() => handleFeedback("like")} - /> - - - } - onClick={() => handleFeedback("dislike")} - /> - - {regenerate && ( - - )} -
-
- ) : ( -
+
+ ) : ( +
- -
- {includeMessageSwitcher && ( -
- { - onMessageSelection( - otherMessagesCanSwitchTo[ - currentMessageInd - 1 - ] - ); - }} - handleNext={() => { - onMessageSelection( - otherMessagesCanSwitchTo[ - currentMessageInd + 1 - ] - ); - }} - /> -
+ > + +
+ {includeMessageSwitcher && ( +
+ { + onMessageSelection( + otherMessagesCanSwitchTo[ + currentMessageInd - 1 + ] + ); + }} + handleNext={() => { + onMessageSelection( + otherMessagesCanSwitchTo[ + currentMessageInd + 1 + ] + ); + }} + /> +
+ )} +
+ + + + + + } + onClick={() => memoizedHandleFeedback("like")} + /> + + + + } + onClick={() => + memoizedHandleFeedback("dislike") + } + /> + + {regenerate && ( + )} -
- - - - - - } - onClick={() => handleFeedback("like")} - /> - - - - } - onClick={() => handleFeedback("dislike")} - /> - - {regenerate && ( - - )} -
-
- ))} + +
+ ))} +
+ {(!toolCall || toolCall.tool_name === SEARCH_TOOL_NAME) && + !query && + continueGenerating && ( + + )}
- {(!toolCall || toolCall.tool_name === SEARCH_TOOL_NAME) && - !query && - continueGenerating && ( - - )}
-
- ); -}; + ); + }, + (prevProps, nextProps) => { + // Custom comparison function to determine if re-render is necessary + const changedProps = Object.keys(nextProps).filter( + (key: string) => + prevProps[key as keyof typeof prevProps] !== + nextProps[key as keyof typeof nextProps] + ); + + if (changedProps.length > 0) { + console.log("AIMessage re-rendered due to changes in:", changedProps); + return false; // Re-render + } + + return true; // Don't re-render + } +); + +// Optionally, set a display name for easier debugging +AIMessage.displayName = "AIMessage"; function MessageSwitcher({ currentPage, diff --git a/web/src/app/chat/modal/SetDefaultModelModal.tsx b/web/src/app/chat/modal/SetDefaultModelModal.tsx index be49ec2934b..97e05cf59b0 100644 --- a/web/src/app/chat/modal/SetDefaultModelModal.tsx +++ b/web/src/app/chat/modal/SetDefaultModelModal.tsx @@ -17,7 +17,7 @@ export function SetDefaultModelModal({ defaultModel, refreshUser, }: { - setPopup: Dispatch>; + setPopup: (popupSpec: PopupSpec | null) => void; llmProviders: LLMProviderDescriptor[]; setLlmOverride: Dispatch>; onClose: () => void;