diff --git a/client/src/components/Chat/ChatView.tsx b/client/src/components/Chat/ChatView.tsx index 4e9950b676..ae8d35231b 100644 --- a/client/src/components/Chat/ChatView.tsx +++ b/client/src/components/Chat/ChatView.tsx @@ -38,7 +38,7 @@ function ChatView({ index = 0 }: { index?: number }) { const fileMap = useFileMapContext(); - const [showCostBar, setShowCostBar] = useState(true); + const [showCostBar, setShowCostBar] = useState(false); const lastScrollY = useRef(0); const { data: messagesTree = null, isLoading } = useGetMessagesByConvoId(conversationId ?? '', { @@ -65,23 +65,33 @@ function ChatView({ index = 0 }: { index?: number }) { useSSE(rootSubmission, chatHelpers, false); useSSE(addedSubmission, addedChatHelpers, true); - useEffect(() => { - const handleScroll = (event: Event) => { - const target = event.target as HTMLElement; - const currentScrollY = target.scrollTop; - const scrollHeight = target.scrollHeight; - const clientHeight = target.clientHeight; + const checkIfAtBottom = useCallback( + (container: HTMLElement) => { + const currentScrollY = container.scrollTop; + const scrollHeight = container.scrollHeight; + const clientHeight = container.clientHeight; const distanceFromBottom = scrollHeight - currentScrollY - clientHeight; const isAtBottom = distanceFromBottom < 10; - setShowCostBar(isAtBottom); + const isStreaming = chatHelpers.isSubmitting || addedChatHelpers.isSubmitting; + setShowCostBar(isAtBottom && !isStreaming); lastScrollY.current = currentScrollY; + }, + [chatHelpers.isSubmitting, addedChatHelpers.isSubmitting], + ); + + useEffect(() => { + const handleScroll = (event: Event) => { + const target = event.target as HTMLElement; + checkIfAtBottom(target); }; const findAndAttachScrollListener = () => { const messagesContainer = document.querySelector('[class*="scrollbar-gutter-stable"]'); if (messagesContainer) { + checkIfAtBottom(messagesContainer as HTMLElement); + messagesContainer.addEventListener('scroll', handleScroll, { passive: true }); return () => { messagesContainer.removeEventListener('scroll', handleScroll); @@ -93,7 +103,19 @@ function ChatView({ index = 0 }: { index?: number }) { const cleanup = findAndAttachScrollListener(); return cleanup; - }, [messagesTree]); + }, [messagesTree, checkIfAtBottom]); + + useEffect(() => { + const isStreaming = chatHelpers.isSubmitting || addedChatHelpers.isSubmitting; + if (isStreaming) { + setShowCostBar(false); + } else { + const messagesContainer = document.querySelector('[class*="scrollbar-gutter-stable"]'); + if (messagesContainer) { + checkIfAtBottom(messagesContainer as HTMLElement); + } + } + }, [chatHelpers.isSubmitting, addedChatHelpers.isSubmitting, checkIfAtBottom]); const methods = useForm({ defaultValues: { text: '' }, @@ -110,6 +132,7 @@ function ChatView({ index = 0 }: { index?: number }) { } else if ((isLoading || isNavigating) && !isLandingPage) { content = ; } else if (!isLandingPage) { + const isStreaming = chatHelpers.isSubmitting || addedChatHelpers.isSubmitting; content = ( + ) } costs={conversationCosts} diff --git a/client/src/components/Chat/Messages/MessagesView.tsx b/client/src/components/Chat/Messages/MessagesView.tsx index 6808a74565..272abbf93d 100644 --- a/client/src/components/Chat/Messages/MessagesView.tsx +++ b/client/src/components/Chat/Messages/MessagesView.tsx @@ -48,7 +48,7 @@ export default function MessagesView({ width: '100%', }} > -
+
{(_messagesTree && _messagesTree.length == 0) || _messagesTree === null ? (
diff --git a/client/src/hooks/Messages/useMessageScrolling.ts b/client/src/hooks/Messages/useMessageScrolling.ts index a9c033f238..2ecf01eedf 100644 --- a/client/src/hooks/Messages/useMessageScrolling.ts +++ b/client/src/hooks/Messages/useMessageScrolling.ts @@ -19,6 +19,7 @@ export default function useMessageScrolling(messagesTree?: TMessage[] | null) { const { conversationId } = conversation ?? {}; const timeoutIdRef = useRef(); + const prevIsSubmittingRef = useRef(false); const debouncedSetShowScrollButton = useCallback((value: boolean) => { clearTimeout(timeoutIdRef.current); @@ -60,7 +61,10 @@ export default function useMessageScrolling(messagesTree?: TMessage[] | null) { } }, [debouncedSetShowScrollButton]); - const scrollCallback = () => debouncedSetShowScrollButton(false); + const scrollCallback = useCallback( + () => debouncedSetShowScrollButton(false), + [debouncedSetShowScrollButton], + ); const { scrollToRef: scrollToBottom, handleSmoothToRef } = useScrollToRef({ targetRef: messagesEndRef, @@ -71,6 +75,18 @@ export default function useMessageScrolling(messagesTree?: TMessage[] | null) { }, }); + const smoothScrollToBottom = useCallback(() => { + if (messagesEndRef.current) { + messagesEndRef.current.scrollIntoView({ + behavior: 'smooth', + block: 'end', + inline: 'nearest', + }); + scrollCallback(); + setAbortScroll(false); + } + }, [scrollCallback, setAbortScroll]); + useEffect(() => { if (!messagesTree || messagesTree.length === 0) { return; @@ -91,6 +107,20 @@ export default function useMessageScrolling(messagesTree?: TMessage[] | null) { }; }, [isSubmitting, messagesTree, scrollToBottom, abortScroll]); + useEffect(() => { + if (!messagesEndRef.current || !scrollableRef.current) { + return; + } + + if (prevIsSubmittingRef.current && !isSubmitting && abortScroll !== true) { + setTimeout(() => { + smoothScrollToBottom(); + }, 100); + } + + prevIsSubmittingRef.current = isSubmitting; + }, [isSubmitting, smoothScrollToBottom, abortScroll]); + useEffect(() => { if (!messagesEndRef.current || !scrollableRef.current) { return; diff --git a/client/src/hooks/useScrollToRef.ts b/client/src/hooks/useScrollToRef.ts index 2fed7c41a3..e84cdc0106 100644 --- a/client/src/hooks/useScrollToRef.ts +++ b/client/src/hooks/useScrollToRef.ts @@ -31,7 +31,7 @@ export default function useScrollToRef({ // eslint-disable-next-line react-hooks/exhaustive-deps const scrollToRef = useCallback( - throttle(() => logAndScroll('instant', callback), 145, { leading: true }), + throttle(() => logAndScroll('instant', callback), 100, { leading: true }), [targetRef], );