fix: scroll issue padding not re render correctly (#6639)

This commit is contained in:
Dinh Long Nguyen 2025-09-29 16:45:40 +07:00 committed by dinhlongviolin1
parent 0cf1b830cf
commit 0dbf3b1652

View File

@ -3,7 +3,8 @@ import { useAppState } from './useAppState'
import { useMessages } from './useMessages'
const VIEWPORT_PADDING = 40 // Offset from viewport bottom for user message positioning
const MAX_DOM_RETRY_ATTEMPTS = 3 // Maximum attempts to find DOM elements before giving up
const MAX_DOM_RETRY_ATTEMPTS = 5 // Maximum attempts to find DOM elements before giving up
const DOM_RETRY_DELAY = 100 // Delay in ms between DOM element retry attempts
export const useThreadScrolling = (
threadId: string,
@ -16,6 +17,7 @@ export const useThreadScrolling = (
const [isAtBottom, setIsAtBottom] = useState(true)
const [hasScrollbar, setHasScrollbar] = useState(false)
const lastScrollTopRef = useRef(0)
const lastAssistantMessageRef = useRef<HTMLElement | null>(null)
const messageCount = useMessages((state) => state.messages[threadId]?.length ?? 0)
const lastMessageRole = useMessages((state) => {
@ -33,13 +35,12 @@ export const useThreadScrolling = (
const userMessages = scrollContainer.querySelectorAll('[data-message-author-role="user"]')
const assistantMessages = scrollContainer.querySelectorAll('[data-message-author-role="assistant"]')
return {
scrollContainer,
lastUserMessage: userMessages[userMessages.length - 1] as HTMLElement,
lastAssistantMessage: assistantMessages[assistantMessages.length - 1] as HTMLElement,
}
}, [])
}, [scrollContainerRef])
const showScrollToBottomBtn = !isAtBottom && hasScrollbar
@ -121,6 +122,7 @@ export const useThreadScrolling = (
setPaddingHeight(calculatedPadding)
originalPaddingRef.current = calculatedPadding
// Scroll after padding is applied to the DOM
requestAnimationFrame(() => {
elements.scrollContainer.scrollTo({
top: elements.scrollContainer.scrollHeight,
@ -136,11 +138,11 @@ export const useThreadScrolling = (
calculatePadding()
} else if (retryCount < MAX_DOM_RETRY_ATTEMPTS) {
retryCount++
requestAnimationFrame(tryCalculatePadding)
setTimeout(tryCalculatePadding, DOM_RETRY_DELAY)
}
}
requestAnimationFrame(tryCalculatePadding)
tryCalculatePadding()
}
prevCountRef.current = messageCount
@ -150,23 +152,48 @@ export const useThreadScrolling = (
const previouslyStreaming = wasStreamingRef.current
const currentlyStreaming = !!streamingContent && streamingContent.thread_id === threadId
const streamingStarted = !previouslyStreaming && currentlyStreaming
const streamingEnded = previouslyStreaming && !currentlyStreaming
const hasPaddingToAdjust = originalPaddingRef.current > 0
// Store the current assistant message when streaming starts
if (streamingStarted) {
const elements = getDOMElements()
lastAssistantMessageRef.current = elements?.lastAssistantMessage || null
}
if (streamingEnded && hasPaddingToAdjust) {
requestAnimationFrame(() => {
let retryCount = 0
const adjustPaddingWhenReady = () => {
const elements = getDOMElements()
if (!elements?.lastAssistantMessage || !elements?.lastUserMessage) return
const currentAssistantMessage = elements?.lastAssistantMessage
const userRect = elements.lastUserMessage.getBoundingClientRect()
const assistantRect = elements.lastAssistantMessage.getBoundingClientRect()
const actualSpacing = assistantRect.top - userRect.bottom
const totalAssistantHeight = elements.lastAssistantMessage.offsetHeight + actualSpacing
const newPadding = Math.max(0, originalPaddingRef.current - totalAssistantHeight)
// Check if a new assistant message has appeared (different from the one before streaming)
const hasNewAssistantMessage = currentAssistantMessage &&
currentAssistantMessage !== lastAssistantMessageRef.current
setPaddingHeight(newPadding)
originalPaddingRef.current = newPadding
})
if (hasNewAssistantMessage && elements?.lastUserMessage) {
const userRect = elements.lastUserMessage.getBoundingClientRect()
const assistantRect = currentAssistantMessage.getBoundingClientRect()
const actualSpacing = assistantRect.top - userRect.bottom
const totalAssistantHeight = currentAssistantMessage.offsetHeight + actualSpacing
const newPadding = Math.max(0, originalPaddingRef.current - totalAssistantHeight)
setPaddingHeight(newPadding)
originalPaddingRef.current = newPadding
lastAssistantMessageRef.current = currentAssistantMessage
} else if (retryCount < MAX_DOM_RETRY_ATTEMPTS) {
retryCount++
setTimeout(adjustPaddingWhenReady, DOM_RETRY_DELAY)
} else {
// Max retries hit - remove padding as fallback
setPaddingHeight(0)
originalPaddingRef.current = 0
}
}
adjustPaddingWhenReady()
}
wasStreamingRef.current = currentlyStreaming