From 3952913ad1324651e509e7718e7815594a650f97 Mon Sep 17 00:00:00 2001 From: rohis <63966639+Nix4444@users.noreply.github.com> Date: Mon, 9 Jun 2025 00:41:02 +0530 Subject: [PATCH 1/3] Issue #10: Add auto scroll and scroll to bottom --- frontend/components/Chat.tsx | 37 ++++++++++++++++++++++++++++++- frontend/components/ChatInput.tsx | 22 ++++++++++++++++-- 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/frontend/components/Chat.tsx b/frontend/components/Chat.tsx index 60ae42c..a7affba 100644 --- a/frontend/components/Chat.tsx +++ b/frontend/components/Chat.tsx @@ -12,6 +12,7 @@ import { SidebarTrigger, useSidebar } from './ui/sidebar'; import { Button } from './ui/button'; import { MessageSquareMore } from 'lucide-react'; import { useChatNavigator } from '@/frontend/hooks/useChatNavigator'; +import { useEffect, useRef, useState } from 'react'; interface ChatProps { threadId: string; @@ -22,7 +23,8 @@ export default function Chat({ threadId, initialMessages }: ChatProps) { const { getKey } = useAPIKeyStore(); const selectedModel = useModelStore((state) => state.selectedModel); const modelConfig = useModelStore((state) => state.getModelConfig()); - + const bottomDivRef = useRef(null); + const [isAtBottom, setIsAtBottom] = useState(true); const { isNavigatorVisible, handleToggleNavigator, @@ -68,6 +70,36 @@ export default function Chat({ threadId, initialMessages }: ChatProps) { }, }); + const scrollToBottom = () => { + bottomDivRef.current?.scrollIntoView({ behavior: 'smooth' }); + }; + + useEffect(() => { + scrollToBottom(); + if (status === 'streaming') { + const interval = setInterval(scrollToBottom, 100); + return () => clearInterval(interval); + } + }, [status]); + + useEffect(() => { + scrollToBottom(); + }, [threadId, messages]); + + useEffect(() => { + if (!bottomDivRef.current) return; + + const observer = new IntersectionObserver( + ([entry]) => { + setIsAtBottom(entry.isIntersecting); + } + ); + + observer.observe(bottomDivRef.current); + + return () => observer.disconnect(); + }, []); + return (
@@ -91,7 +123,10 @@ export default function Chat({ threadId, initialMessages }: ChatProps) { append={append} setInput={setInput} stop={stop} + scrollToBottom={scrollToBottom} + isAtBottom={isAtBottom} /> +
+ ); +}; + const SendButton = memo(PureSendButton, (prevProps, nextProps) => { return prevProps.disabled === nextProps.disabled; }); From 4f28d24a2dc1d1f48f33f418bf59ec775575c0b6 Mon Sep 17 00:00:00 2001 From: rohis <63966639+Nix4444@users.noreply.github.com> Date: Mon, 9 Jun 2025 02:08:30 +0530 Subject: [PATCH 2/3] memoized button and useCallback for function --- frontend/components/Chat.tsx | 6 +++--- frontend/components/ChatInput.tsx | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/frontend/components/Chat.tsx b/frontend/components/Chat.tsx index a7affba..fe0be35 100644 --- a/frontend/components/Chat.tsx +++ b/frontend/components/Chat.tsx @@ -12,7 +12,7 @@ import { SidebarTrigger, useSidebar } from './ui/sidebar'; import { Button } from './ui/button'; import { MessageSquareMore } from 'lucide-react'; import { useChatNavigator } from '@/frontend/hooks/useChatNavigator'; -import { useEffect, useRef, useState } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; interface ChatProps { threadId: string; @@ -70,9 +70,9 @@ export default function Chat({ threadId, initialMessages }: ChatProps) { }, }); - const scrollToBottom = () => { + const scrollToBottom = useCallback(() => { bottomDivRef.current?.scrollIntoView({ behavior: 'smooth' }); - }; + }, []); useEffect(() => { scrollToBottom(); diff --git a/frontend/components/ChatInput.tsx b/frontend/components/ChatInput.tsx index 817fc41..642020c 100644 --- a/frontend/components/ChatInput.tsx +++ b/frontend/components/ChatInput.tsx @@ -284,7 +284,7 @@ const PureSendButton = ({ onSubmit, disabled }: SendButtonProps) => { ); }; -const ScrollButton = ({ scrollToBottom }: ScrollButtonProps) => { +const PureScrollButton = ({ scrollToBottom }: ScrollButtonProps) => { return (