372 lines
8.4 KiB
QML
372 lines
8.4 KiB
QML
import QtQuick
|
|
import ZShell.Components
|
|
import ZShell.Config
|
|
import ZShell.Llm
|
|
import qs.Effects
|
|
import qs.Components
|
|
import qs.Services
|
|
|
|
CustomClippingRect {
|
|
id: root
|
|
|
|
property bool expanded: false
|
|
property bool highlighted: false
|
|
required property ChatSession modelData
|
|
|
|
signal clicked(content: ChatSession)
|
|
signal remove(content: ChatSession)
|
|
|
|
color: highlighted ? Qt.alpha(Colors.palette.m3primaryContainer, 0.3) : Qt.alpha(Colors.tPalette.m3surfaceContainer, 0.2)
|
|
implicitHeight: {
|
|
let h = 0;
|
|
|
|
h += infoContainer.implicitHeight;
|
|
|
|
if (expanded)
|
|
h += body.implicitHeight + body.topMargin + actionRow.implicitHeight + actionRow.anchors.topMargin;
|
|
else
|
|
h += preview.implicitHeight + preview.topMargin;
|
|
|
|
h += Tokens.padding.small;
|
|
|
|
const icon = chatIcon.implicitHeight + chatIcon.anchors.topMargin * 2;
|
|
if (h < icon)
|
|
return icon;
|
|
return h;
|
|
}
|
|
radius: Tokens.rounding.large
|
|
clip: true
|
|
|
|
CustomRect {
|
|
id: borderHighlight
|
|
|
|
anchors.fill: parent
|
|
border.width: root.highlighted ? 2 : 0
|
|
border.color: Colors.palette.m3secondary
|
|
radius: root.radius - border.width + 1
|
|
|
|
Behavior on border.width {
|
|
Anim {}
|
|
}
|
|
}
|
|
|
|
Behavior on implicitHeight {
|
|
enabled: titleText.readOnly
|
|
|
|
Anim {}
|
|
}
|
|
|
|
MaterialIcon {
|
|
id: chatIcon
|
|
|
|
anchors.left: parent.left
|
|
anchors.leftMargin: Tokens.padding.large
|
|
anchors.top: parent.top
|
|
anchors.topMargin: Tokens.padding.large
|
|
font.pointSize: Tokens.font.size.extraLarge
|
|
text: root.modelData.icon || "check"
|
|
color: Colors.palette.m3onSurface
|
|
}
|
|
|
|
Item {
|
|
id: infoContainer
|
|
|
|
anchors.left: chatIcon.right
|
|
anchors.leftMargin: Tokens.spacing.medium
|
|
anchors.top: parent.top
|
|
anchors.right: expandBtn.left
|
|
anchors.rightMargin: Tokens.spacing.medium
|
|
implicitHeight: {
|
|
let h = 0;
|
|
h += title.implicitHeight + title.anchors.topMargin;
|
|
h += timestamp.implicitHeight + timestamp.anchors.topMargin;
|
|
if (root.expanded)
|
|
h += created.implicitHeight + created.anchors.topMargin;
|
|
return h;
|
|
}
|
|
|
|
CustomRect {
|
|
id: title
|
|
|
|
anchors.left: parent.left
|
|
anchors.topMargin: Tokens.padding.extraSmall
|
|
anchors.top: parent.top
|
|
color: Colors.layer(Colors.palette.m3surfaceContainerHighest, 1)
|
|
implicitHeight: titleText.implicitHeight + Tokens.padding.extraSmall * 2
|
|
radius: Tokens.rounding.full
|
|
implicitWidth: titleText.implicitWidth + Tokens.padding.large * 2
|
|
opacity: titleText.readOnly ? 0 : 1
|
|
|
|
Behavior on opacity {
|
|
Anim {}
|
|
}
|
|
}
|
|
|
|
Item {
|
|
id: titleWrapper
|
|
|
|
anchors.left: parent.left
|
|
anchors.top: parent.top
|
|
anchors.right: parent.right
|
|
implicitHeight: titleText.implicitHeight + Tokens.padding.small * 2
|
|
|
|
layer.enabled: true
|
|
layer.effect: OpacityMask {
|
|
maskSource: fadeMask
|
|
}
|
|
|
|
TextFieldBase {
|
|
id: titleText
|
|
|
|
anchors.left: parent.left
|
|
anchors.leftMargin: readOnly ? 0 : Tokens.padding.large
|
|
anchors.top: parent.top
|
|
anchors.topMargin: readOnly ? Tokens.padding.small : Tokens.padding.extraSmall * 2
|
|
font.pointSize: Tokens.font.size.normal
|
|
text: root.modelData.title || qsTr("New chat")
|
|
readOnly: true
|
|
color: Colors.palette.m3onSurface
|
|
|
|
Behavior on anchors.leftMargin {
|
|
Anim {}
|
|
}
|
|
Behavior on anchors.topMargin {
|
|
Anim {}
|
|
}
|
|
|
|
onEditingFinished: root.modelData.title = text
|
|
onReadOnlyChanged: {
|
|
if (!readOnly) {
|
|
this.forceActiveFocus();
|
|
cursorPosition = text.length;
|
|
} else {
|
|
root.forceActiveFocus();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
CustomRect {
|
|
id: fadeMask
|
|
|
|
// anchors.top: titleText.top
|
|
// anchors.bottom: titleText.bottom
|
|
// anchors.left: titleText.left
|
|
// anchors.right: parent.right
|
|
anchors.fill: titleWrapper
|
|
|
|
layer.enabled: true
|
|
visible: false
|
|
|
|
gradient: Gradient {
|
|
orientation: Gradient.Horizontal
|
|
|
|
GradientStop {
|
|
color: Qt.rgba(1, 1, 1, 1.0)
|
|
position: 0.85
|
|
}
|
|
|
|
GradientStop {
|
|
color: Qt.rgba(1, 1, 1, 0)
|
|
position: 1.0
|
|
}
|
|
}
|
|
}
|
|
|
|
CustomText {
|
|
id: created
|
|
|
|
anchors.top: title.bottom
|
|
anchors.topMargin: Tokens.spacing.extraSmall
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
font.pointSize: Tokens.font.size.small
|
|
color: Colors.palette.m3onSurfaceVariant
|
|
text: qsTr("Created on: %1").arg(root.modelData.createdAt.toLocaleString(Qt.locale("en_US"), "MMM d, yyyy - h:mm AP"))
|
|
opacity: root.expanded ? 1 : 0
|
|
|
|
Behavior on opacity {
|
|
Anim {}
|
|
}
|
|
}
|
|
|
|
CustomText {
|
|
id: updated
|
|
|
|
anchors.top: created.bottom
|
|
anchors.topMargin: Tokens.spacing.small
|
|
anchors.left: parent.left
|
|
font.pointSize: Tokens.font.size.small
|
|
color: Colors.palette.m3onSurfaceVariant
|
|
text: qsTr("Updated on: ")
|
|
opacity: root.expanded ? 1 : 0
|
|
|
|
Behavior on opacity {
|
|
Anim {}
|
|
}
|
|
}
|
|
|
|
CustomText {
|
|
id: timestamp
|
|
|
|
anchors.left: parent.left
|
|
anchors.top: title.bottom
|
|
anchors.topMargin: Tokens.spacing.extraSmall
|
|
color: Colors.palette.m3onSurfaceVariant
|
|
font.pointSize: Tokens.font.size.small
|
|
text: root.modelData.updatedAt.toLocaleString(Qt.locale("en_US"), "MMM d, yyyy - h:mm AP")
|
|
|
|
states: State {
|
|
name: "expanded"
|
|
when: root.expanded
|
|
|
|
AnchorChanges {
|
|
target: timestamp
|
|
anchors.left: updated.right
|
|
anchors.top: undefined
|
|
anchors.verticalCenter: updated.verticalCenter
|
|
}
|
|
}
|
|
transitions: Transition {
|
|
AnchorAnim {}
|
|
}
|
|
}
|
|
}
|
|
|
|
CustomText {
|
|
id: preview
|
|
|
|
readonly property int topMargin: Tokens.spacing.extraSmall
|
|
|
|
anchors.left: chatIcon.right
|
|
anchors.margins: Tokens.spacing.medium
|
|
anchors.right: parent.right
|
|
y: infoContainer.implicitHeight + topMargin
|
|
elide: Text.ElideRight
|
|
color: Colors.palette.m3onSurfaceVariant
|
|
font.pointSize: Tokens.font.size.small
|
|
maximumLineCount: 1
|
|
opacity: !root.expanded ? 1 : 0
|
|
text: root.modelData.messagesModel.lastMessage?.activeGeneration.content.replace(/\s+/g, " ").trim() ?? qsTr("No messages yet")
|
|
|
|
Behavior on y {
|
|
Anim {}
|
|
}
|
|
Behavior on opacity {
|
|
Anim {
|
|
type: Anim.DefaultEffects
|
|
}
|
|
}
|
|
}
|
|
|
|
CustomText {
|
|
id: body
|
|
|
|
readonly property int topMargin: root.expanded ? Tokens.spacing.medium : Tokens.spacing.extraSmall
|
|
|
|
anchors.left: chatIcon.right
|
|
anchors.margins: Tokens.spacing.medium
|
|
anchors.right: parent.right
|
|
y: infoContainer.implicitHeight + topMargin
|
|
animate: true
|
|
color: Colors.palette.m3onSurfaceVariant
|
|
maximumLineCount: 5
|
|
opacity: root.expanded ? 1 : 0
|
|
text: root.modelData.messagesModel.lastMessage?.activeGeneration.content ?? qsTr("No messages yet")
|
|
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
|
|
|
Behavior on y {
|
|
Anim {}
|
|
}
|
|
Behavior on opacity {
|
|
Anim {
|
|
type: Anim.DefaultEffects
|
|
}
|
|
}
|
|
}
|
|
|
|
StateLayer {
|
|
id: sLayer
|
|
|
|
property int startY
|
|
|
|
onClicked: {
|
|
root.clicked(root.modelData);
|
|
}
|
|
}
|
|
|
|
ButtonRow {
|
|
id: actionRow
|
|
|
|
anchors.top: body.bottom
|
|
anchors.left: chatIcon.right
|
|
anchors.right: parent.right
|
|
anchors.topMargin: Tokens.spacing.small
|
|
anchors.leftMargin: Tokens.spacing.medium
|
|
anchors.rightMargin: Tokens.padding.medium
|
|
spacing: Tokens.spacing.medium
|
|
|
|
IconButton {
|
|
icon: "close"
|
|
fillWidth: true
|
|
shapeMorph: true
|
|
isRound: true
|
|
font.pointSize: Tokens.font.size.large
|
|
inactiveColor: Colors.layer(Colors.palette.m3surfaceContainerHighest, 3)
|
|
inactiveOnColor: Colors.palette.m3onSurfaceVariant
|
|
|
|
onClicked: root.remove(root.modelData)
|
|
}
|
|
|
|
IconButton {
|
|
icon: titleText.readOnly ? "edit" : "check"
|
|
label.animate: true
|
|
isToggle: true
|
|
fillWidth: true
|
|
shapeMorph: true
|
|
font.pointSize: Tokens.font.size.large
|
|
isRound: true
|
|
inactiveColor: Colors.layer(Colors.palette.m3surfaceContainerHighest, 3)
|
|
inactiveOnColor: Colors.palette.m3onSurfaceVariant
|
|
activeColor: Colors.palette.m3secondary
|
|
activeOnColor: Colors.palette.m3onSecondary
|
|
checked: !titleText.readOnly
|
|
|
|
onClicked: titleText.readOnly = !titleText.readOnly
|
|
}
|
|
}
|
|
|
|
CustomRect {
|
|
id: expandBtn
|
|
|
|
anchors.right: parent.right
|
|
anchors.top: parent.top
|
|
anchors.margins: Tokens.padding.small
|
|
implicitHeight: expandIcon.implicitHeight
|
|
implicitWidth: expandIcon.implicitHeight
|
|
radius: Tokens.rounding.full
|
|
color: Colors.layer(Colors.palette.m3surfaceContainerHighest, 3)
|
|
|
|
StateLayer {
|
|
onClicked: root.expanded = !root.expanded
|
|
}
|
|
|
|
MaterialIcon {
|
|
id: expandIcon
|
|
|
|
anchors.centerIn: parent
|
|
anchors.verticalCenterOffset: root.expanded ? -1 : 1
|
|
rotation: root.expanded ? 180 : 0
|
|
color: Colors.palette.m3onSurface
|
|
text: "expand_more"
|
|
|
|
Behavior on anchors.verticalCenterOffset {
|
|
Anim {}
|
|
}
|
|
Behavior on rotation {
|
|
Anim {}
|
|
}
|
|
}
|
|
}
|
|
}
|