Fix chat rendering and improve scroll behavior
- Emit SegmentsRole in dataChanged signals so QML updates properly - Add onContentHeightChanged to scroll when content loads after session switch - Add floating scroll-to-bottom button that appears when scrolled up - Wrap chat ScrollView in Item to allow button overlay
This commit is contained in:
parent
07232b94df
commit
0ac03a9931
|
|
@ -223,7 +223,7 @@ void ChatBlockModel::appendData(const QString &text)
|
|||
|
||||
// But emit with the rendered HTML that includes partial content
|
||||
QModelIndex idx = index(m_blocks.size() - 1);
|
||||
emit dataChanged(idx, idx, {RenderedHtmlRole});
|
||||
emit dataChanged(idx, idx, {RenderedHtmlRole, SegmentsRole});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -483,7 +483,7 @@ void ChatBlockModel::appendContent(const QString &line)
|
|||
rebuildBlockHtml(m_blocks.size() - 1);
|
||||
|
||||
QModelIndex idx = index(m_blocks.size() - 1);
|
||||
emit dataChanged(idx, idx, {ContentRole, RenderedHtmlRole});
|
||||
emit dataChanged(idx, idx, {ContentRole, RenderedHtmlRole, SegmentsRole});
|
||||
}
|
||||
|
||||
void ChatBlockModel::processLine(const QString &line)
|
||||
|
|
|
|||
140
gui/main.qml
140
gui/main.qml
|
|
@ -1220,64 +1220,74 @@ ApplicationWindow {
|
|||
|
||||
Rectangle { Layout.fillWidth: true; height: 1; color: theme.midColor }
|
||||
|
||||
// Chat display — block-based ListView
|
||||
ScrollView {
|
||||
// Chat display — block-based ListView with scroll-to-bottom button
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
clip: true
|
||||
|
||||
// Placeholder when no agent selected or disconnected
|
||||
Label {
|
||||
anchors.centerIn: parent
|
||||
text: {
|
||||
if (!ollie.daemonConnected) return "Not connected"
|
||||
if (!ollie.activeSessionId) return "Select a session"
|
||||
return "Select an agent to view chat"
|
||||
}
|
||||
color: theme.midColor
|
||||
font.italic: true
|
||||
font.pixelSize: theme.fontSize > 0 ? theme.fontSize : 12
|
||||
visible: !ollie.activeAgentId || !ollie.daemonConnected
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: chatView
|
||||
model: chatModel
|
||||
spacing: 4
|
||||
|
||||
ScrollView {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 4
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
visible: ollie.daemonConnected && ollie.activeAgentId !== ""
|
||||
clip: true
|
||||
|
||||
// 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
|
||||
// Placeholder when no agent selected or disconnected
|
||||
Label {
|
||||
anchors.centerIn: parent
|
||||
text: {
|
||||
if (!ollie.daemonConnected) return "Not connected"
|
||||
if (!ollie.activeSessionId) return "Select a session"
|
||||
return "Select an agent to view chat"
|
||||
}
|
||||
color: theme.midColor
|
||||
font.italic: true
|
||||
font.pixelSize: theme.fontSize > 0 ? theme.fontSize : 12
|
||||
visible: !ollie.activeAgentId || !ollie.daemonConnected
|
||||
}
|
||||
|
||||
onCountChanged: {
|
||||
// New block added - always scroll if we were following
|
||||
if (count > 0) {
|
||||
if (shouldAutoScroll)
|
||||
scrollTimer.start()
|
||||
}
|
||||
}
|
||||
ListView {
|
||||
id: chatView
|
||||
model: chatModel
|
||||
spacing: 4
|
||||
anchors.fill: parent
|
||||
anchors.margins: 4
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
visible: ollie.daemonConnected && ollie.activeAgentId !== ""
|
||||
|
||||
Connections {
|
||||
target: chatModel
|
||||
function onDataChanged() {
|
||||
// Content updated (streaming) - scroll if following
|
||||
if (chatView.shouldAutoScroll && chatView.count > 0)
|
||||
scrollTimer.start()
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
onContentHeightChanged: {
|
||||
// Content height changed (e.g., after loading) - scroll if following
|
||||
if (shouldAutoScroll && count > 0) {
|
||||
positionViewAtEnd()
|
||||
}
|
||||
}
|
||||
|
||||
onCountChanged: {
|
||||
// New block added - always scroll if we were following
|
||||
if (count > 0) {
|
||||
if (shouldAutoScroll)
|
||||
scrollTimer.start()
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: chatModel
|
||||
function onDataChanged() {
|
||||
// Content updated (streaming) - scroll if following
|
||||
if (chatView.shouldAutoScroll && chatView.count > 0)
|
||||
scrollTimer.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delegate: ColumnLayout {
|
||||
id: blockItem
|
||||
|
|
@ -1585,6 +1595,38 @@ property bool blockExpanded: {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Scroll-to-bottom floating button
|
||||
Rectangle {
|
||||
id: scrollToBottomBtn
|
||||
width: 40
|
||||
height: 40
|
||||
radius: 20
|
||||
color: theme.highlightColor
|
||||
opacity: 0.9
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: 16
|
||||
visible: !chatView.shouldAutoScroll && chatView.count > 0 && chatView.visible
|
||||
|
||||
Label {
|
||||
anchors.centerIn: parent
|
||||
text: "↓"
|
||||
font.pixelSize: 20
|
||||
font.bold: true
|
||||
color: theme.highlightedTextColor
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
chatView.shouldAutoScroll = true
|
||||
chatView.positionViewAtEnd()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle { Layout.fillWidth: true; height: 1; color: theme.midColor }
|
||||
|
|
|
|||
Reference in New Issue