Skip to content

Commit 2a814a7

Browse files
committed
better autoscroll
1 parent 3ff0710 commit 2a814a7

File tree

2 files changed

+42
-21
lines changed

2 files changed

+42
-21
lines changed

tools/server/webui/src/components/ChatScreen.tsx

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect, useMemo, useState } from 'react';
1+
import { useEffect, useMemo, useRef, useState } from 'react';
22
import { CallbackGeneratedChunk, useAppContext } from '../utils/app.context';
33
import ChatMessage from './ChatMessage';
44
import { CanvasType, Message, PendingMessage } from '../utils/types';
@@ -19,6 +19,7 @@ import {
1919
import Dropzone from 'react-dropzone';
2020
import toast from 'react-hot-toast';
2121
import ChatInputExtraContextItem from './ChatInputExtraContextItem.tsx';
22+
import { scrollToBottom, useChatScroll } from './useChatScroll.tsx';
2223

2324
/**
2425
* A message display is a message node with additional information for rendering.
@@ -84,24 +85,6 @@ function getListMessageDisplay(
8485
return res;
8586
}
8687

87-
const scrollToBottom = throttle(
88-
(requiresNearBottom: boolean, delay: number = 80) => {
89-
const mainScrollElem = document.getElementById('main-scroll');
90-
if (!mainScrollElem) return;
91-
const spaceToBottom =
92-
mainScrollElem.scrollHeight -
93-
mainScrollElem.scrollTop -
94-
mainScrollElem.clientHeight;
95-
if (!requiresNearBottom || spaceToBottom < 50) {
96-
setTimeout(
97-
() => mainScrollElem.scrollTo({ top: mainScrollElem.scrollHeight }),
98-
delay
99-
);
100-
}
101-
},
102-
80
103-
);
104-
10588
export default function ChatScreen() {
10689
const {
10790
viewingChat,
@@ -117,6 +100,9 @@ export default function ChatScreen() {
117100
const extraContext = useChatExtraContext();
118101
useVSCodeContext(textarea, extraContext);
119102

103+
const msgListRef = useRef<HTMLDivElement>(null);
104+
useChatScroll(msgListRef);
105+
120106
// keep track of leaf node for rendering
121107
const [currNodeId, setCurrNodeId] = useState<number>(-1);
122108
const messages: MessageDisplay[] = useMemo(() => {
@@ -139,7 +125,7 @@ export default function ChatScreen() {
139125
if (currLeafNodeId) {
140126
setCurrNodeId(currLeafNodeId);
141127
}
142-
scrollToBottom(true);
128+
// useChatScroll will handle the auto scroll
143129
};
144130

145131
const sendNewMessage = async () => {
@@ -246,7 +232,7 @@ export default function ChatScreen() {
246232
})}
247233
>
248234
{/* chat messages */}
249-
<div id="messages-list" className="grow">
235+
<div id="messages-list" className="grow" ref={msgListRef}>
250236
<div className="mt-auto flex flex-col items-center">
251237
{/* placeholder to shift the message to the bottom */}
252238
{viewingChat ? (
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import React, { useEffect } from 'react';
2+
import { throttle } from '../utils/misc';
3+
4+
export const scrollToBottom = throttle(
5+
(requiresNearBottom: boolean, delay?: number) => {
6+
const mainScrollElem = document.getElementById('main-scroll');
7+
if (!mainScrollElem) return;
8+
const spaceToBottom =
9+
mainScrollElem.scrollHeight -
10+
mainScrollElem.scrollTop -
11+
mainScrollElem.clientHeight;
12+
if (!requiresNearBottom || spaceToBottom < 100) {
13+
setTimeout(
14+
() => mainScrollElem.scrollTo({ top: mainScrollElem.scrollHeight }),
15+
delay ?? 80
16+
);
17+
}
18+
},
19+
80
20+
);
21+
22+
export function useChatScroll(msgListRef: React.RefObject<HTMLDivElement>) {
23+
useEffect(() => {
24+
if (!msgListRef.current) return;
25+
26+
const resizeObserver = new ResizeObserver((_) => {
27+
scrollToBottom(true);
28+
});
29+
30+
resizeObserver.observe(msgListRef.current);
31+
return () => {
32+
resizeObserver.disconnect();
33+
};
34+
}, [msgListRef]);
35+
}

0 commit comments

Comments
 (0)