diff --git a/src/web-ui/src/flow_chat/components/modern/VirtualMessageList.tsx b/src/web-ui/src/flow_chat/components/modern/VirtualMessageList.tsx index cf3c4476e..1b0e0c577 100644 --- a/src/web-ui/src/flow_chat/components/modern/VirtualMessageList.tsx +++ b/src/web-ui/src/flow_chat/components/modern/VirtualMessageList.tsx @@ -1553,15 +1553,15 @@ export const VirtualMessageList = forwardRef((_, ref) => }, [activeSession, isProcessing]); const scrollToLatestEndPositionInternal = useCallback((behavior: 'auto' | 'smooth') => { - if (virtuosoRef.current && virtualItems.length > 0) { + const scroller = scrollerElementRef.current; + if (scroller) { clearAllBottomReservationsForUserNavigation(); - virtuosoRef.current.scrollToIndex({ - index: virtualItems.length - 1, - align: 'end', + scroller.scrollTo({ + top: Math.max(0, scroller.scrollHeight - scroller.clientHeight), behavior, }); } - }, [clearAllBottomReservationsForUserNavigation, virtualItems.length]); + }, [clearAllBottomReservationsForUserNavigation]); const requestTurnPinToTop = useCallback((turnId: string, options?: { behavior?: ScrollBehavior; pinMode?: FlowChatPinTurnToTopMode }) => { const requestedPinMode = options?.pinMode ?? 'transient'; @@ -1896,15 +1896,15 @@ export const VirtualMessageList = forwardRef((_, ref) => }); const scrollToPhysicalBottomAndClearPin = useCallback(() => { - if (virtuosoRef.current && virtualItems.length > 0) { + const scroller = scrollerElementRef.current; + if (scroller) { clearAllBottomReservationsForUserNavigation(); - virtuosoRef.current.scrollToIndex({ - index: virtualItems.length - 1, - align: 'end', + scroller.scrollTo({ + top: Math.max(0, scroller.scrollHeight - scroller.clientHeight), behavior: 'smooth', }); } - }, [clearAllBottomReservationsForUserNavigation, virtualItems.length]); + }, [clearAllBottomReservationsForUserNavigation]); const scrollToLatestEndPosition = useCallback(() => { enterFollowOutput('jump-to-latest'); diff --git a/src/web-ui/src/flow_chat/utils/flowChatScrollLayout.ts b/src/web-ui/src/flow_chat/utils/flowChatScrollLayout.ts index e02e0c044..5ea2e7b7f 100644 --- a/src/web-ui/src/flow_chat/utils/flowChatScrollLayout.ts +++ b/src/web-ui/src/flow_chat/utils/flowChatScrollLayout.ts @@ -14,6 +14,7 @@ export const SCROLL_TO_LATEST_INPUT_CLEARANCE_PX = 6; const FALLBACK_INPUT_BLOCK_ACTIVE_PX = 96; const FALLBACK_INPUT_BLOCK_COLLAPSED_PX = 54; +const NORMAL_INPUT_BLOCK_SAFE_PX = 96; /** * Height of the Virtuoso footer spacer needed so the last message clears the floating input. @@ -23,11 +24,11 @@ export function computeFlowChatInputStackFooterPx( measuredInputHeight: number, isInputActive: boolean, ): number { - const inputBlock = - measuredInputHeight > 0 - ? measuredInputHeight - : isInputActive - ? FALLBACK_INPUT_BLOCK_ACTIVE_PX - : FALLBACK_INPUT_BLOCK_COLLAPSED_PX; + const measuredInputBlock = measuredInputHeight > 0 + ? measuredInputHeight + : isInputActive + ? FALLBACK_INPUT_BLOCK_ACTIVE_PX + : FALLBACK_INPUT_BLOCK_COLLAPSED_PX; + const inputBlock = Math.max(measuredInputBlock, NORMAL_INPUT_BLOCK_SAFE_PX); return inputBlock + CHAT_INPUT_DROP_ZONE_BOTTOM_PX + FLOWCHAT_MESSAGE_TAIL_CLEARANCE_PX; }