'use client'; import { Fragment, useEffect, useRef, useState } from 'react'; import MessageInput from './MessageInput'; import { File, Message } from './ChatWindow'; import MessageBox from './MessageBox'; import MessageBoxLoading from './MessageBoxLoading'; import { check } from 'drizzle-orm/gel-core'; const Chat = ({ loading, messages, sendMessage, scrollTrigger, rewrite, fileIds, setFileIds, files, setFiles, optimizationMode, setOptimizationMode, focusMode, setFocusMode, }: { messages: Message[]; sendMessage: ( message: string, options?: { messageId?: string; rewriteIndex?: number; suggestions?: string[]; }, ) => void; loading: boolean; scrollTrigger: number; rewrite: (messageId: string) => void; fileIds: string[]; setFileIds: (fileIds: string[]) => void; files: File[]; setFiles: (files: File[]) => void; optimizationMode: string; setOptimizationMode: (mode: string) => void; focusMode: string; setFocusMode: (mode: string) => void; }) => { const [dividerWidth, setDividerWidth] = useState(0); const [isAtBottom, setIsAtBottom] = useState(true); const [manuallyScrolledUp, setManuallyScrolledUp] = useState(false); const dividerRef = useRef(null); const messageEnd = useRef(null); const SCROLL_THRESHOLD = 250; // pixels from bottom to consider "at bottom" // Check if user is at bottom of page useEffect(() => { const checkIsAtBottom = () => { const position = window.innerHeight + window.scrollY; const height = document.body.scrollHeight; const atBottom = position >= height - SCROLL_THRESHOLD; setIsAtBottom(atBottom); }; // Initial check checkIsAtBottom(); // Add scroll event listener window.addEventListener('scroll', checkIsAtBottom); return () => { window.removeEventListener('scroll', checkIsAtBottom); }; }, []); // Detect wheel and touch events to identify user's scrolling direction useEffect(() => { const checkIsAtBottom = () => { const position = window.innerHeight + window.scrollY; const height = document.body.scrollHeight; const atBottom = position >= height - SCROLL_THRESHOLD; // If user scrolls to bottom, reset the manuallyScrolledUp flag if (atBottom) { setManuallyScrolledUp(false); } setIsAtBottom(atBottom); }; const handleWheel = (e: WheelEvent) => { // Positive deltaY means scrolling down, negative means scrolling up if (e.deltaY < 0) { // User is scrolling up setManuallyScrolledUp(true); } else if (e.deltaY > 0) { checkIsAtBottom(); } }; const handleTouchStart = (e: TouchEvent) => { // Immediately stop auto-scrolling on any touch interaction setManuallyScrolledUp(true); }; // Add event listeners window.addEventListener('wheel', handleWheel, { passive: true }); window.addEventListener('touchstart', handleTouchStart, { passive: true }); return () => { window.removeEventListener('wheel', handleWheel); window.removeEventListener('touchstart', handleTouchStart); }; }, [isAtBottom]); useEffect(() => { const updateDividerWidth = () => { if (dividerRef.current) { setDividerWidth(dividerRef.current.scrollWidth); } }; updateDividerWidth(); window.addEventListener('resize', updateDividerWidth); return () => { window.removeEventListener('resize', updateDividerWidth); }; }); // Scroll when user sends a message useEffect(() => { const scroll = () => { messageEnd.current?.scrollIntoView({ behavior: 'smooth' }); }; if (messages.length === 1) { document.title = `${messages[0].content.substring(0, 30)} - Perplexica`; } // Always scroll when user sends a message if (messages[messages.length - 1]?.role === 'user') { scroll(); setIsAtBottom(true); // Reset to true when user sends a message setManuallyScrolledUp(false); // Reset manually scrolled flag when user sends a message } }, [messages]); // Auto-scroll for assistant responses only if user is at bottom and hasn't manually scrolled up useEffect(() => { const position = window.innerHeight + window.scrollY; const height = document.body.scrollHeight; const atBottom = position >= height - SCROLL_THRESHOLD; setIsAtBottom(atBottom); if (isAtBottom && !manuallyScrolledUp && messages.length > 0) { messageEnd.current?.scrollIntoView({ behavior: 'smooth' }); } }, [scrollTrigger, isAtBottom, messages.length, manuallyScrolledUp]); return (
{messages.map((msg, i) => { const isLast = i === messages.length - 1; return ( {!isLast && msg.role === 'assistant' && (
)} ); })} {loading && }
{dividerWidth > 0 && (
{/* Scroll to bottom button - appears above the MessageInput when user has scrolled up */} {manuallyScrolledUp && !isAtBottom && (
)}
)}
); }; export default Chat;