File size: 3,344 Bytes
f0743f4 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | import { useRecoilValue } from 'recoil';
import { Constants } from 'librechat-data-provider';
import { useState, useRef, useCallback, useEffect } from 'react';
import type { TMessage } from 'librechat-data-provider';
import { useMessagesConversation, useMessagesSubmission } from '~/Providers';
import useScrollToRef from '~/hooks/useScrollToRef';
import store from '~/store';
const threshold = 0.85;
const debounceRate = 150;
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, conversationId } = useMessagesConversation();
const { setAbortScroll, isSubmitting, abortScroll } = useMessagesSubmission();
const timeoutIdRef = useRef<NodeJS.Timeout>();
const debouncedSetShowScrollButton = useCallback((value: boolean) => {
clearTimeout(timeoutIdRef.current);
timeoutIdRef.current = setTimeout(() => {
setShowScrollButton(value);
}, debounceRate);
}, []);
useEffect(() => {
if (!messagesEndRef.current || !scrollableRef.current) {
return;
}
const observer = new IntersectionObserver(
([entry]) => {
debouncedSetShowScrollButton(!entry.isIntersecting);
},
{ root: scrollableRef.current, threshold },
);
observer.observe(messagesEndRef.current);
return () => {
observer.disconnect();
clearTimeout(timeoutIdRef.current);
};
}, [messagesEndRef, scrollableRef, debouncedSetShowScrollButton]);
const debouncedHandleScroll = useCallback(() => {
if (messagesEndRef.current && scrollableRef.current) {
const observer = new IntersectionObserver(
([entry]) => {
debouncedSetShowScrollButton(!entry.isIntersecting);
},
{ root: scrollableRef.current, threshold },
);
observer.observe(messagesEndRef.current);
return () => observer.disconnect();
}
}, [debouncedSetShowScrollButton]);
const scrollCallback = () => debouncedSetShowScrollButton(false);
const { scrollToRef: scrollToBottom, handleSmoothToRef } = useScrollToRef({
targetRef: messagesEndRef,
callback: scrollCallback,
smoothCallback: () => {
scrollCallback();
setAbortScroll(false);
},
});
useEffect(() => {
if (!messagesTree || messagesTree.length === 0) {
return;
}
if (!messagesEndRef.current || !scrollableRef.current) {
return;
}
if (isSubmitting && scrollToBottom && abortScroll !== true) {
scrollToBottom();
}
return () => {
if (abortScroll === true) {
scrollToBottom && scrollToBottom.cancel();
}
};
}, [isSubmitting, messagesTree, scrollToBottom, abortScroll]);
useEffect(() => {
if (!messagesEndRef.current || !scrollableRef.current) {
return;
}
if (scrollToBottom && autoScroll && conversationId !== Constants.NEW_CONVO) {
scrollToBottom();
}
}, [autoScroll, conversationId, scrollToBottom]);
return {
conversation,
scrollableRef,
messagesEndRef,
scrollToBottom,
showScrollButton,
handleSmoothToRef,
debouncedHandleScroll,
};
}
|