pragma ComponentBehavior: Bound import QtQuick import QtQuick.Layouts import Quickshell import ZShell.Config import ZShell.Llm import qs.Components import qs.Services MouseArea { id: root readonly property ChatGeneration current: modelData.activeGeneration required property int index readonly property bool isUser: modelData.role === ChatMessage.Role.User required property ChatMessage modelData property bool reasoningExpanded: false readonly property var blocks: blockify(current.segments) property real savedContentY: -1 function handleReasoningToggle(expanded: bool): void { const view = root.ListView.view; if (!view) return; if (expanded) { root.savedContentY = view.contentY; root.reasoningExpanded = true; } else { root.reasoningExpanded = false; if (root.savedContentY !== -1) { restoreAnim.to = root.savedContentY; restoreAnim.restart(); root.savedContentY = -1; } } } function blockify(segs) { const blocks = []; for (let i = 0; i < segs.length; i++) { const seg = segs[i]; if (seg.type === LlmSegment.Type.Content) { blocks.push({ kind: "content", id: i, segments: [seg] }); } else { const last = blocks.length ? blocks[blocks.length - 1] : null; if (last && last.kind === "process") { last.segments.push(seg); } else { blocks.push({ kind: "process", id: i, segments: [seg] }); } } } return blocks; } implicitWidth: ListView.view.width hoverEnabled: true preventStealing: true implicitHeight: layout.implicitHeight + Tokens.padding.medium Binding { property: "contentY" restoreMode: Binding.RestoreNone target: root.ListView.view value: root.y - Tokens.padding.large * 2 when: root.reasoningExpanded && root.ListView.view && ((root.y - Tokens.padding.large * 2) < root.ListView.view.contentY) } Anim { id: restoreAnim property: "contentY" target: root.ListView.view type: Anim.DefaultEffects } Column { id: layout anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right Repeater { id: segmentRep model: ScriptModel { values: root.blocks objectProp: "id" } delegate: DelegateChooser { role: "kind" DelegateChoice { roleValue: "process" delegate: ProcessBlock { required property int index required property var modelData segments: modelData.segments isActive: index === root.blocks.length - 1 width: root.width onExpandedChanged: root.handleReasoningToggle(expanded) } } DelegateChoice { roleValue: "content" delegate: ContentBubble { required property var modelData isUser: root.isUser segment: modelData.segments[0] width: root.width repeater: segmentRep message: root.modelData current: root.current hovered: root.containsMouse onEdit: text => { root.modelData.edit(text); if (root.isUser) root.modelData.generate(); } } } } } } }