import { useCallback, useEffect, useRef, useState } from "react"; import { Virtuoso } from "react-virtuoso"; import { renderMessage } from "./renderMessage"; import "./chat-message-list.styles.scss"; export default function ChatMessageListComponent({ messages }) { const virtuosoRef = useRef(null); const [atBottom, setAtBottom] = useState(true); const loadedImagesRef = useRef(0); const handleScrollStateChange = (isAtBottom) => { setAtBottom(isAtBottom); }; const resetImageLoadState = () => { loadedImagesRef.current = 0; }; const preloadImages = useCallback((imagePaths, onComplete) => { resetImageLoadState(); if (imagePaths.length === 0) { onComplete(); return; } imagePaths.forEach((url) => { const img = new Image(); img.src = url; img.onload = img.onerror = () => { loadedImagesRef.current += 1; if (loadedImagesRef.current === imagePaths.length) { onComplete(); } }; }); }, []); // Ensure all images are loaded on initial render useEffect(() => { const imagePaths = messages .filter((message) => message.image && message.image_path?.length > 0) .flatMap((message) => message.image_path); preloadImages(imagePaths, () => { if (virtuosoRef.current) { virtuosoRef.current.scrollToIndex({ index: messages.length - 1, align: "end", behavior: "auto" }); } }); }, [messages, preloadImages]); // Handle scrolling when new messages are added useEffect(() => { if (!atBottom) return; const latestMessage = messages[messages.length - 1]; const imagePaths = latestMessage?.image_path || []; preloadImages(imagePaths, () => { if (virtuosoRef.current) { virtuosoRef.current.scrollToIndex({ index: messages.length - 1, align: "end", behavior: "smooth" }); } }); }, [messages, atBottom, preloadImages]); return (
acc + (message.image_path?.length || 0), 0) ? messages.length : 0} itemContent={(index) => renderMessage(messages, index)} followOutput={(isAtBottom) => handleScrollStateChange(isAtBottom)} initialTopMostItemIndex={messages.length - 1} style={{ height: "100%", width: "100%" }} />
); }