From 8e4e423f1aa55517f5869784656e1bf23dc7da23 Mon Sep 17 00:00:00 2001 From: Nate Kelley Date: Thu, 10 Apr 2025 12:56:03 -0600 Subject: [PATCH] chat scroll --- .../ReasoningController.tsx | 52 +++++++++++++------ .../ReasoningScrollToBottom.tsx | 31 +++++++++++ web/src/hooks/useAutoScroll.ts | 1 - .../ChatContent/ChatScrollToBottom.tsx | 4 +- 4 files changed, 70 insertions(+), 18 deletions(-) create mode 100644 web/src/controllers/ReasoningController/ReasoningScrollToBottom.tsx diff --git a/web/src/controllers/ReasoningController/ReasoningController.tsx b/web/src/controllers/ReasoningController/ReasoningController.tsx index ec4cadfa3..5f71a1965 100644 --- a/web/src/controllers/ReasoningController/ReasoningController.tsx +++ b/web/src/controllers/ReasoningController/ReasoningController.tsx @@ -1,11 +1,14 @@ 'use client'; -import React from 'react'; +import React, { useEffect, useRef } from 'react'; import { useGetChat, useGetChatMessage } from '@/api/buster_rest/chats'; import { ReasoningMessageSelector } from './ReasoningMessages'; import { BlackBoxMessage } from './ReasoningMessages/ReasoningBlackBoxMessage'; import { FileIndeterminateLoader } from '@/components/features/FileIndeterminateLoader'; import { ScrollArea } from '@/components/ui/scroll-area'; +import { useAutoScroll } from '@/hooks/useAutoScroll'; +import isEmpty from 'lodash/isEmpty'; +import { ReasoningScrollToBottom } from './ReasoningScrollToBottom'; interface ReasoningControllerProps { chatId: string; @@ -16,24 +19,43 @@ export const ReasoningController: React.FC = ({ chatId const { data: hasChat } = useGetChat({ id: chatId || '' }, (x) => !!x.id); const reasoningMessageIds = useGetChatMessage(messageId, (x) => x?.reasoning_message_ids); const isCompletedStream = useGetChatMessage(messageId, (x) => x?.isCompletedStream); + const viewportRef = useRef(null); + + const { isAutoScrollEnabled, scrollToBottom, enableAutoScroll } = useAutoScroll(viewportRef, { + observeSubTree: true, + enabled: false + }); + + useEffect(() => { + if (hasChat && reasoningMessageIds) { + enableAutoScroll(); + } + }, [hasChat, isEmpty(reasoningMessageIds)]); if (!hasChat || !reasoningMessageIds) return ; return ( - -
- {reasoningMessageIds?.map((reasoningMessageId) => ( - - ))} + <> + +
+ {reasoningMessageIds?.map((reasoningMessageId) => ( + + ))} - -
-
+ +
+
+ + + ); }; diff --git a/web/src/controllers/ReasoningController/ReasoningScrollToBottom.tsx b/web/src/controllers/ReasoningController/ReasoningScrollToBottom.tsx new file mode 100644 index 000000000..bc7063be3 --- /dev/null +++ b/web/src/controllers/ReasoningController/ReasoningScrollToBottom.tsx @@ -0,0 +1,31 @@ +import { ChevronDown } from '@/components/ui/icons'; +import { AppTooltip } from '@/components/ui/tooltip'; +import { cn } from '@/lib/classMerge'; +import React from 'react'; + +export const ReasoningScrollToBottom: React.FC<{ + isAutoScrollEnabled: boolean; + scrollToBottom: () => void; +}> = React.memo(({ isAutoScrollEnabled, scrollToBottom }) => { + return ( +
+ + + +
+ ); +}); + +ReasoningScrollToBottom.displayName = 'ReasoningScrollToBottom'; diff --git a/web/src/hooks/useAutoScroll.ts b/web/src/hooks/useAutoScroll.ts index a1b78b08a..4fa76fff0 100644 --- a/web/src/hooks/useAutoScroll.ts +++ b/web/src/hooks/useAutoScroll.ts @@ -221,7 +221,6 @@ export const useAutoScroll = ( // Only disable auto–scroll if we're not near the bottom. if (!isAtBottom(container, bottomThreshold)) { setIsAutoScrollEnabled(false); - console.log('disableAutoScrollHandler', isAutoScrollEnabled, enabled); // Stop any ongoing animations if (rAFIdRef.current) { diff --git a/web/src/layouts/ChatLayout/ChatContainer/ChatContent/ChatScrollToBottom.tsx b/web/src/layouts/ChatLayout/ChatContainer/ChatContent/ChatScrollToBottom.tsx index e9521649c..312c12f1e 100644 --- a/web/src/layouts/ChatLayout/ChatContainer/ChatContent/ChatScrollToBottom.tsx +++ b/web/src/layouts/ChatLayout/ChatContainer/ChatContent/ChatScrollToBottom.tsx @@ -11,7 +11,7 @@ export const ChatScrollToBottom: React.FC<{ return (