150 lines
3.8 KiB
QML
150 lines
3.8 KiB
QML
import QtQuick
|
|
import QtQuick.Layouts
|
|
import ZShell.Config
|
|
import ZShell.Llm
|
|
import qs.Components
|
|
import qs.Services
|
|
|
|
Item {
|
|
id: root
|
|
|
|
required property var segments
|
|
required property bool isActive
|
|
property bool expanded: false
|
|
readonly property LlmSegment lastSegment: root.segments[root.segments.length - 1]
|
|
readonly property real totalElapsedMs: root.segments.reduce((sum, s) => sum + (s.elapsedMs ?? 0), 0)
|
|
|
|
implicitHeight: expandedRect.implicitHeight + collapsedText.implicitHeight + expandedRect.anchors.topMargin
|
|
|
|
LoadingIndicator {
|
|
id: spinnerReasoning
|
|
|
|
anchors.centerIn: expandBtn
|
|
implicitSize: collapsedText.implicitHeight
|
|
opacity: root.isActive ? 1 : 0
|
|
visible: opacity > 0
|
|
|
|
Behavior on opacity {
|
|
Anim {}
|
|
}
|
|
}
|
|
|
|
IconButton {
|
|
id: expandBtn
|
|
|
|
anchors.left: parent.left
|
|
anchors.verticalCenter: collapsedText.verticalCenter
|
|
font.pointSize: Tokens.font.size.large
|
|
anchors.topMargin: 0
|
|
icon: "keyboard_arrow_down"
|
|
inactiveOnColor: hovered ? Colors.palette.m3onSurface : Colors.palette.m3outline
|
|
opacity: root.isActive ? 0 : 1
|
|
rotation: root.expanded ? 180 : 0
|
|
type: IconButton.Text
|
|
|
|
Behavior on rotation {
|
|
Anim {}
|
|
}
|
|
|
|
onClicked: root.expanded = !root.expanded
|
|
}
|
|
|
|
CustomText {
|
|
id: collapsedText
|
|
|
|
anchors.left: expandBtn.right
|
|
anchors.margins: Tokens.padding.medium
|
|
anchors.right: parent.right
|
|
anchors.top: parent.top
|
|
anchors.topMargin: 0
|
|
color: Colors.palette.m3outline
|
|
font.pointSize: Tokens.font.size.small
|
|
text: {
|
|
if (root.isActive) {
|
|
if (root.lastSegment.type === LlmSegment.Type.ToolCall)
|
|
return qsTr("Using %1...").arg(root.lastSegment.name);
|
|
return qsTr("Thinking...");
|
|
}
|
|
return qsTr("Worked for %1s").arg((root.totalElapsedMs / 1000).toFixed(1));
|
|
}
|
|
|
|
Behavior on opacity {
|
|
Anim {}
|
|
}
|
|
}
|
|
|
|
CustomClippingRect {
|
|
id: expandedRect
|
|
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
anchors.top: collapsedText.bottom
|
|
anchors.topMargin: Tokens.spacing.medium
|
|
color: Colors.palette.m3surfaceContainerLow
|
|
implicitHeight: root.expanded ? expandedContent.contentHeight + expandedContent.anchors.margins * 2 : 0
|
|
opacity: root.expanded ? 1 : 0
|
|
radius: Tokens.rounding.medium
|
|
visible: opacity > 0
|
|
|
|
Behavior on implicitHeight {
|
|
Anim {
|
|
type: Anim.DefaultEffects
|
|
}
|
|
}
|
|
Behavior on opacity {
|
|
Anim {}
|
|
}
|
|
|
|
ListView {
|
|
id: expandedContent
|
|
|
|
anchors.fill: parent
|
|
anchors.margins: Tokens.padding.medium
|
|
model: root.segments
|
|
spacing: Tokens.spacing.medium
|
|
|
|
delegate: Item {
|
|
id: segment
|
|
|
|
required property LlmSegment modelData
|
|
required property int index
|
|
|
|
implicitWidth: ListView.view.width
|
|
implicitHeight: header.implicitHeight + content.implicitHeight + content.anchors.topMargin
|
|
|
|
RowLayout {
|
|
id: header
|
|
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
spacing: Tokens.spacing.small
|
|
|
|
MaterialIcon {
|
|
text: segment.modelData.type === LlmSegment.Type.Reasoning ? "cognition" : "build"
|
|
fill: 1
|
|
}
|
|
|
|
CustomText {
|
|
Layout.fillWidth: true
|
|
text: segment.modelData.type === LlmSegment.Type.Reasoning ? qsTr("Thought for %1s").arg((segment.modelData.elapsedMs / 1000).toFixed(1)) : qsTr("Used %1 for %2s").arg(segment.modelData.name).arg((segment.modelData.elapsedMs / 1000).toFixed(1))
|
|
}
|
|
}
|
|
|
|
CustomText {
|
|
id: content
|
|
|
|
anchors.left: parent.left
|
|
anchors.leftMargin: Tokens.padding.small
|
|
anchors.top: header.bottom
|
|
anchors.topMargin: Tokens.spacing.small
|
|
anchors.right: parent.right
|
|
color: Colors.palette.m3outline
|
|
font.pointSize: Tokens.font.size.small
|
|
wrapMode: CustomText.WrapAtWordBoundaryOrAnywhere
|
|
text: segment.modelData.type === LlmSegment.Type.Reasoning ? segment.modelData.text : qsTr("Fetched %1").arg(JSON.parse(segment.modelData.arguments)?.url ?? "website")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|