Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions web-app/src/routes/threads/$threadId.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ function ThreadDetail() {
const [isAtBottom, setIsAtBottom] = useState(true)
const [hasScrollbar, setHasScrollbar] = useState(false)
const lastScrollTopRef = useRef(0)
const userIntendedPositionRef = useRef<number | null>(null)
const wasStreamingRef = useRef(false)
const { currentThreadId, setCurrentThreadId } = useThreads()
const { setCurrentAssistant, assistants } = useAssistant()
const { setMessages, deleteMessage } = useMessages()
Expand Down Expand Up @@ -112,6 +114,8 @@ function ThreadDetail() {
scrollToBottom()
setIsAtBottom(true)
setIsUserScrolling(false)
userIntendedPositionRef.current = null
wasStreamingRef.current = false
checkScrollState()
return
}
Expand All @@ -123,11 +127,39 @@ function ThreadDetail() {
scrollToBottom()
setIsAtBottom(true)
setIsUserScrolling(false)
userIntendedPositionRef.current = null
wasStreamingRef.current = false
checkScrollState()
}, [threadId])

// Single useEffect for all auto-scrolling logic
useEffect(() => {
// Track streaming state changes
const isCurrentlyStreaming = !!streamingContent
const justFinishedStreaming = wasStreamingRef.current && !isCurrentlyStreaming
wasStreamingRef.current = isCurrentlyStreaming

// If streaming just finished and user had an intended position, restore it
if (justFinishedStreaming && userIntendedPositionRef.current !== null) {
// Small delay to ensure DOM has updated
setTimeout(() => {
if (scrollContainerRef.current && userIntendedPositionRef.current !== null) {
scrollContainerRef.current.scrollTo({
top: userIntendedPositionRef.current,
behavior: 'smooth'
})
userIntendedPositionRef.current = null
setIsUserScrolling(false)
}
}, 100)
return
}

// Clear intended position when streaming starts fresh
if (isCurrentlyStreaming && !wasStreamingRef.current) {
userIntendedPositionRef.current = null
}

// Only auto-scroll when the user is not actively scrolling
// AND either at the bottom OR there's streaming content
if (!isUserScrolling && (streamingContent || isAtBottom) && messagesCount) {
Expand Down Expand Up @@ -163,6 +195,11 @@ function ThreadDetail() {
// Detect if this is a user-initiated scroll
if (Math.abs(scrollTop - lastScrollTopRef.current) > 10) {
setIsUserScrolling(!isBottom)

// If user scrolls during streaming and moves away from bottom, record their intended position
if (streamingContent && !isBottom) {
userIntendedPositionRef.current = scrollTop
}
}
setIsAtBottom(isBottom)
setHasScrollbar(hasScroll)
Expand All @@ -180,6 +217,11 @@ function ThreadDetail() {
// Detect if this is a user-initiated scroll
if (Math.abs(scrollTop - lastScrollTopRef.current) > 10) {
setIsUserScrolling(!isBottom)

// If user scrolls during streaming and moves away from bottom, record their intended position
if (streamingContent && !isBottom) {
userIntendedPositionRef.current = scrollTop
}
}
setIsAtBottom(isBottom)
setHasScrollbar(hasScroll)
Expand Down
Loading