From 952dee955c3903c56fe6e06ef8f8456005800f69 Mon Sep 17 00:00:00 2001 From: Abdul Rahman <81605929+abdulrahmancodes@users.noreply.github.com> Date: Sat, 27 Dec 2025 13:49:44 +0530 Subject: [PATCH] fix: Stop autoscroll when user manually scrolls up during streaming (#16795) https://github.com/user-attachments/assets/2ca908ca-63b8-4895-9ed7-7beaeea13ca9 --- .../ai/components/AgentChatMessagesEffect.tsx | 14 +++++++++++--- .../modules/ai/hooks/useAgentChatScrollToBottom.ts | 13 ++++++++++++- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/packages/twenty-front/src/modules/ai/components/AgentChatMessagesEffect.tsx b/packages/twenty-front/src/modules/ai/components/AgentChatMessagesEffect.tsx index 17b205c406..6ea6a5632f 100644 --- a/packages/twenty-front/src/modules/ai/components/AgentChatMessagesEffect.tsx +++ b/packages/twenty-front/src/modules/ai/components/AgentChatMessagesEffect.tsx @@ -8,7 +8,7 @@ export const AgentChatMessagesEffect = ({ }: { messages: ExtendedUIMessage[]; }) => { - const { scrollToBottom } = useAgentChatScrollToBottom(); + const { scrollToBottom, isNearBottom } = useAgentChatScrollToBottom(); const [, setPreviousMessages] = useState(null); useEffect(() => { @@ -22,10 +22,18 @@ export const AgentChatMessagesEffect = ({ // We intentionally force this effect because the chat transport streams messages incrementally // and the only reliable way to react to those chunks is through useEffect updates. - scrollToBottom(); + + const isNewMessage = + previousMessages === null || + messages.length !== previousMessages.length; + + if (isNewMessage || isNearBottom) { + scrollToBottom(); + } + return messages; }); - }, [messages, scrollToBottom]); + }, [messages, scrollToBottom, isNearBottom]); return null; }; diff --git a/packages/twenty-front/src/modules/ai/hooks/useAgentChatScrollToBottom.ts b/packages/twenty-front/src/modules/ai/hooks/useAgentChatScrollToBottom.ts index 51a724c3ac..b8bf2b5ee1 100644 --- a/packages/twenty-front/src/modules/ai/hooks/useAgentChatScrollToBottom.ts +++ b/packages/twenty-front/src/modules/ai/hooks/useAgentChatScrollToBottom.ts @@ -1,12 +1,23 @@ import { AI_CHAT_SCROLL_WRAPPER_ID } from '@/ai/constants/AiChatScrollWrapperId'; import { useScrollWrapperHTMLElement } from '@/ui/utilities/scroll/hooks/useScrollWrapperHTMLElement'; +import { scrollWrapperScrollBottomComponentState } from '@/ui/utilities/scroll/states/scrollWrapperScrollBottomComponentState'; +import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { isDefined } from 'twenty-shared/utils'; +const SCROLL_BOTTOM_THRESHOLD_PX = 10; + export const useAgentChatScrollToBottom = () => { const { getScrollWrapperElement } = useScrollWrapperHTMLElement( AI_CHAT_SCROLL_WRAPPER_ID, ); + const scrollBottom = useRecoilComponentValue( + scrollWrapperScrollBottomComponentState, + AI_CHAT_SCROLL_WRAPPER_ID, + ); + + const isNearBottom = scrollBottom <= SCROLL_BOTTOM_THRESHOLD_PX; + const scrollToBottom = () => { const { scrollWrapperElement } = getScrollWrapperElement(); if (!isDefined(scrollWrapperElement)) { @@ -18,5 +29,5 @@ export const useAgentChatScrollToBottom = () => { }); }; - return { scrollToBottom }; + return { scrollToBottom, isNearBottom }; };