From b0d3d485cf21219eef6a359a46ff0690e023d3df Mon Sep 17 00:00:00 2001 From: Faisal Amir Date: Mon, 2 Jun 2025 22:00:12 +0700 Subject: [PATCH] fix: sticky action scroll to bottom when edit message (#5169) --- web-app/src/routes/threads/$threadId.tsx | 29 +++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/web-app/src/routes/threads/$threadId.tsx b/web-app/src/routes/threads/$threadId.tsx index 3403656fa..7b05a6871 100644 --- a/web-app/src/routes/threads/$threadId.tsx +++ b/web-app/src/routes/threads/$threadId.tsx @@ -27,6 +27,7 @@ function ThreadDetail() { const { threadId } = useParams({ from: Route.id }) const [isUserScrolling, setIsUserScrolling] = useState(false) const [isAtBottom, setIsAtBottom] = useState(true) + const [hasScrollbar, setHasScrollbar] = useState(false) const lastScrollTopRef = useRef(0) const { currentThreadId, setCurrentThreadId } = useThreads() const { setCurrentAssistant, assistants } = useAssistant() @@ -45,6 +46,19 @@ function ThreadDetail() { const isFirstRender = useRef(true) const messagesCount = useMemo(() => messages?.length ?? 0, [messages]) + // Function to check scroll position and scrollbar presence + const checkScrollState = () => { + const scrollContainer = scrollContainerRef.current + if (!scrollContainer) return + + const { scrollTop, scrollHeight, clientHeight } = scrollContainer + const isBottom = Math.abs(scrollHeight - scrollTop - clientHeight) < 10 + const hasScroll = scrollHeight > clientHeight + + setIsAtBottom(isBottom) + setHasScrollbar(hasScroll) + } + useEffect(() => { if (currentThreadId !== threadId) { setCurrentThreadId(threadId) @@ -86,6 +100,7 @@ function ThreadDetail() { scrollToBottom() setIsAtBottom(true) setIsUserScrolling(false) + checkScrollState() return } }, []) @@ -96,6 +111,7 @@ function ThreadDetail() { scrollToBottom() setIsAtBottom(true) setIsUserScrolling(false) + checkScrollState() }, [threadId]) // Single useEffect for all auto-scrolling logic @@ -109,6 +125,13 @@ function ThreadDetail() { // eslint-disable-next-line react-hooks/exhaustive-deps }, [streamingContent, isUserScrolling, messagesCount]) + useEffect(() => { + if (streamingContent) { + const interval = setInterval(checkScrollState, 100) + return () => clearInterval(interval) + } + }, [streamingContent]) + const scrollToBottom = (smooth = false) => { if (scrollContainerRef.current) { scrollContainerRef.current.scrollTo({ @@ -123,12 +146,14 @@ function ThreadDetail() { const { scrollTop, scrollHeight, clientHeight } = target // Use a small tolerance to better detect when we're at the bottom const isBottom = Math.abs(scrollHeight - scrollTop - clientHeight) < 10 + const hasScroll = scrollHeight > clientHeight // Detect if this is a user-initiated scroll if (Math.abs(scrollTop - lastScrollTopRef.current) > 10) { setIsUserScrolling(!isBottom) } setIsAtBottom(isBottom) + setHasScrollbar(hasScroll) lastScrollTopRef.current = scrollTop } @@ -138,12 +163,14 @@ function ThreadDetail() { const { scrollTop, scrollHeight, clientHeight } = target // Use a small tolerance to better detect when we're at the bottom const isBottom = Math.abs(scrollHeight - scrollTop - clientHeight) < 10 + const hasScroll = scrollHeight > clientHeight // Detect if this is a user-initiated scroll if (Math.abs(scrollTop - lastScrollTopRef.current) > 10) { setIsUserScrolling(!isBottom) } setIsAtBottom(isBottom) + setHasScrollbar(hasScroll) lastScrollTopRef.current = scrollTop } @@ -215,7 +242,7 @@ function ThreadDetail() {