0x1998 - MANAGER
Düzenlenen Dosya: ChatMessages.jsx
import { AgentMessage } from '@agent/components/messages/AgentMessage'; import { StatusMessage } from '@agent/components/messages/StatusMessage'; import { UserMessage } from '@agent/components/messages/UserMessage'; import { WorkflowComponent } from '@agent/components/messages/WorkflowComponent'; import { WorkflowMessage } from '@agent/components/messages/WorkflowMessage'; import { ScrollDownButton } from '@agent/components/ScrollDownButton'; import { ScrollIntoViewOnce } from '@agent/components/ScrollIntoViewOnce'; import { useWhenFinishedToolProps } from '@agent/hooks/useWhenFinishedToolProps'; import { useChatStore } from '@agent/state/chat'; import { useGlobalStore } from '@agent/state/global'; import { useWorkflowStore } from '@agent/state/workflows'; import { createElement, useEffect, useLayoutEffect, useRef, useState, } from '@wordpress/element'; export const ChatMessages = () => { const { open } = useGlobalStore(); const { messages } = useChatStore(); const { getWorkflow } = useWorkflowStore(); const workflow = getWorkflow(); const whenFinishedToolProps = useWhenFinishedToolProps(); const whenFinishedComponent = workflow?.whenFinished?.component; const [canScrollDown, setCanScrollDown] = useState(false); const containerRef = useRef(null); const isFreshPageLoad = useRef(true); const [ready, setReady] = useState(false); // If last message is a user message, move it to the top const isUserMessage = messages.filter(({ type }) => type !== 'status').at(-1)?.details?.role === 'user'; useEffect(() => { if (!containerRef.current || !open) return; if (!isFreshPageLoad.current) return; isFreshPageLoad.current = false; // Scroll to the bottom of the chat container on load const c = containerRef.current; const last = c.querySelector( '#extendify-agent-chat-scroll-area > :last-child', ); let id2; const id = requestAnimationFrame(() => { id2 = requestAnimationFrame(() => { last?.scrollIntoView({ behavior: 'auto', block: 'start' }); setReady(true); }); }); return () => { cancelAnimationFrame(id); cancelAnimationFrame(id2); isFreshPageLoad.current = true; setReady(false); }; }, [open]); // Handles scrolling to the top of the last user message // TODO: if the user sends in a long message, maybe we scroll to the bottom // of the message offset by 2-3 lines useEffect(() => { if (!containerRef.current) return; if (!isUserMessage) return; const c = containerRef.current; const messages = c.querySelectorAll('[data-agent-message-role="user"]'); const last = messages[messages.length - 1]; if (!last || messages.length < 2) return; const scrollArea = c.querySelector('#extendify-agent-chat-scroll-area'); const lastRect = last.getBoundingClientRect(); const innerHeight = Array.from(scrollArea.children).reduce( (sum, child) => sum + child.offsetHeight, 0, ); const minHeight = innerHeight + c.clientHeight - lastRect.height; scrollArea.style.minHeight = `${minHeight}px`; last.scrollIntoView({ behavior: 'smooth', block: 'start' }); }, [isUserMessage, messages]); // Handles the scroll down button visibility useLayoutEffect(() => { const c = containerRef.current; if (!c) return; const last = c.querySelector( '#extendify-agent-chat-scroll-area > :last-child', ); if (!last) return; const areWeAtBottom = c.scrollTop + c.clientHeight >= c.scrollHeight - 4; if (areWeAtBottom) return setCanScrollDown(false); const observer = new IntersectionObserver( ([entry]) => { const last = c.querySelector( '#extendify-agent-chat-scroll-area > :last-child', ); if (!last) return setCanScrollDown(false); const areWeAtBottom = c.scrollTop + c.clientHeight >= c.scrollHeight - 4; if (areWeAtBottom) return setCanScrollDown(false); setCanScrollDown(!entry.isIntersecting); }, { root: c, threshold: 0.1 }, ); observer.observe(last); return () => observer.disconnect(); }, [messages]); return ( <div ref={containerRef} style={{ overscrollBehavior: 'contain' }} className="relative grow overflow-y-auto overflow-x-hidden p-1 pb-0 text-sm text-gray-900 md:p-2" > <div id="extendify-agent-chat-scroll-area" className={ready ? '' : 'invisible pointer-events-none'} > {messages.map((message) => { const isLastMessage = messages.at(-1)?.id === message.id; const freshLoad = isFreshPageLoad.current; if (message.details?.role === 'user') { return <UserMessage key={message.id} message={message} />; } if (message.details?.role === 'assistant') { return ( <AgentMessage key={message.id} animate={!freshLoad} message={message} /> ); } if (message.type === 'workflow') { return <WorkflowMessage key={message.id} message={message} />; } if (message.type === 'workflow-component') { return <WorkflowComponent key={message.id} message={message} />; } if ( message.type === 'status' && // Only show the status if it's last, or a workflow-tool-completed message (isLastMessage || ['workflow-tool-completed', 'workflow-canceled'].includes( message.details?.type, )) ) { const isError = message.details?.type === 'error'; return ( <StatusMessage animate={!isError} key={message.id} status={message} /> ); } return null; })} {!workflow?.needsRedirect?.() && whenFinishedToolProps?.id && whenFinishedComponent ? ( <ScrollIntoViewOnce> {createElement(whenFinishedComponent, whenFinishedToolProps)} </ScrollIntoViewOnce> ) : null} {workflow?.needsRedirect?.() ? <workflow.redirectComponent /> : null} </div> <ScrollDownButton canScrollDown={canScrollDown} onClick={() => { if (!containerRef.current) return; const c = containerRef.current; // Scroll the last message into view const last = c.querySelector( '#extendify-agent-chat-scroll-area > :last-child', ); if (!last) return; last.scrollIntoView({ behavior: 'smooth', block: 'start' }); }} /> </div> ); };
geri dön