Skip to content

Commit c38b79c

Browse files
feat: add auto-scroll toggle to chat settings (#111)
Introduces an 'Auto scroll to bottom' toggle in the settings menu, allowing users to enable or disable automatic scrolling in the chat view. Updates the store to persist the autoScroll setting and integrates the toggle into the UI and chat content logic. The default auto scrolling behavior is extremely annoying, especially if a user has to keep scrolling up to read the response from the start. If a user tried to scroll up while the response was still streaming it would keep scrolling back down. This gives users the option to manually scroll (and perhaps, if all's good, can be made the default behavior).
1 parent adda8ef commit c38b79c

File tree

6 files changed

+48
-0
lines changed

6 files changed

+48
-0
lines changed

public/locales/en/main.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"cloneChat": "Clone Chat",
4848
"cloned": "Cloned",
4949
"enterToSubmit": "Enter to submit",
50+
"autoScroll": "Auto scroll to bottom",
5051
"submitPlaceholder": "Type a message or click [/] for prompts...",
5152
"stopNonStreamGenerationWarning": "Stopping generation is not recommended for non-stream models. Only use it if UI is stuck. Do you want to proceed?",
5253
"reduceMessagesWarning": "Reducing messages may result in data loss. It is recommended to download the chat in JSON format if you care about the data. Do you want to proceed?",

src/components/Chat/ChatContent/ChatContent.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ const ChatContent = () => {
4343
const advancedMode = useStore((state) => state.advancedMode);
4444
const generating = useStore.getState().generating;
4545
const hideSideMenu = useStore((state) => state.hideSideMenu);
46+
const autoScroll = useStore((state) => state.autoScroll);
4647
const model = useStore((state) =>
4748
state.chats &&
4849
state.chats.length > 0 &&
@@ -99,11 +100,17 @@ const ChatContent = () => {
99100

100101
const { error } = useSubmit();
101102

103+
// Custom scroller function to control auto-scroll behavior
104+
const customScroller = ({ maxValue }: { maxValue: number; minValue: number; offsetHeight: number; scrollHeight: number; scrollTop: number }) => {
105+
return autoScroll ? maxValue : 0;
106+
};
107+
102108
return (
103109
<div className='flex-1 overflow-hidden'>
104110
<ScrollToBottom
105111
className='h-full dark:bg-gray-800'
106112
followButtonClassName='hidden'
113+
scroller={customScroller}
107114
>
108115
<ScrollToBottomButton />
109116
<div className='flex flex-col items-center text-sm dark:bg-gray-800'>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import React, { useEffect, useState } from 'react';
2+
import { useTranslation } from 'react-i18next';
3+
import useStore from '@store/store';
4+
import Toggle from '@components/Toggle';
5+
6+
const AutoScrollToggle = () => {
7+
const { t } = useTranslation();
8+
9+
const setAutoScroll = useStore((state) => state.setAutoScroll);
10+
11+
const [isChecked, setIsChecked] = useState<boolean>(
12+
useStore.getState().autoScroll
13+
);
14+
15+
useEffect(() => {
16+
setAutoScroll(isChecked);
17+
}, [isChecked]);
18+
19+
return (
20+
<Toggle
21+
label={t('autoScroll') as string}
22+
isChecked={isChecked}
23+
setIsChecked={setIsChecked}
24+
/>
25+
);
26+
};
27+
28+
export default AutoScrollToggle;

src/components/SettingsMenu/SettingsMenu.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import InlineLatexToggle from './InlineLatexToggle';
1212
import PromptLibraryMenu from '@components/PromptLibraryMenu';
1313
import ChatConfigMenu from '@components/ChatConfigMenu';
1414
import EnterToSubmitToggle from './EnterToSubmitToggle';
15+
import AutoScrollToggle from './AutoScrollToggle';
1516
import TotalTokenCost, { TotalTokenCostToggle } from './TotalTokenCost';
1617
import ClearConversation from '@components/Menu/MenuOptions/ClearConversation';
1718
import DisplayChatSizeToggle from './DisplayChatSizeToggle';
@@ -50,6 +51,7 @@ const SettingsMenu = () => {
5051
<div className='flex flex-col gap-3'>
5152
<AutoTitleToggle />
5253
<EnterToSubmitToggle />
54+
<AutoScrollToggle />
5355
<InlineLatexToggle />
5456
<AdvancedModeToggle />
5557
<TotalTokenCostToggle />

src/store/config-slice.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export interface ConfigSlice {
2222
menuWidth: number;
2323
displayChatSize: boolean;
2424
defaultImageDetail: ImageDetail;
25+
autoScroll: boolean;
2526
setOpenConfig: (openConfig: boolean) => void;
2627
setTheme: (theme: Theme) => void;
2728
setAutoTitle: (autoTitle: boolean) => void;
@@ -39,6 +40,7 @@ export interface ConfigSlice {
3940
setMenuWidth: (menuWidth: number) => void;
4041
setDisplayChatSize: (displayChatSize: boolean) => void;
4142
setDefaultImageDetail: (imageDetail: ImageDetail) => void;
43+
setAutoScroll: (autoScroll: boolean) => void;
4244
}
4345

4446
export const createConfigSlice: StoreSlice<ConfigSlice> = (set, get) => ({
@@ -59,6 +61,7 @@ export const createConfigSlice: StoreSlice<ConfigSlice> = (set, get) => ({
5961
menuWidth: _defaultMenuWidth,
6062
displayChatSize: _defaultDisplayChatSize,
6163
defaultImageDetail: _defaultImageDetail,
64+
autoScroll: true,
6265
setOpenConfig: (openConfig: boolean) => {
6366
set((prev: ConfigSlice) => ({
6467
...prev,
@@ -161,4 +164,10 @@ export const createConfigSlice: StoreSlice<ConfigSlice> = (set, get) => ({
161164
defaultImageDetail: imageDetail,
162165
}));
163166
},
167+
setAutoScroll: (autoScroll: boolean) => {
168+
set((prev: ConfigSlice) => ({
169+
...prev,
170+
autoScroll: autoScroll,
171+
}));
172+
},
164173
});

src/store/store.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ export const createPartializedState = (state: StoreState) => ({
7171
displayChatSize: state.displayChatSize,
7272
menuWidth: state.menuWidth,
7373
defaultImageDetail: state.defaultImageDetail,
74+
autoScroll: state.autoScroll,
7475
customModels: state.customModels,
7576
});
7677

0 commit comments

Comments
 (0)