import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import MessageWrapper from './MessageWrapper'; import QuickActions from './QuickActions'; import InputSection from './InputSection'; import StreamingAssistantMessage from './Messages/StreamingAssistantMessage'; // import { WPAssistantLogo } from '../Logos/WPAssistantLogo'; import { useCurrentConversationId, useMessages, useSendMessage, useRetryError, useDismissError, useCurrentConversationState, } from '../../contexts/ChatContext'; import useRenderTracker from '../../hooks/useRenderTracker'; // Threshold distance for auto-scrolling to bottom const BOTTOM_THRESHOLD = 20; interface ChatAreaProps { scrollRef: React.RefObject; scrollToBottom: (behavior: ScrollBehavior) => void; scrollToTop: (behavior: ScrollBehavior) => void; } const ChatArea = React.memo(({ scrollRef, scrollToBottom, scrollToTop }) => { const currentConversationId = useCurrentConversationId(); const messages = useMessages(); const sendMessage = useSendMessage(); const retryError = useRetryError(); const dismissError = useDismissError(); const currentConversationState = useCurrentConversationState(); // So we can refer to the container if needed const chatContainerRef = useRef(null); useRenderTracker('ChatArea', { currentConversationId, messages, sendMessage, retryError, dismissError, currentConversationState, }); // Retry & Dismiss const handleRetry = useCallback( (id: string) => { retryError(id); }, [retryError] ); const handleDismiss = useCallback( (id: string) => { dismissError(id); }, [dismissError] ); // Sending a user message const handleSendMessage = useCallback( async (text: string) => { sendMessage(text); }, [sendMessage] ); // currentConversation object (for potential future usage) const currentConversation = useMemo(() => { if (!currentConversationId) return null; return { id: currentConversationId, messages: messages || [], phase: currentConversationState?.phase, }; }, [currentConversationId, messages, currentConversationState]); // Completed messages const completedMessages = useMemo(() => { if (!currentConversationId) return null; let userTurnIndex = 0; let currentTurnKey: string | undefined; return messages.map((message) => { if (message.role === 'user') { userTurnIndex += 1; currentTurnKey = `${currentConversationId}:turn:${userTurnIndex}`; } const turnKey = message.role === 'assistant' ? currentTurnKey : undefined; return ( ); }); }, [messages, handleRetry, handleDismiss, currentConversationId]); const pendingAssistantMessage = useMemo(() => { if (!currentConversationId) return null; if (currentConversationState?.phase !== 'THINKING' && currentConversationState?.phase !== 'STREAMING') { return null; } return ; }, [currentConversationState, currentConversationId]); // Rendered messages const renderedMessages = useMemo(() => { return [...(completedMessages || []), pendingAssistantMessage].filter(Boolean); }, [completedMessages, pendingAssistantMessage]); // Autoscroll partial streaming if user is near bottom const [userHasScrolledUp, setUserHasScrolledUp] = useState(false); const handleScroll = useCallback(() => { const el = scrollRef.current; if (!el) return; const distanceFromBottom = el.scrollHeight - (el.scrollTop + el.clientHeight); setUserHasScrolledUp(!(distanceFromBottom <= BOTTOM_THRESHOLD)); }, [scrollRef]); useEffect(() => { const el = scrollRef.current; if (!el) return; el.addEventListener('scroll', handleScroll); return () => { el.removeEventListener('scroll', handleScroll); }; }, [scrollRef, handleScroll]); useEffect(() => { // If there are any partial messages streaming and user is near bottom => auto scroll if (!currentConversationId) return; if (currentConversation?.phase !== 'STREAMING' && currentConversation?.phase !== 'THINKING') return; if (!userHasScrolledUp) { scrollToBottom('auto'); } }, [currentConversation?.phase, userHasScrolledUp, scrollToBottom]); // Detect conversation switches that the user explicitly triggered const prevConversationIdRef = useRef(null); const [hasJustChangedConversation, setHasJustChangedConversation] = useState(false); useEffect(() => { const oldConversation = prevConversationIdRef.current; const newConversation = currentConversationId || null; // If we have no newConversation, do nothing if (!newConversation) { prevConversationIdRef.current = newConversation; return; } const isConversationSwitch = oldConversation && oldConversation !== newConversation; const isFirstConversation = !oldConversation; if (isConversationSwitch || isFirstConversation) { // Immediately scroll top so user doesn't see partial old messages scrollToTop('auto'); // Then do a "smooth scroll down" once the new conversation's messages have loaded setHasJustChangedConversation(true); } prevConversationIdRef.current = newConversation; }, [currentConversationId, scrollToTop]); useEffect(() => { if (hasJustChangedConversation && messages.length > 0) { scrollToBottom('smooth'); setHasJustChangedConversation(false); } }, [hasJustChangedConversation, messages, scrollToBottom]); // Render return (
{!currentConversationId ? (

How can I help you with WordPress?

Examples
) : (
{renderedMessages}
)}
); }); export default ChatArea;