gui: smart auto-scroll follows streaming content

- Add shouldAutoScroll property to track user intent
- Auto-scroll when near bottom (within 50px threshold)
- Disable auto-scroll when user scrolls up to read history
- Re-enable when user scrolls back to bottom
- Reset to auto-scroll on session/agent switch
- Increase timer interval to 16ms for smoother layout settling
This commit is contained in:
Levi Neely 2026-08-07 10:21:53 +02:00
parent 1ca5563fdb
commit 624b7ad66d
1 changed files with 27 additions and 5 deletions

View File

@ -96,9 +96,12 @@ ApplicationWindow {
// Deferred scroll timer — fires once after model changes settle // Deferred scroll timer — fires once after model changes settle
Timer { Timer {
id: scrollTimer id: scrollTimer
interval: 0 interval: 16 // ~1 frame at 60fps, allows layout to settle
repeat: false repeat: false
onTriggered: chatView.positionViewAtEnd() onTriggered: {
if (chatView.shouldAutoScroll)
chatView.positionViewAtEnd()
}
} }
// Session ID to kill — set on right-click, consumed by context menu // Session ID to kill — set on right-click, consumed by context menu
@ -485,6 +488,7 @@ ApplicationWindow {
target: ollie target: ollie
function onActiveSessionIdChanged() { function onActiveSessionIdChanged() {
chatModel.reset() chatModel.reset()
chatView.shouldAutoScroll = true // Reset scroll state on session switch
if (ollie.activeSessionId && ollie.activeAgentId) { if (ollie.activeSessionId && ollie.activeAgentId) {
var log = ollie.readLogForSession(ollie.activeSessionId, ollie.activeAgentId) var log = ollie.readLogForSession(ollie.activeSessionId, ollie.activeAgentId)
if (log) { if (log) {
@ -496,6 +500,7 @@ ApplicationWindow {
} }
function onActiveAgentIdChanged() { function onActiveAgentIdChanged() {
chatModel.reset() chatModel.reset()
chatView.shouldAutoScroll = true // Reset scroll state on agent switch
if (ollie.activeSessionId && ollie.activeAgentId) { if (ollie.activeSessionId && ollie.activeAgentId) {
var log = ollie.readLogForSession(ollie.activeSessionId, ollie.activeAgentId) var log = ollie.readLogForSession(ollie.activeSessionId, ollie.activeAgentId)
if (log) { if (log) {
@ -1244,15 +1249,32 @@ ApplicationWindow {
boundsBehavior: Flickable.StopAtBounds boundsBehavior: Flickable.StopAtBounds
visible: ollie.daemonConnected && ollie.activeAgentId !== "" visible: ollie.daemonConnected && ollie.activeAgentId !== ""
// Auto-scroll when near bottom (within ~50px or at end)
// Reset to true on new blocks, user can scroll up to disable
property bool shouldAutoScroll: true
readonly property bool nearBottom: contentHeight - contentY - height < 50
onContentYChanged: {
// User scrolled up - disable auto-scroll
// User scrolled to bottom - re-enable
if (dragging || flicking) {
shouldAutoScroll = nearBottom || atYEnd
}
}
onCountChanged: { onCountChanged: {
if (count > 0) // New block added - always scroll if we were following
scrollTimer.start() if (count > 0) {
if (shouldAutoScroll)
scrollTimer.start()
}
} }
Connections { Connections {
target: chatModel target: chatModel
function onDataChanged() { function onDataChanged() {
if (chatView.atYEnd && chatView.count > 0) // Content updated (streaming) - scroll if following
if (chatView.shouldAutoScroll && chatView.count > 0)
scrollTimer.start() scrollTimer.start()
} }
} }