Make streaming behavior more natural (#2336)

* fix scrolling behavior + add cursor to streaming chats

* lint

* linting

---------

Co-authored-by: timothycarambat <rambat1010@gmail.com>
This commit is contained in:
Sean Hatfield 2024-09-23 08:53:36 -07:00 committed by GitHub
parent 12b8af4654
commit d75fee0c07
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -20,13 +20,16 @@ export default function ChatHistory({
regenerateAssistantMessage,
hasAttachments = false,
}) {
const lastScrollTopRef = useRef(0);
const { user } = useUser();
const { threadSlug = null } = useParams();
const { showing, showModal, hideModal } = useManageWorkspaceModal();
const [isAtBottom, setIsAtBottom] = useState(true);
const chatHistoryRef = useRef(null);
const [textSize, setTextSize] = useState("normal");
const [isUserScrolling, setIsUserScrolling] = useState(false);
const showScrollbar = Appearance.getSettings()?.showScrollbar || false;
const isStreaming = history[history.length - 1]?.animate;
const getTextSizeClass = (size) => {
switch (size) {
@ -58,35 +61,44 @@ export default function ChatHistory({
}, []);
useEffect(() => {
if (isAtBottom) scrollToBottom();
}, [history]);
if (!isUserScrolling && (isAtBottom || isStreaming)) {
scrollToBottom(false); // Use instant scroll for auto-scrolling
}
}, [history, isAtBottom, isStreaming, isUserScrolling]);
const handleScroll = (e) => {
const { scrollTop, scrollHeight, clientHeight } = e.target;
const isBottom = scrollHeight - scrollTop === clientHeight;
// Detect if this is a user-initiated scroll
if (Math.abs(scrollTop - lastScrollTopRef.current) > 10) {
setIsUserScrolling(!isBottom);
}
const handleScroll = () => {
const diff =
chatHistoryRef.current.scrollHeight -
chatHistoryRef.current.scrollTop -
chatHistoryRef.current.clientHeight;
// Fuzzy margin for what qualifies as "bottom". Stronger than straight comparison since that may change over time.
const isBottom = diff <= 10;
setIsAtBottom(isBottom);
lastScrollTopRef.current = scrollTop;
};
const debouncedScroll = debounce(handleScroll, 100);
useEffect(() => {
function watchScrollEvent() {
if (!chatHistoryRef.current) return null;
const chatHistoryElement = chatHistoryRef.current;
if (!chatHistoryElement) return null;
const chatHistoryElement = chatHistoryRef.current;
if (chatHistoryElement) {
chatHistoryElement.addEventListener("scroll", debouncedScroll);
return () =>
chatHistoryElement.removeEventListener("scroll", debouncedScroll);
}
watchScrollEvent();
}, []);
const scrollToBottom = () => {
const scrollToBottom = (smooth = false) => {
if (chatHistoryRef.current) {
chatHistoryRef.current.scrollTo({
top: chatHistoryRef.current.scrollHeight,
behavior: "smooth",
// Smooth is on when user clicks the button but disabled during auto scroll
// We must disable this during auto scroll because it causes issues with
// detecting when we are at the bottom of the chat.
...(smooth ? { behavior: "smooth" } : {}),
});
}
};
@ -197,6 +209,7 @@ export default function ChatHistory({
}`}
id="chat-history"
ref={chatHistoryRef}
onScroll={handleScroll}
>
{history.map((props, index) => {
const isLastBotReply =
@ -251,12 +264,14 @@ export default function ChatHistory({
{!isAtBottom && (
<div className="fixed bottom-40 right-10 md:right-20 z-50 cursor-pointer animate-pulse">
<div className="flex flex-col items-center">
<div className="p-1 rounded-full border border-white/10 bg-white/10 hover:bg-white/20 hover:text-white">
<ArrowDown
weight="bold"
className="text-white/60 w-5 h-5"
onClick={scrollToBottom}
/>
<div
className="p-1 rounded-full border border-white/10 bg-white/10 hover:bg-white/20 hover:text-white"
onClick={() => {
scrollToBottom(true);
setIsUserScrolling(false);
}}
>
<ArrowDown weight="bold" className="text-white/60 w-5 h-5" />
</div>
</div>
</div>