From 33ed9064b8ec45ef12011551a9e1ebb48886a8a7 Mon Sep 17 00:00:00 2001 From: tfomkin Date: Sat, 7 Feb 2026 01:47:05 +0800 Subject: [PATCH 1/3] fix: allow user to scroll up while response is generating --- .../components/messages-list/component.tsx | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/libs/mobile/chat/features/chat/src/lib/components/messages-list/component.tsx b/libs/mobile/chat/features/chat/src/lib/components/messages-list/component.tsx index 31b3eaf..0932ad7 100644 --- a/libs/mobile/chat/features/chat/src/lib/components/messages-list/component.tsx +++ b/libs/mobile/chat/features/chat/src/lib/components/messages-list/component.tsx @@ -1,7 +1,7 @@ import { FlashList } from '@shopify/flash-list'; import { useLocalSearchParams } from 'expo-router'; import { delay } from 'lodash-es'; -import React, { ReactElement, useCallback, useRef, useState } from 'react'; +import React, { ReactElement, useCallback, useRef } from 'react'; import { NativeScrollEvent, NativeSyntheticEvent } from 'react-native'; import { useSharedValue, withTiming } from 'react-native-reanimated'; import { AiMessageActions } from '@open-webui-react-native/mobile/chat/features/ai-message-actions'; @@ -62,7 +62,7 @@ export default function ChatMessagesList({ const isScrollToBottomAvailableTimeout = useRef(null); //NOTE: number needs to fix pipeline lint error const isScrollToBottomVisible = useSharedValue(0); const previousScrollY = useRef(0); - const [autoscrollToBottomThreshold, setAutoscrollToBottomThreshold] = useState(1); + const isNearBottomRef = useRef(true); const { showPreviousSibling, showNextSibling, getSiblingsInfo } = useManageMessageSiblings(chatId, history); const { mutate: completeChat } = chatApi.useCompleteChat(); @@ -80,6 +80,12 @@ export default function ChatMessagesList({ isScrollToBottomAvailable.current = true; }, 500); + if (isNearBottomRef.current && listRef.current && messages?.length > 0) { + requestAnimationFrame(() => { + listRef.current?.scrollToEnd({ animated: true }); + }); + } + if (!isMessagesListLoaded && listRef.current && messages?.length > 0) { delay(() => { listRef.current?.scrollToIndex({ @@ -108,6 +114,7 @@ export default function ChatMessagesList({ //NOTE: The indent of 100 is needed to display the button not immediately when we start scrolling, //but when a small distance has been scrolled. const isNearBottom = scrollY + containerHeight >= contentHeight - 100; + isNearBottomRef.current = isNearBottom; if (isNearBottom || isScrollingUp) { animateScrollToBottom(0); @@ -120,6 +127,8 @@ export default function ChatMessagesList({ //NOTE: Needs to hide scroll to bottom button to avoid its jumping while scrolling to bottom animateScrollToBottom(0); isScrollToBottomAvailable.current = false; + isNearBottomRef.current = true; + delay(() => { isScrollToBottomAvailable.current = true; }, 1000); @@ -128,7 +137,6 @@ export default function ChatMessagesList({ }; const handleEditPress = (index: number, messageId: string, content: string): void => { - setAutoscrollToBottomThreshold(undefined); onEditPress(messageId, content); delay(() => { listRef.current?.scrollToIndex({ @@ -137,9 +145,6 @@ export default function ChatMessagesList({ animated: true, }); }, 500); - delay(() => { - setAutoscrollToBottomThreshold(1); - }, 1000); }; const handleContinueResponsePress = (messageId: string): void => { @@ -243,12 +248,6 @@ export default function ChatMessagesList({ ItemSeparatorComponent={() => } data={messages} renderItem={renderItem} - // TODO: Add autoscrollToBottom logic when it implemented in lib - maintainVisibleContentPosition={{ - startRenderingFromBottom: true, - animateAutoScrollToBottom: true, - autoscrollToBottomThreshold, - }} onContentSizeChange={handleContentSizeChange} onScroll={handleScroll} scrollEventThrottle={16} From 2a9bca9c6dce5a9ed945f72295f5a67be87bd54d Mon Sep 17 00:00:00 2001 From: tfomkin Date: Wed, 11 Feb 2026 13:12:21 +0800 Subject: [PATCH 2/3] fix: enable maintainVisibleContentPosition startRenderingFromBottom --- .../chat/src/lib/components/messages-list/component.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libs/mobile/chat/features/chat/src/lib/components/messages-list/component.tsx b/libs/mobile/chat/features/chat/src/lib/components/messages-list/component.tsx index 0932ad7..d11f70c 100644 --- a/libs/mobile/chat/features/chat/src/lib/components/messages-list/component.tsx +++ b/libs/mobile/chat/features/chat/src/lib/components/messages-list/component.tsx @@ -248,6 +248,9 @@ export default function ChatMessagesList({ ItemSeparatorComponent={() => } data={messages} renderItem={renderItem} + maintainVisibleContentPosition={{ + startRenderingFromBottom: true, + }} onContentSizeChange={handleContentSizeChange} onScroll={handleScroll} scrollEventThrottle={16} From e91293dd91182f3881e9c18bacfcca475b9b19f7 Mon Sep 17 00:00:00 2001 From: tfomkin Date: Thu, 19 Feb 2026 17:59:20 +0800 Subject: [PATCH 3/3] fix: improved auto scroll behavior while response is generating --- .../components/messages-list/component.tsx | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/libs/mobile/chat/features/chat/src/lib/components/messages-list/component.tsx b/libs/mobile/chat/features/chat/src/lib/components/messages-list/component.tsx index d11f70c..88ddbb4 100644 --- a/libs/mobile/chat/features/chat/src/lib/components/messages-list/component.tsx +++ b/libs/mobile/chat/features/chat/src/lib/components/messages-list/component.tsx @@ -2,7 +2,7 @@ import { FlashList } from '@shopify/flash-list'; import { useLocalSearchParams } from 'expo-router'; import { delay } from 'lodash-es'; import React, { ReactElement, useCallback, useRef } from 'react'; -import { NativeScrollEvent, NativeSyntheticEvent } from 'react-native'; +import { GestureResponderEvent, NativeScrollEvent, NativeSyntheticEvent } from 'react-native'; import { useSharedValue, withTiming } from 'react-native-reanimated'; import { AiMessageActions } from '@open-webui-react-native/mobile/chat/features/ai-message-actions'; import { useManageMessageSiblings } from '@open-webui-react-native/mobile/chat/features/use-manage-messages-siblings'; @@ -62,7 +62,8 @@ export default function ChatMessagesList({ const isScrollToBottomAvailableTimeout = useRef(null); //NOTE: number needs to fix pipeline lint error const isScrollToBottomVisible = useSharedValue(0); const previousScrollY = useRef(0); - const isNearBottomRef = useRef(true); + const shouldAutoscrollToBottomRef = useRef(true); + const previousTouchY = useRef(0); const { showPreviousSibling, showNextSibling, getSiblingsInfo } = useManageMessageSiblings(chatId, history); const { mutate: completeChat } = chatApi.useCompleteChat(); @@ -80,7 +81,7 @@ export default function ChatMessagesList({ isScrollToBottomAvailable.current = true; }, 500); - if (isNearBottomRef.current && listRef.current && messages?.length > 0) { + if (shouldAutoscrollToBottomRef.current) { requestAnimationFrame(() => { listRef.current?.scrollToEnd({ animated: true }); }); @@ -114,7 +115,6 @@ export default function ChatMessagesList({ //NOTE: The indent of 100 is needed to display the button not immediately when we start scrolling, //but when a small distance has been scrolled. const isNearBottom = scrollY + containerHeight >= contentHeight - 100; - isNearBottomRef.current = isNearBottom; if (isNearBottom || isScrollingUp) { animateScrollToBottom(0); @@ -127,7 +127,6 @@ export default function ChatMessagesList({ //NOTE: Needs to hide scroll to bottom button to avoid its jumping while scrolling to bottom animateScrollToBottom(0); isScrollToBottomAvailable.current = false; - isNearBottomRef.current = true; delay(() => { isScrollToBottomAvailable.current = true; @@ -176,6 +175,23 @@ export default function ChatMessagesList({ onFollowUpPress(text); }; + const handleTouchStart = (e: GestureResponderEvent): void => { + if (!isResponseGenerating) return; + + shouldAutoscrollToBottomRef.current = false; + previousTouchY.current = e.nativeEvent.pageY; + }; + + const handleTouchMove = (e: GestureResponderEvent): void => { + if (!isResponseGenerating) return; + + const { pageY } = e.nativeEvent; + const deltaY = pageY - previousTouchY.current; + + previousTouchY.current = pageY; + shouldAutoscrollToBottomRef.current = deltaY < 0; + }; + const renderItem = useCallback( ({ item, index }: { item: Message; index: number }) => { const message = history?.messages[item.id]; @@ -253,6 +269,8 @@ export default function ChatMessagesList({ }} onContentSizeChange={handleContentSizeChange} onScroll={handleScroll} + onTouchStart={handleTouchStart} + onTouchMove={handleTouchMove} scrollEventThrottle={16} />