mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 08:50:15 +01:00
refactor: Consolidate Message Scrolling & other Logic to Custom Hooks 🔄 (#1257)
* refactor: remove unnecessary drilling/invoking of ScrollToBottom - feat: useMessageScrolling: consolidates all scrolling logic to hook - feat: useMessageHelpers: creates message utilities and consolidates logic from UI component * fix: ensure automatic scrolling is triggered by messagesTree re-render and is throttled
This commit is contained in:
parent
ebd23f7295
commit
4674a54c70
12 changed files with 208 additions and 169 deletions
|
|
@ -112,7 +112,6 @@ export type TMessageProps = {
|
||||||
isSearchView?: boolean;
|
isSearchView?: boolean;
|
||||||
siblingIdx?: number;
|
siblingIdx?: number;
|
||||||
siblingCount?: number;
|
siblingCount?: number;
|
||||||
scrollToBottom?: () => void;
|
|
||||||
setCurrentEditId?: React.Dispatch<React.SetStateAction<string | number | null>> | null;
|
setCurrentEditId?: React.Dispatch<React.SetStateAction<string | number | null>> | null;
|
||||||
setSiblingIdx?: ((value: number) => void | React.Dispatch<React.SetStateAction<number>>) | null;
|
setSiblingIdx?: ((value: number) => void | React.Dispatch<React.SetStateAction<number>>) | null;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,6 @@ const Image = ({
|
||||||
cx="60"
|
cx="60"
|
||||||
cy="60"
|
cy="60"
|
||||||
/>
|
/>
|
||||||
{/* <circle className="origin-[50%_50%] -rotate-90 transition-[stroke-dashoffset]" stroke="currentColor" strokeWidth="10" strokeDashoffset="311.01767270538954" strokeDasharray="345.57519189487726 345.57519189487726" fill="transparent" r="55" cx="60" cy="60"/>*/}
|
|
||||||
<circle
|
<circle
|
||||||
className="origin-[50%_50%] -rotate-90 transition-[stroke-dashoffset]"
|
className="origin-[50%_50%] -rotate-90 transition-[stroke-dashoffset]"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { EModelEndpoint } from 'librechat-data-provider';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import { MessagesSquared, GPTIcon } from '~/components/svg';
|
import { MessagesSquared, GPTIcon } from '~/components/svg';
|
||||||
import { useChatContext } from '~/Providers';
|
import { useChatContext } from '~/Providers';
|
||||||
import { Button } from '~/components';
|
import { Button } from '~/components/ui';
|
||||||
import { cn } from '~/utils/';
|
import { cn } from '~/utils/';
|
||||||
|
|
||||||
type TPopoverButton = {
|
type TPopoverButton = {
|
||||||
|
|
|
||||||
|
|
@ -1,121 +1,39 @@
|
||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
import copy from 'copy-to-clipboard';
|
|
||||||
import { useRecoilValue } from 'recoil';
|
|
||||||
import { Plugin } from '~/components/Messages/Content';
|
import { Plugin } from '~/components/Messages/Content';
|
||||||
import MessageContent from './Content/MessageContent';
|
import MessageContent from './Content/MessageContent';
|
||||||
import { Icon } from '~/components/Endpoints';
|
|
||||||
import SiblingSwitch from './SiblingSwitch';
|
|
||||||
import type { TMessageProps } from '~/common';
|
import type { TMessageProps } from '~/common';
|
||||||
import { useChatContext } from '~/Providers';
|
import SiblingSwitch from './SiblingSwitch';
|
||||||
|
import { useMessageHelpers } from '~/hooks';
|
||||||
// eslint-disable-next-line import/no-cycle
|
// eslint-disable-next-line import/no-cycle
|
||||||
import MultiMessage from './MultiMessage';
|
import MultiMessage from './MultiMessage';
|
||||||
import HoverButtons from './HoverButtons';
|
import HoverButtons from './HoverButtons';
|
||||||
import SubRow from './SubRow';
|
import SubRow from './SubRow';
|
||||||
import { cn } from '~/utils';
|
import { cn } from '~/utils';
|
||||||
import store from '~/store';
|
|
||||||
|
|
||||||
export default function Message(props: TMessageProps) {
|
export default function Message(props: TMessageProps) {
|
||||||
const autoScroll = useRecoilValue(store.autoScroll);
|
const { message, siblingIdx, siblingCount, setSiblingIdx, currentEditId, setCurrentEditId } =
|
||||||
const {
|
props;
|
||||||
message,
|
|
||||||
scrollToBottom,
|
|
||||||
currentEditId,
|
|
||||||
setCurrentEditId,
|
|
||||||
siblingIdx,
|
|
||||||
siblingCount,
|
|
||||||
setSiblingIdx,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
ask,
|
ask,
|
||||||
regenerate,
|
icon,
|
||||||
abortScroll,
|
edit,
|
||||||
isSubmitting,
|
isLast,
|
||||||
|
enterEdit,
|
||||||
|
handleScroll,
|
||||||
conversation,
|
conversation,
|
||||||
setAbortScroll,
|
isSubmitting,
|
||||||
handleContinue,
|
|
||||||
latestMessage,
|
latestMessage,
|
||||||
setLatestMessage,
|
handleContinue,
|
||||||
} = useChatContext();
|
copyToClipboard,
|
||||||
|
regenerateMessage,
|
||||||
const { conversationId } = conversation ?? {};
|
} = useMessageHelpers(props);
|
||||||
|
|
||||||
const { text, children, messageId = null, isCreatedByUser, error, unfinished } = message ?? {};
|
const { text, children, messageId = null, isCreatedByUser, error, unfinished } = message ?? {};
|
||||||
|
|
||||||
const isLast = !children?.length;
|
|
||||||
const edit = messageId === currentEditId;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isSubmitting && scrollToBottom && !abortScroll) {
|
|
||||||
scrollToBottom();
|
|
||||||
}
|
|
||||||
}, [isSubmitting, text, scrollToBottom, abortScroll]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (scrollToBottom && autoScroll && conversationId !== 'new') {
|
|
||||||
scrollToBottom();
|
|
||||||
}
|
|
||||||
}, [autoScroll, conversationId, scrollToBottom]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!message) {
|
|
||||||
return;
|
|
||||||
} else if (isLast) {
|
|
||||||
setLatestMessage({ ...message });
|
|
||||||
}
|
|
||||||
}, [isLast, message, setLatestMessage]);
|
|
||||||
|
|
||||||
if (!message) {
|
if (!message) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const enterEdit = (cancel?: boolean) =>
|
|
||||||
setCurrentEditId && setCurrentEditId(cancel ? -1 : messageId);
|
|
||||||
|
|
||||||
const handleScroll = () => {
|
|
||||||
if (isSubmitting) {
|
|
||||||
setAbortScroll(true);
|
|
||||||
} else {
|
|
||||||
setAbortScroll(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// const commonClasses =
|
|
||||||
// 'w-full border-b text-gray-800 group border-black/10 dark:border-gray-900/50 dark:text-gray-100 dark:border-none';
|
|
||||||
// const uniqueClasses = isCreatedByUser
|
|
||||||
// ? 'bg-white dark:bg-gray-800 dark:text-gray-20'
|
|
||||||
// : 'bg-white dark:bg-gray-800 dark:text-gray-70';
|
|
||||||
|
|
||||||
// const messageProps = {
|
|
||||||
// className: cn(commonClasses, uniqueClasses),
|
|
||||||
// titleclass: '',
|
|
||||||
// };
|
|
||||||
|
|
||||||
const icon = Icon({
|
|
||||||
...conversation,
|
|
||||||
...message,
|
|
||||||
model: message?.model ?? conversation?.model,
|
|
||||||
size: 28.8,
|
|
||||||
});
|
|
||||||
|
|
||||||
const regenerateMessage = () => {
|
|
||||||
if (isSubmitting && isCreatedByUser) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
regenerate(message);
|
|
||||||
};
|
|
||||||
|
|
||||||
const copyToClipboard = (setIsCopied: React.Dispatch<React.SetStateAction<boolean>>) => {
|
|
||||||
setIsCopied(true);
|
|
||||||
copy(text ?? '');
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
setIsCopied(false);
|
|
||||||
}, 3000);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
|
|
@ -198,7 +116,6 @@ export default function Message(props: TMessageProps) {
|
||||||
messageId={messageId}
|
messageId={messageId}
|
||||||
conversation={conversation}
|
conversation={conversation}
|
||||||
messagesTree={children ?? []}
|
messagesTree={children ?? []}
|
||||||
scrollToBottom={scrollToBottom}
|
|
||||||
currentEditId={currentEditId}
|
currentEditId={currentEditId}
|
||||||
setCurrentEditId={setCurrentEditId}
|
setCurrentEditId={setCurrentEditId}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
import { useLayoutEffect, useState, useRef, useCallback } from 'react';
|
import { useState } from 'react';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import type { TMessage } from 'librechat-data-provider';
|
import type { TMessage } from 'librechat-data-provider';
|
||||||
import ScrollToBottom from '~/components/Messages/ScrollToBottom';
|
import ScrollToBottom from '~/components/Messages/ScrollToBottom';
|
||||||
import { useScreenshot, useScrollToRef } from '~/hooks';
|
import { useScreenshot, useMessageScrolling } from '~/hooks';
|
||||||
import { CSSTransition } from 'react-transition-group';
|
import { CSSTransition } from 'react-transition-group';
|
||||||
import { useChatContext } from '~/Providers';
|
|
||||||
import MultiMessage from './MultiMessage';
|
import MultiMessage from './MultiMessage';
|
||||||
|
|
||||||
export default function MessagesView({
|
export default function MessagesView({
|
||||||
|
|
@ -14,54 +13,19 @@ export default function MessagesView({
|
||||||
messagesTree?: TMessage[] | null;
|
messagesTree?: TMessage[] | null;
|
||||||
Header?: ReactNode;
|
Header?: ReactNode;
|
||||||
}) {
|
}) {
|
||||||
const timeoutIdRef = useRef<NodeJS.Timeout>();
|
|
||||||
const scrollableRef = useRef<HTMLDivElement | null>(null);
|
|
||||||
const messagesEndRef = useRef<HTMLDivElement | null>(null);
|
|
||||||
const [showScrollButton, setShowScrollButton] = useState(false);
|
|
||||||
const [currentEditId, setCurrentEditId] = useState<number | string | null>(-1);
|
|
||||||
const { conversation, setAbortScroll } = useChatContext();
|
|
||||||
const { conversationId } = conversation ?? {};
|
|
||||||
|
|
||||||
const { screenshotTargetRef } = useScreenshot();
|
const { screenshotTargetRef } = useScreenshot();
|
||||||
|
const [currentEditId, setCurrentEditId] = useState<number | string | null>(-1);
|
||||||
|
|
||||||
const checkIfAtBottom = useCallback(() => {
|
const {
|
||||||
if (!scrollableRef.current) {
|
conversation,
|
||||||
return;
|
scrollableRef,
|
||||||
}
|
messagesEndRef,
|
||||||
|
showScrollButton,
|
||||||
|
handleSmoothToRef,
|
||||||
|
debouncedHandleScroll,
|
||||||
|
} = useMessageScrolling(_messagesTree);
|
||||||
|
|
||||||
const { scrollTop, scrollHeight, clientHeight } = scrollableRef.current;
|
const { conversationId } = conversation ?? {};
|
||||||
const diff = Math.abs(scrollHeight - scrollTop);
|
|
||||||
const percent = Math.abs(clientHeight - diff) / clientHeight;
|
|
||||||
const hasScrollbar = scrollHeight > clientHeight && percent >= 0.15;
|
|
||||||
setShowScrollButton(hasScrollbar);
|
|
||||||
}, [scrollableRef]);
|
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
|
||||||
const scrollableElement = scrollableRef.current;
|
|
||||||
if (!scrollableElement) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const timeoutId = setTimeout(checkIfAtBottom, 650);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
};
|
|
||||||
}, [checkIfAtBottom]);
|
|
||||||
|
|
||||||
const debouncedHandleScroll = useCallback(() => {
|
|
||||||
clearTimeout(timeoutIdRef.current);
|
|
||||||
timeoutIdRef.current = setTimeout(checkIfAtBottom, 100);
|
|
||||||
}, [checkIfAtBottom]);
|
|
||||||
|
|
||||||
const scrollCallback = () => setShowScrollButton(false);
|
|
||||||
const { scrollToRef: scrollToBottom, handleSmoothToRef } = useScrollToRef({
|
|
||||||
targetRef: messagesEndRef,
|
|
||||||
callback: scrollCallback,
|
|
||||||
smoothCallback: () => {
|
|
||||||
scrollCallback();
|
|
||||||
setAbortScroll(false);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex-1 overflow-hidden overflow-y-auto">
|
<div className="flex-1 overflow-hidden overflow-y-auto">
|
||||||
|
|
@ -86,9 +50,8 @@ export default function MessagesView({
|
||||||
<div ref={screenshotTargetRef}>
|
<div ref={screenshotTargetRef}>
|
||||||
<MultiMessage
|
<MultiMessage
|
||||||
key={conversationId} // avoid internal state mixture
|
key={conversationId} // avoid internal state mixture
|
||||||
messageId={conversationId ?? null}
|
|
||||||
messagesTree={_messagesTree}
|
messagesTree={_messagesTree}
|
||||||
scrollToBottom={scrollToBottom}
|
messageId={conversationId ?? null}
|
||||||
setCurrentEditId={setCurrentEditId}
|
setCurrentEditId={setCurrentEditId}
|
||||||
currentEditId={currentEditId ?? null}
|
currentEditId={currentEditId ?? null}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ export default function MultiMessage({
|
||||||
// messageId is used recursively here
|
// messageId is used recursively here
|
||||||
messageId,
|
messageId,
|
||||||
messagesTree,
|
messagesTree,
|
||||||
scrollToBottom,
|
|
||||||
currentEditId,
|
currentEditId,
|
||||||
setCurrentEditId,
|
setCurrentEditId,
|
||||||
}: TMessageProps) {
|
}: TMessageProps) {
|
||||||
|
|
@ -45,7 +44,6 @@ export default function MultiMessage({
|
||||||
<Message
|
<Message
|
||||||
key={message.messageId}
|
key={message.messageId}
|
||||||
message={message}
|
message={message}
|
||||||
scrollToBottom={scrollToBottom}
|
|
||||||
currentEditId={currentEditId}
|
currentEditId={currentEditId}
|
||||||
setCurrentEditId={setCurrentEditId}
|
setCurrentEditId={setCurrentEditId}
|
||||||
siblingIdx={messagesTree.length - siblingIdx - 1}
|
siblingIdx={messagesTree.length - siblingIdx - 1}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { EModelEndpoint } from 'librechat-data-provider';
|
import { EModelEndpoint } from 'librechat-data-provider';
|
||||||
import { Plugin, GPTIcon, AnthropicIcon, AzureMinimalIcon } from '~/components/svg';
|
import { Plugin, GPTIcon, AnthropicIcon, AzureMinimalIcon } from '~/components/svg';
|
||||||
import { useAuthContext } from '~/hooks';
|
import { useAuthContext } from '~/hooks/AuthContext';
|
||||||
import { IconProps } from '~/common';
|
import { IconProps } from '~/common';
|
||||||
import { cn } from '~/utils';
|
import { cn } from '~/utils';
|
||||||
|
|
||||||
|
|
|
||||||
2
client/src/hooks/Messages/index.ts
Normal file
2
client/src/hooks/Messages/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
export { default as useMessageHelpers } from './useMessageHelpers';
|
||||||
|
export { default as useMessageScrolling } from './useMessageScrolling';
|
||||||
83
client/src/hooks/Messages/useMessageHelpers.ts
Normal file
83
client/src/hooks/Messages/useMessageHelpers.ts
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import copy from 'copy-to-clipboard';
|
||||||
|
import type { TMessage } from 'librechat-data-provider';
|
||||||
|
import type { TMessageProps } from '~/common';
|
||||||
|
import Icon from '~/components/Endpoints/Icon';
|
||||||
|
import { useChatContext } from '~/Providers';
|
||||||
|
|
||||||
|
export default function useMessageHelpers(props: TMessageProps) {
|
||||||
|
const { message, currentEditId, setCurrentEditId } = props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
ask,
|
||||||
|
regenerate,
|
||||||
|
isSubmitting,
|
||||||
|
conversation,
|
||||||
|
latestMessage,
|
||||||
|
setAbortScroll,
|
||||||
|
handleContinue,
|
||||||
|
setLatestMessage,
|
||||||
|
} = useChatContext();
|
||||||
|
|
||||||
|
const { text, children, messageId = null, isCreatedByUser } = message ?? {};
|
||||||
|
const edit = messageId === currentEditId;
|
||||||
|
const isLast = !children?.length;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!message) {
|
||||||
|
return;
|
||||||
|
} else if (isLast) {
|
||||||
|
setLatestMessage({ ...message });
|
||||||
|
}
|
||||||
|
}, [isLast, message, setLatestMessage]);
|
||||||
|
|
||||||
|
const enterEdit = (cancel?: boolean) =>
|
||||||
|
setCurrentEditId && setCurrentEditId(cancel ? -1 : messageId);
|
||||||
|
|
||||||
|
const handleScroll = () => {
|
||||||
|
if (isSubmitting) {
|
||||||
|
setAbortScroll(true);
|
||||||
|
} else {
|
||||||
|
setAbortScroll(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const icon = Icon({
|
||||||
|
...conversation,
|
||||||
|
...(message as TMessage),
|
||||||
|
model: message?.model ?? conversation?.model,
|
||||||
|
size: 28.8,
|
||||||
|
});
|
||||||
|
|
||||||
|
const regenerateMessage = () => {
|
||||||
|
if ((isSubmitting && isCreatedByUser) || !message) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
regenerate(message);
|
||||||
|
};
|
||||||
|
|
||||||
|
const copyToClipboard = (setIsCopied: React.Dispatch<React.SetStateAction<boolean>>) => {
|
||||||
|
setIsCopied(true);
|
||||||
|
copy(text ?? '');
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsCopied(false);
|
||||||
|
}, 3000);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
ask,
|
||||||
|
icon,
|
||||||
|
edit,
|
||||||
|
isLast,
|
||||||
|
enterEdit,
|
||||||
|
conversation,
|
||||||
|
isSubmitting,
|
||||||
|
handleScroll,
|
||||||
|
latestMessage,
|
||||||
|
handleContinue,
|
||||||
|
copyToClipboard,
|
||||||
|
regenerateMessage,
|
||||||
|
};
|
||||||
|
}
|
||||||
83
client/src/hooks/Messages/useMessageScrolling.ts
Normal file
83
client/src/hooks/Messages/useMessageScrolling.ts
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
import { useLayoutEffect, useState, useRef, useCallback, useEffect } from 'react';
|
||||||
|
import type { TMessage } from 'librechat-data-provider';
|
||||||
|
import useScrollToRef from '../useScrollToRef';
|
||||||
|
import { useChatContext } from '~/Providers';
|
||||||
|
import store from '~/store';
|
||||||
|
|
||||||
|
export default function useMessageScrolling(messagesTree?: TMessage[] | null) {
|
||||||
|
const autoScroll = useRecoilValue(store.autoScroll);
|
||||||
|
|
||||||
|
const timeoutIdRef = useRef<NodeJS.Timeout>();
|
||||||
|
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 ?? {};
|
||||||
|
|
||||||
|
const checkIfAtBottom = useCallback(() => {
|
||||||
|
if (!scrollableRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { scrollTop, scrollHeight, clientHeight } = scrollableRef.current;
|
||||||
|
const diff = Math.abs(scrollHeight - scrollTop);
|
||||||
|
const percent = Math.abs(clientHeight - diff) / clientHeight;
|
||||||
|
const hasScrollbar = scrollHeight > clientHeight && percent >= 0.15;
|
||||||
|
setShowScrollButton(hasScrollbar);
|
||||||
|
}, [scrollableRef]);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
const scrollableElement = scrollableRef.current;
|
||||||
|
if (!scrollableElement) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const timeoutId = setTimeout(checkIfAtBottom, 650);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
};
|
||||||
|
}, [checkIfAtBottom]);
|
||||||
|
|
||||||
|
const debouncedHandleScroll = useCallback(() => {
|
||||||
|
clearTimeout(timeoutIdRef.current);
|
||||||
|
timeoutIdRef.current = setTimeout(checkIfAtBottom, 100);
|
||||||
|
}, [checkIfAtBottom]);
|
||||||
|
|
||||||
|
const scrollCallback = () => setShowScrollButton(false);
|
||||||
|
|
||||||
|
const { scrollToRef: scrollToBottom, handleSmoothToRef } = useScrollToRef({
|
||||||
|
targetRef: messagesEndRef,
|
||||||
|
callback: scrollCallback,
|
||||||
|
smoothCallback: () => {
|
||||||
|
scrollCallback();
|
||||||
|
setAbortScroll(false);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!messagesTree) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSubmitting && scrollToBottom && !abortScroll) {
|
||||||
|
scrollToBottom();
|
||||||
|
}
|
||||||
|
}, [isSubmitting, messagesTree, scrollToBottom, abortScroll]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (scrollToBottom && autoScroll && conversationId !== 'new') {
|
||||||
|
scrollToBottom();
|
||||||
|
}
|
||||||
|
}, [autoScroll, conversationId, scrollToBottom]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
conversation,
|
||||||
|
scrollableRef,
|
||||||
|
messagesEndRef,
|
||||||
|
scrollToBottom,
|
||||||
|
showScrollButton,
|
||||||
|
handleSmoothToRef,
|
||||||
|
debouncedHandleScroll,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
export * from './Messages';
|
||||||
|
|
||||||
export * from './AuthContext';
|
export * from './AuthContext';
|
||||||
export * from './ThemeContext';
|
export * from './ThemeContext';
|
||||||
export * from './ScreenshotContext';
|
export * from './ScreenshotContext';
|
||||||
|
|
|
||||||
|
|
@ -8,29 +8,22 @@ type TUseScrollToRef = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function useScrollToRef({ targetRef, callback, smoothCallback }: TUseScrollToRef) {
|
export default function useScrollToRef({ targetRef, callback, smoothCallback }: TUseScrollToRef) {
|
||||||
|
const logAndScroll = (behavior: 'instant' | 'smooth', callbackFn: () => void) => {
|
||||||
|
// Debugging:
|
||||||
|
// console.log(`Scrolling with behavior: ${behavior}, Time: ${new Date().toISOString()}`);
|
||||||
|
targetRef.current?.scrollIntoView({ behavior });
|
||||||
|
callbackFn();
|
||||||
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
const scrollToRef = useCallback(
|
const scrollToRef = useCallback(
|
||||||
throttle(
|
throttle(() => logAndScroll('instant', callback), 450, { leading: true }),
|
||||||
() => {
|
|
||||||
targetRef.current?.scrollIntoView({ behavior: 'instant' });
|
|
||||||
callback();
|
|
||||||
},
|
|
||||||
450,
|
|
||||||
{ leading: true },
|
|
||||||
),
|
|
||||||
[targetRef],
|
[targetRef],
|
||||||
);
|
);
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
const scrollToRefSmooth = useCallback(
|
const scrollToRefSmooth = useCallback(
|
||||||
throttle(
|
throttle(() => logAndScroll('smooth', smoothCallback), 750, { leading: true }),
|
||||||
() => {
|
|
||||||
targetRef.current?.scrollIntoView({ behavior: 'smooth' });
|
|
||||||
smoothCallback();
|
|
||||||
},
|
|
||||||
750,
|
|
||||||
{ leading: true },
|
|
||||||
),
|
|
||||||
[targetRef],
|
[targetRef],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue