212 lines
4.1 KiB
QML
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();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|