From 0db3fb120e758dffae4a0031deb94bc96dde6aec Mon Sep 17 00:00:00 2001 From: Charles Howard <96023061+charlesrhoward@users.noreply.github.com> Date: Sat, 21 Feb 2026 16:27:58 -0500 Subject: [PATCH] perf: memoize chat message rendering --- components/ai-chat/chat-messages.tsx | 145 ++++++++++++++++++--------- 1 file changed, 96 insertions(+), 49 deletions(-) diff --git a/components/ai-chat/chat-messages.tsx b/components/ai-chat/chat-messages.tsx index d6786fe..0321b06 100644 --- a/components/ai-chat/chat-messages.tsx +++ b/components/ai-chat/chat-messages.tsx @@ -4,6 +4,7 @@ import { MessageSquare, Loader2, Brain } from "lucide-react" import type { UIMessage } from "ai" import ReactMarkdown from "react-markdown" import Image from "next/image" +import { memo, useMemo } from "react" /** * Sanitize image URLs to prevent XSS attacks @@ -44,6 +45,90 @@ interface ChatMessagesProps { isLoading: boolean } +function getRenderablePartSignature(message: UIMessage): string { + if (!message.parts || message.parts.length === 0) return "" + + return message.parts + .filter((part) => part.type === "text" || part.type === "file") + .map((part) => { + if (part.type === "text") { + return `text:${part.text}` + } + + if (part.type === "file") { + return `file:${part.url || ""}:${part.filename || ""}:${part.mediaType || ""}` + } + + return "" + }) + .join("|") +} + +const AssistantMarkdown = memo( + function AssistantMarkdown({ text }: { text: string }) { + return ( +
+ {part.text} +
+ ) + } + + if (part.type === "file") { + const sanitizedUrl = sanitizeImageUrl(part.url) + return ( +- {part.text} -
- ) - } - - // Handle file parts (images) - if (part.type === "file") { - const sanitizedUrl = sanitizeImageUrl(part.url) - return ( -