mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-02-07 02:01:50 +01:00
feat: auto-scroll to the bottom of the conversation (#1049)
* added button for autoscroll
* fix(General) removed bold
* fix(General) typescript error with checked={autoScroll}
* added return condition for new conversations
* refactor(Message) limit nesting
* fix(settings) used effects
* fix(Message) disabled autoscroll when search
* test(AutoScrollSwitch)
* fix(AutoScrollSwitch) test
* fix(ci): attempt to debug workflow
* refactor: move AutoScrollSwitch from General file, don't use cache for npm
* fix(ci): add test config to avoid redirects and silentRefresh
* chore: add back workflow caching
* chore(AutoScrollSwitch): remove comments, fix type issues, clarify switch intent
* refactor(Message): remove unnecessary message prop form scrolling condition
* fix(AutoScrollSwitch.spec): do not get by text
---------
Co-authored-by: Danny Avila <messagedaniel@protonmail.com>
This commit is contained in:
parent
cff45df0ef
commit
b1a96ecedc
12 changed files with 158 additions and 37 deletions
|
|
@ -1,7 +1,7 @@
|
|||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { useGetConversationByIdQuery } from 'librechat-data-provider';
|
||||
import { useEffect } from 'react';
|
||||
import { useSetRecoilState, useRecoilState } from 'recoil';
|
||||
import { useSetRecoilState, useRecoilState, useRecoilValue } from 'recoil';
|
||||
import copy from 'copy-to-clipboard';
|
||||
import { SubRow, Plugin, MessageContent } from './Content';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
|
|
@ -13,21 +13,27 @@ import { useMessageHandler, useConversation } from '~/hooks';
|
|||
import type { TMessageProps } from '~/common';
|
||||
import { cn } from '~/utils';
|
||||
import store from '~/store';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
export default function Message(props: TMessageProps) {
|
||||
const {
|
||||
conversation,
|
||||
message,
|
||||
scrollToBottom,
|
||||
currentEditId,
|
||||
setCurrentEditId,
|
||||
siblingIdx,
|
||||
siblingCount,
|
||||
setSiblingIdx,
|
||||
} = props;
|
||||
|
||||
export default function Message({
|
||||
conversation,
|
||||
message,
|
||||
scrollToBottom,
|
||||
currentEditId,
|
||||
setCurrentEditId,
|
||||
siblingIdx,
|
||||
siblingCount,
|
||||
setSiblingIdx,
|
||||
}: TMessageProps) {
|
||||
const setLatestMessage = useSetRecoilState(store.latestMessage);
|
||||
const [abortScroll, setAbortScroll] = useRecoilState(store.abortScroll);
|
||||
const { isSubmitting, ask, regenerate, handleContinue } = useMessageHandler();
|
||||
const { switchToConversation } = useConversation();
|
||||
const { conversationId } = useParams();
|
||||
const isSearching = useRecoilValue(store.isSearching);
|
||||
|
||||
const {
|
||||
text,
|
||||
children,
|
||||
|
|
@ -37,24 +43,26 @@ export default function Message({
|
|||
error,
|
||||
unfinished,
|
||||
} = message ?? {};
|
||||
|
||||
const isLast = !children?.length;
|
||||
const edit = messageId == currentEditId;
|
||||
const edit = messageId === currentEditId;
|
||||
const getConversationQuery = useGetConversationByIdQuery(message?.conversationId ?? '', {
|
||||
enabled: false,
|
||||
});
|
||||
const blinker = message?.submitting && isSubmitting;
|
||||
|
||||
// debugging
|
||||
// useEffect(() => {
|
||||
// console.log('isSubmitting:', isSubmitting);
|
||||
// console.log('unfinished:', unfinished);
|
||||
// }, [isSubmitting, unfinished]);
|
||||
const autoScroll = useRecoilValue(store.autoScroll);
|
||||
|
||||
useEffect(() => {
|
||||
if (blinker && scrollToBottom && !abortScroll) {
|
||||
if (isSubmitting && scrollToBottom && !abortScroll) {
|
||||
scrollToBottom();
|
||||
}
|
||||
}, [isSubmitting, blinker, text, scrollToBottom]);
|
||||
}, [isSubmitting, text, scrollToBottom, abortScroll]);
|
||||
|
||||
useEffect(() => {
|
||||
if (scrollToBottom && autoScroll && !isSearching && conversationId !== 'new') {
|
||||
scrollToBottom();
|
||||
}
|
||||
}, [autoScroll, conversationId, scrollToBottom, isSearching]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!message) {
|
||||
|
|
@ -62,7 +70,7 @@ export default function Message({
|
|||
} else if (isLast) {
|
||||
setLatestMessage({ ...message });
|
||||
}
|
||||
}, [isLast, message]);
|
||||
}, [isLast, message, setLatestMessage]);
|
||||
|
||||
if (!message) {
|
||||
return null;
|
||||
|
|
@ -72,7 +80,7 @@ export default function Message({
|
|||
setCurrentEditId && setCurrentEditId(cancel ? -1 : messageId);
|
||||
|
||||
const handleScroll = () => {
|
||||
if (blinker) {
|
||||
if (isSubmitting) {
|
||||
setAbortScroll(true);
|
||||
} else {
|
||||
setAbortScroll(false);
|
||||
|
|
@ -85,7 +93,7 @@ export default function Message({
|
|||
? 'bg-white dark:bg-gray-800 dark:text-gray-20'
|
||||
: 'bg-gray-50 dark:bg-gray-1000 dark:text-gray-70';
|
||||
|
||||
const props = {
|
||||
const messageProps = {
|
||||
className: cn(commonClasses, uniqueClasses),
|
||||
titleclass: '',
|
||||
};
|
||||
|
|
@ -98,8 +106,8 @@ export default function Message({
|
|||
});
|
||||
|
||||
if (message?.bg && searchResult) {
|
||||
props.className = message?.bg?.split('hover')[0];
|
||||
props.titleclass = message?.bg?.split(props.className)[1] + ' cursor-pointer';
|
||||
messageProps.className = message?.bg?.split('hover')[0];
|
||||
messageProps.titleclass = message?.bg?.split(messageProps.className)[1] + ' cursor-pointer';
|
||||
}
|
||||
|
||||
const regenerateMessage = () => {
|
||||
|
|
@ -124,17 +132,20 @@ export default function Message({
|
|||
if (!message) {
|
||||
return;
|
||||
}
|
||||
getConversationQuery.refetch({ queryKey: [message?.conversationId] }).then((response) => {
|
||||
console.log('getConversationQuery response.data:', response.data);
|
||||
if (response.data) {
|
||||
switchToConversation(response.data);
|
||||
}
|
||||
const response = await getConversationQuery.refetch({
|
||||
queryKey: [message?.conversationId],
|
||||
});
|
||||
|
||||
console.log('getConversationQuery response.data:', response.data);
|
||||
|
||||
if (response.data) {
|
||||
switchToConversation(response.data);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div {...props} onWheel={handleScroll} onTouchMove={handleScroll}>
|
||||
<div {...messageProps} onWheel={handleScroll} onTouchMove={handleScroll}>
|
||||
<div className="relative m-auto flex gap-4 p-4 text-base md:max-w-2xl md:gap-6 md:py-6 lg:max-w-2xl lg:px-0 xl:max-w-3xl">
|
||||
<div className="relative flex h-[40px] w-[40px] flex-col items-end text-right text-xs md:text-sm">
|
||||
{typeof icon === 'string' && /[^\\x00-\\x7F]+/.test(icon as string) ? (
|
||||
|
|
@ -153,7 +164,7 @@ export default function Message({
|
|||
<div className="relative flex w-[calc(100%-50px)] flex-col gap-1 md:gap-3 lg:w-[calc(100%-115px)]">
|
||||
{searchResult && (
|
||||
<SubRow
|
||||
classes={props.titleclass + ' rounded'}
|
||||
classes={messageProps.titleclass + ' rounded'}
|
||||
subclasses="switch-result pl-2 pb-2"
|
||||
onClick={clickSearchResult}
|
||||
>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue