Files
z-bar-qt/Modules/Notifications/Sidebar/Chat/Content/MessageDelegate.qml
T
2026-08-26 17:10:34 +02:00

212 lines
4.1 KiB
QML

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
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: false
implicitHeight: layout.implicitHeight + Tokens.padding.medium
Behavior on current {
id: changeAnim
property var object: targetProperty
readonly property bool next: {
const cond = root.modelData.generations.indexOf(current) < root.modelData.generations.indexOf(changeAnim.targetValue);
return cond;
}
animation: SequentialAnimation {
ParallelAnimation {
Anim {
target: root
property: "x"
to: changeAnim.next ? -root.width / 2 : root.width / 2
from: 0
}
Anim {
target: root
property: "opacity"
from: 1
to: 0
}
}
PropertyAction {}
ParallelAnimation {
Anim {
target: root
property: "x"
from: changeAnim.next ? root.width / 2 : -root.width / 2
to: 0
}
Anim {
target: root
property: "opacity"
from: 0
to: 1
}
}
}
}
// Behavior on implicitHeight {
// enabled: !root.isUser && root.current.streaming
//
// Anim {}
// }
onCurrentChanged: {
console.log(modelData.generations.indexOf(current));
}
// 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
// onImplicitHeightChanged: console.log("LAYOUT:", layout.implicitHeight)
// add: Transition {
// Anim {
// from: 10
// property: "y"
// }
// }
// move: Transition {
// Anim {
// property: "y"
// }
// }
Repeater {
id: segmentRep
model: ScriptModel {
values: root.blocks
objectProp: "id"
}
delegate: DelegateChooser {
role: "kind"
DelegateChoice {
roleValue: "process"
delegate: ProcessBlock {
width: root.width
blocks: root.blocks
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();
}
}
}
}
}
}
}