2023-12-01 19:54:09 -05:00
|
|
|
import { useRecoilValue } from 'recoil';
|
2024-08-06 16:18:15 -04:00
|
|
|
import { Constants } from 'librechat-data-provider';
|
|
|
|
|
import { useState, useRef, useCallback, useEffect } from 'react';
|
2023-12-01 19:54:09 -05:00
|
|
|
import type { TMessage } from 'librechat-data-provider';
|
2024-08-06 16:18:15 -04:00
|
|
|
import useScrollToRef from '~/hooks/useScrollToRef';
|
2023-12-01 19:54:09 -05:00
|
|
|
import { useChatContext } from '~/Providers';
|
|
|
|
|
import store from '~/store';
|
|
|
|
|
|
2024-08-08 10:02:30 -04:00
|
|
|
const threshold = 0.85;
|
|
|
|
|
const debounceRate = 150;
|
2024-08-07 14:23:33 -04:00
|
|
|
|
2023-12-01 19:54:09 -05:00
|
|
|
export default function useMessageScrolling(messagesTree?: TMessage[] | null) {
|
|
|
|
|
const autoScroll = useRecoilValue(store.autoScroll);
|
|
|
|
|
|
|
|
|
|
const scrollableRef = useRef<HTMLDivElement | null>(null);
|
|
|
|
|
const messagesEndRef = useRef<HTMLDivElement | null>(null);
|
|
|
|
|
const [showScrollButton, setShowScrollButton] = useState(false);
|
|
|
|
|
const { conversation, setAbortScroll, isSubmitting, abortScroll } = useChatContext();
|
|
|
|
|
const { conversationId } = conversation ?? {};
|
|
|
|
|
|
2024-08-06 16:18:15 -04:00
|
|
|
const timeoutIdRef = useRef<NodeJS.Timeout>();
|
2023-12-01 19:54:09 -05:00
|
|
|
|
2024-08-06 16:18:15 -04:00
|
|
|
const debouncedSetShowScrollButton = useCallback((value: boolean) => {
|
|
|
|
|
clearTimeout(timeoutIdRef.current);
|
|
|
|
|
timeoutIdRef.current = setTimeout(() => {
|
|
|
|
|
setShowScrollButton(value);
|
2024-08-07 14:23:33 -04:00
|
|
|
}, debounceRate);
|
2024-08-06 16:18:15 -04:00
|
|
|
}, []);
|
2023-12-01 19:54:09 -05:00
|
|
|
|
2024-08-06 16:18:15 -04:00
|
|
|
useEffect(() => {
|
|
|
|
|
if (!messagesEndRef.current || !scrollableRef.current) {
|
2023-12-01 19:54:09 -05:00
|
|
|
return;
|
|
|
|
|
}
|
2024-08-06 16:18:15 -04:00
|
|
|
|
|
|
|
|
const observer = new IntersectionObserver(
|
|
|
|
|
([entry]) => {
|
|
|
|
|
debouncedSetShowScrollButton(!entry.isIntersecting);
|
|
|
|
|
},
|
2024-08-07 14:23:33 -04:00
|
|
|
{ root: scrollableRef.current, threshold },
|
2024-08-06 16:18:15 -04:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
observer.observe(messagesEndRef.current);
|
2023-12-01 19:54:09 -05:00
|
|
|
|
|
|
|
|
return () => {
|
2024-08-06 16:18:15 -04:00
|
|
|
observer.disconnect();
|
|
|
|
|
clearTimeout(timeoutIdRef.current);
|
2023-12-01 19:54:09 -05:00
|
|
|
};
|
2024-08-06 16:18:15 -04:00
|
|
|
}, [messagesEndRef, scrollableRef, debouncedSetShowScrollButton]);
|
2023-12-01 19:54:09 -05:00
|
|
|
|
|
|
|
|
const debouncedHandleScroll = useCallback(() => {
|
2024-08-06 16:18:15 -04:00
|
|
|
if (messagesEndRef.current && scrollableRef.current) {
|
|
|
|
|
const observer = new IntersectionObserver(
|
|
|
|
|
([entry]) => {
|
|
|
|
|
debouncedSetShowScrollButton(!entry.isIntersecting);
|
|
|
|
|
},
|
2024-08-07 14:23:33 -04:00
|
|
|
{ root: scrollableRef.current, threshold },
|
2024-08-06 16:18:15 -04:00
|
|
|
);
|
|
|
|
|
observer.observe(messagesEndRef.current);
|
|
|
|
|
return () => observer.disconnect();
|
|
|
|
|
}
|
|
|
|
|
}, [debouncedSetShowScrollButton]);
|
2023-12-01 19:54:09 -05:00
|
|
|
|
2024-08-06 16:18:15 -04:00
|
|
|
const scrollCallback = () => debouncedSetShowScrollButton(false);
|
2023-12-01 19:54:09 -05:00
|
|
|
|
|
|
|
|
const { scrollToRef: scrollToBottom, handleSmoothToRef } = useScrollToRef({
|
|
|
|
|
targetRef: messagesEndRef,
|
|
|
|
|
callback: scrollCallback,
|
|
|
|
|
smoothCallback: () => {
|
|
|
|
|
scrollCallback();
|
|
|
|
|
setAbortScroll(false);
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
2025-01-06 10:32:44 -05:00
|
|
|
if (!messagesTree || messagesTree.length === 0) {
|
2023-12-01 19:54:09 -05:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-06 10:32:44 -05:00
|
|
|
if (!messagesEndRef.current || !scrollableRef.current) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isSubmitting && scrollToBottom && abortScroll !== true) {
|
2023-12-01 19:54:09 -05:00
|
|
|
scrollToBottom();
|
|
|
|
|
}
|
2024-01-18 21:25:57 -05:00
|
|
|
|
|
|
|
|
return () => {
|
2025-01-06 10:32:44 -05:00
|
|
|
if (abortScroll === true) {
|
2024-08-06 16:18:15 -04:00
|
|
|
scrollToBottom && scrollToBottom.cancel();
|
2024-01-18 21:25:57 -05:00
|
|
|
}
|
|
|
|
|
};
|
2023-12-01 19:54:09 -05:00
|
|
|
}, [isSubmitting, messagesTree, scrollToBottom, abortScroll]);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
2025-01-06 10:32:44 -05:00
|
|
|
if (!messagesEndRef.current || !scrollableRef.current) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-06 16:18:15 -04:00
|
|
|
if (scrollToBottom && autoScroll && conversationId !== Constants.NEW_CONVO) {
|
2023-12-01 19:54:09 -05:00
|
|
|
scrollToBottom();
|
|
|
|
|
}
|
|
|
|
|
}, [autoScroll, conversationId, scrollToBottom]);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
conversation,
|
|
|
|
|
scrollableRef,
|
|
|
|
|
messagesEndRef,
|
|
|
|
|
scrollToBottom,
|
|
|
|
|
showScrollButton,
|
|
|
|
|
handleSmoothToRef,
|
|
|
|
|
debouncedHandleScroll,
|
|
|
|
|
};
|
|
|
|
|
}
|