mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
Fix useScrollToBottom hook for rapid content changes (#7715)
Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
parent
0ab9d97f2d
commit
c71ef11a25
@ -127,7 +127,7 @@ export function ChatInterface() {
|
||||
<div
|
||||
ref={scrollRef}
|
||||
onScroll={(e) => onChatBodyScroll(e.currentTarget)}
|
||||
className="flex flex-col grow overflow-y-auto overflow-x-hidden px-4 pt-4 gap-2"
|
||||
className="flex flex-col grow overflow-y-auto overflow-x-hidden px-4 pt-4 gap-2 fast-smooth-scroll"
|
||||
>
|
||||
{isLoadingMessages && (
|
||||
<div className="flex justify-center">
|
||||
|
||||
@ -20,7 +20,7 @@ export function JupyterEditor({ maxWidth }: JupyterEditorProps) {
|
||||
<div className="flex-1 h-full flex flex-col" style={{ maxWidth }}>
|
||||
<div
|
||||
data-testid="jupyter-container"
|
||||
className="flex-1 overflow-y-auto"
|
||||
className="flex-1 overflow-y-auto fast-smooth-scroll"
|
||||
ref={jupyterRef}
|
||||
onScroll={(e) => onChatBodyScroll(e.currentTarget)}
|
||||
>
|
||||
|
||||
@ -131,7 +131,10 @@ function SecurityInvariant() {
|
||||
{t(I18nKey.INVARIANT$EXPORT_TRACE_LABEL)}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex-1 p-4 max-h-screen overflow-y-auto" ref={logsRef}>
|
||||
<div
|
||||
className="flex-1 p-4 max-h-screen overflow-y-auto fast-smooth-scroll"
|
||||
ref={logsRef}
|
||||
>
|
||||
{logs.map((log: SecurityAnalyzerLog, index: number) => (
|
||||
<div
|
||||
key={index}
|
||||
|
||||
@ -1,41 +1,70 @@
|
||||
import { RefObject, useEffect, useState } from "react";
|
||||
import { RefObject, useEffect, useState, useCallback } from "react";
|
||||
|
||||
export function useScrollToBottom(scrollRef: RefObject<HTMLDivElement | null>) {
|
||||
// for auto-scroll
|
||||
// Track whether we should auto-scroll to the bottom when content changes
|
||||
const [shouldScrollToBottom, setShouldScrollToBottom] = useState(true);
|
||||
|
||||
const [autoScroll, setAutoScroll] = useState(true);
|
||||
// Track whether the user is currently at the bottom of the scroll area
|
||||
const [hitBottom, setHitBottom] = useState(true);
|
||||
|
||||
const onChatBodyScroll = (e: HTMLElement) => {
|
||||
const bottomHeight = e.scrollTop + e.clientHeight;
|
||||
// Check if the scroll position is at the bottom
|
||||
const isAtBottom = useCallback((element: HTMLElement): boolean => {
|
||||
const bottomThreshold = 10; // Pixels from bottom to consider "at bottom"
|
||||
const bottomPosition = element.scrollTop + element.clientHeight;
|
||||
return bottomPosition >= element.scrollHeight - bottomThreshold;
|
||||
}, []);
|
||||
|
||||
const isHitBottom = bottomHeight >= e.scrollHeight - 10;
|
||||
// Handle scroll events
|
||||
const onChatBodyScroll = useCallback(
|
||||
(e: HTMLElement) => {
|
||||
const isCurrentlyAtBottom = isAtBottom(e);
|
||||
setHitBottom(isCurrentlyAtBottom);
|
||||
|
||||
setHitBottom(isHitBottom);
|
||||
setAutoScroll(isHitBottom);
|
||||
};
|
||||
// Only update shouldScrollToBottom when user manually scrolls
|
||||
// This prevents content changes from affecting our scroll behavior decision
|
||||
setShouldScrollToBottom(isCurrentlyAtBottom);
|
||||
},
|
||||
[isAtBottom],
|
||||
);
|
||||
|
||||
function scrollDomToBottom() {
|
||||
// Scroll to bottom function with animation
|
||||
const scrollDomToBottom = useCallback(() => {
|
||||
const dom = scrollRef.current;
|
||||
if (dom) {
|
||||
requestAnimationFrame(() => {
|
||||
setAutoScroll(true);
|
||||
dom.scrollTo({ top: dom.scrollHeight, behavior: "auto" });
|
||||
// Set shouldScrollToBottom to true when manually scrolling to bottom
|
||||
setShouldScrollToBottom(true);
|
||||
setHitBottom(true);
|
||||
|
||||
// Use smooth scrolling but with a fast duration
|
||||
dom.scrollTo({
|
||||
top: dom.scrollHeight,
|
||||
behavior: "smooth",
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [scrollRef]);
|
||||
|
||||
// auto scroll
|
||||
// Auto-scroll effect that runs when content changes
|
||||
useEffect(() => {
|
||||
if (autoScroll) {
|
||||
scrollDomToBottom();
|
||||
// Only auto-scroll if the user was already at the bottom
|
||||
if (shouldScrollToBottom) {
|
||||
const dom = scrollRef.current;
|
||||
if (dom) {
|
||||
requestAnimationFrame(() => {
|
||||
dom.scrollTo({
|
||||
top: dom.scrollHeight,
|
||||
behavior: "smooth",
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
scrollRef,
|
||||
autoScroll,
|
||||
setAutoScroll,
|
||||
autoScroll: shouldScrollToBottom,
|
||||
setAutoScroll: setShouldScrollToBottom,
|
||||
scrollDomToBottom,
|
||||
hitBottom,
|
||||
setHitBottom,
|
||||
|
||||
@ -57,3 +57,9 @@ code {
|
||||
.markdown-body td {
|
||||
padding: 0.1rem 1rem;
|
||||
}
|
||||
|
||||
/* Fast smooth scrolling for chat interface */
|
||||
.fast-smooth-scroll {
|
||||
scroll-behavior: smooth;
|
||||
scroll-timeline: 100ms;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user