From 7f40f178ed0e595632aaaee92c363519cd112048 Mon Sep 17 00:00:00 2001 From: KVOJJJin Date: Wed, 28 Jan 2026 19:57:15 +0800 Subject: [PATCH] fix(app-log): fetching messages correctly when scrolling message list (#31655) --- web/app/components/app/log/list.spec.tsx | 4 +- web/app/components/app/log/list.tsx | 77 ++++++------------------ 2 files changed, 21 insertions(+), 60 deletions(-) diff --git a/web/app/components/app/log/list.spec.tsx b/web/app/components/app/log/list.spec.tsx index 81901c6cad..cb92ecc437 100644 --- a/web/app/components/app/log/list.spec.tsx +++ b/web/app/components/app/log/list.spec.tsx @@ -182,12 +182,12 @@ describe('Chat Message Loading Race Condition Prevention', () => { // Update pagination anchor with oldest answer ID const answerItems = chatItems.filter(item => item.isAnswer) - const oldestAnswer = answerItems[answerItems.length - 1] + const oldestAnswer = answerItems[0] if (oldestAnswer?.id) { oldestAnswerIdRef = oldestAnswer.id } - expect(oldestAnswerIdRef).toBe('answer-2') + expect(oldestAnswerIdRef).toBe('answer-1') }) it('should use pagination anchor in subsequent requests', () => { diff --git a/web/app/components/app/log/list.tsx b/web/app/components/app/log/list.tsx index 5197a02bb3..b13eec2e3d 100644 --- a/web/app/components/app/log/list.tsx +++ b/web/app/components/app/log/list.tsx @@ -321,7 +321,7 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) { // Update pagination anchor ref with the oldest answer ID const answerItems = allChatItems.filter(item => item.isAnswer) - const oldestAnswer = answerItems[answerItems.length - 1] + const oldestAnswer = answerItems[0] if (oldestAnswer?.id) oldestAnswerIdRef.current = oldestAnswer.id }, [allChatItems, hasMore, detail?.model_config?.configs?.introduction]) @@ -506,56 +506,18 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) { } }, [detail.id, hasMore, isLoading, timezone, t, appDetail, detail?.model_config?.configs?.introduction]) - useEffect(() => { + const handleScroll = useCallback(() => { const scrollableDiv = document.getElementById('scrollableDiv') - const outerDiv = scrollableDiv?.parentElement - const chatContainer = document.querySelector('.mx-1.mb-1.grow.overflow-auto') as HTMLElement - - let scrollContainer: HTMLElement | null = null - - if (outerDiv && outerDiv.scrollHeight > outerDiv.clientHeight) { - scrollContainer = outerDiv - } - else if (scrollableDiv && scrollableDiv.scrollHeight > scrollableDiv.clientHeight) { - scrollContainer = scrollableDiv - } - else if (chatContainer && chatContainer.scrollHeight > chatContainer.clientHeight) { - scrollContainer = chatContainer - } - else { - const possibleContainers = document.querySelectorAll('.overflow-auto, .overflow-y-auto') - for (let i = 0; i < possibleContainers.length; i++) { - const container = possibleContainers[i] as HTMLElement - if (container.scrollHeight > container.clientHeight) { - scrollContainer = container - break - } - } - } - - if (!scrollContainer) + if (!scrollableDiv) return + const clientHeight = scrollableDiv.clientHeight + const scrollHeight = scrollableDiv.scrollHeight + const currentScrollTop = scrollableDiv.scrollTop + // currentScrollTop is negative due to column-reverse flex direction + const isNearTop = Math.abs(currentScrollTop) > scrollHeight - clientHeight - 40 - const handleScroll = () => { - const currentScrollTop = scrollContainer!.scrollTop - const isNearTop = currentScrollTop < 30 - - if (isNearTop && hasMore && !isLoading) { - loadMoreMessages() - } - } - - scrollContainer.addEventListener('scroll', handleScroll, { passive: true }) - - const handleWheel = (e: WheelEvent) => { - if (e.deltaY < 0) - handleScroll() - } - scrollContainer.addEventListener('wheel', handleWheel, { passive: true }) - - return () => { - scrollContainer!.removeEventListener('scroll', handleScroll) - scrollContainer!.removeEventListener('wheel', handleWheel) + if (isNearTop && hasMore && !isLoading) { + loadMoreMessages() } }, [hasMore, isLoading, loadMoreMessages]) @@ -690,19 +652,10 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) { height: '100%', overflow: 'auto', }} + onScroll={handleScroll} > {/* Put the scroll bar always on the bottom */}
- {/* Loading state indicator - only shown when loading */} - {hasMore && isLoading && ( -
-
- {t('detail.loading', { ns: 'appLog' })} - ... -
-
- )} -
+ {hasMore && ( +
+
+ {t('detail.loading', { ns: 'appLog' })} + ... +
+
+ )} )}