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

This commit is contained in:
Dinh Long Nguyen 2025-09-29 17:27:26 +07:00 committed by GitHub
commit cf8ada5e8e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

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