diff --git a/gui/main.qml b/gui/main.qml index 2d6231e..ddc069c 100644 --- a/gui/main.qml +++ b/gui/main.qml @@ -363,24 +363,16 @@ ApplicationWindow { value: dbus.chatText || "" } - ListView { - id: chatList + Flickable { + id: chatFlick anchors.fill: parent anchors.margins: 4 clip: true - spacing: 8 - cacheBuffer: 200 - model: chatModel - verticalLayoutDirection: ListView.TopToBottom - - property bool _scrolling: false - - Component.onCompleted: { - Qt.callLater(function() { chatList.positionViewAtEnd() }) - } + contentWidth: width + contentHeight: chatColumn.implicitHeight + boundsBehavior: Flickable.StopAtBounds onContentYChanged: { - if (_scrolling) return let bottom = contentHeight - height parent.atBottom = (bottom <= 0 || contentY >= bottom - 4) @@ -388,47 +380,51 @@ ApplicationWindow { if (contentY < 100 && contentHeight > height) { let oldHeight = contentHeight if (chatModel.loadEarlier()) { - // Maintain scroll position after prepend Qt.callLater(function() { - chatList.contentY += (chatList.contentHeight - oldHeight) + chatFlick.contentY += (chatFlick.contentHeight - oldHeight) }) } } } onContentHeightChanged: { - if (parent.atBottom && !_scrolling) { - _scrolling = true - Qt.callLater(function() { - chatList.positionViewAtEnd() - chatList._scrolling = false - }) + if (parent.atBottom) { + contentY = Math.max(0, contentHeight - height) } } - // Placeholder when empty - Label { - anchors.centerIn: parent - visible: chatList.count === 0 - text: dbus.activeSessionId - ? "(waiting for output...)" - : "Select or create a session" - color: palette.placeholderText - } + Column { + id: chatColumn + width: chatFlick.width + spacing: 8 - delegate: Loader { - width: chatList.width - height: item ? item.implicitHeight : 0 - property var block: model - sourceComponent: { - switch (blockType) { - case "user": return userDelegate - case "assistant": return assistantDelegate - case "reasoning": return reasoningDelegate - case "call": return callDelegate - case "tool": return toolDelegate - case "error": return errorDelegate - case "info": return infoDelegate - default: return assistantDelegate + // Placeholder + Label { + visible: chatRepeater.count === 0 + text: dbus.activeSessionId + ? "(waiting for output...)" + : "Select or create a session" + color: palette.placeholderText + } + + Repeater { + id: chatRepeater + model: chatModel + + delegate: Loader { + width: chatColumn.width + property var block: model + sourceComponent: { + switch (blockType) { + case "user": return userDelegate + case "assistant": return assistantDelegate + case "reasoning": return reasoningDelegate + case "call": return callDelegate + case "tool": return toolDelegate + case "error": return errorDelegate + case "info": return infoDelegate + default: return assistantDelegate + } + } } } } @@ -441,7 +437,7 @@ ApplicationWindow { icon.name: "go-down" visible: !parent.atBottom onClicked: { - chatList.contentY = Math.max(0, chatList.contentHeight - chatList.height) + chatFlick.contentY = Math.max(0, chatFlick.contentHeight - chatFlick.height) parent.atBottom = true } ToolTip.text: "Scroll to bottom"