Skip to content

Commit

Permalink
Make streaming behavior more natural (#2336)
Browse files Browse the repository at this point in the history
* fix scrolling behavior + add cursor to streaming chats

* lint

* linting

---------

Co-authored-by: timothycarambat <[email protected]>
  • Loading branch information
shatfield4 and timothycarambat authored Sep 23, 2024
1 parent 12b8af4 commit d75fee0
Showing 1 changed file with 37 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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" } : {}),
});
}
};
Expand Down Expand Up @@ -197,6 +209,7 @@ export default function ChatHistory({
}`}
id="chat-history"
ref={chatHistoryRef}
onScroll={handleScroll}
>
{history.map((props, index) => {
const isLastBotReply =
Expand Down Expand Up @@ -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>
Expand Down

0 comments on commit d75fee0

Please sign in to comment.