pragma ComponentBehavior: Bound import Quickshell import QtQuick import QtQuick.Layouts import ZShell.Llm import ZShell.Config import qs.Services import qs.Components Item { id: root property alias list: list property alias model: list.model signal deleteChatRequest(content: ChatSession) signal loadChatRequest(content: ChatSession, index: int) signal newChatRequest CustomText { id: header anchors.left: parent.left anchors.margins: Tokens.padding.extraSmall anchors.right: parent.right anchors.top: parent.top color: Colors.palette.m3outline font.family: "CaskaydiaCove NF" font.pointSize: 13 font.weight: 500 text: qsTr("%1 Chat%2").arg(list.count).arg(list.count > 1 ? "s" : "") } CustomListView { id: list anchors.bottom: parent.bottom anchors.left: parent.left anchors.right: parent.right anchors.top: header.bottom anchors.topMargin: Tokens.spacing.medium cacheBuffer: height * 2 clip: true spacing: Tokens.spacing.medium delegate: Component { Item { id: chatItem property alias chat: chat property bool closing: false property bool editVisible: false required property int index required property var modelData property real startY: 0.0 anchors.fill: undefined implicitHeight: chat.layout.implicitHeight + chat.layout.anchors.topMargin + chat.layout.anchors.bottomMargin implicitWidth: ListView.view.width onEditVisibleChanged: if (!editVisible) chat.x = 0 ParallelAnimation { id: removeAnim onFinished: root.deleteChatRequest(chatItem.modelData) onStarted: chatItem.closing = true Anim { property: "implicitHeight" target: chatItem to: 0 } Anim { property: "x" target: chat to: -chat.implicitWidth } Anim { property: "opacity" target: chatItem to: 0 } } ChatDelegate { id: chat chatItem: chatItem implicitWidth: chatItem.implicitWidth modelData: chatItem.modelData radius: Tokens.rounding.medium Behavior on x { Anim { } } Behavior on y { Anim { } } StateLayer { cursorShape: drag.active ? Qt.ClosedHandCursor : Qt.PointingHandCursor drag.axis: Drag.XAxis drag.target: chat hoverEnabled: true preventStealing: true onClicked: { if (chatItem.editVisible) chatItem.editVisible = false; else root.loadChatRequest(chatItem.modelData, chatItem.index); } onPressed: event => { chatItem.startY = event.y; } onReleased: event => { if (chat.x > -(editTools.implicitWidth + Tokens.padding.extraSmall)) { chat.x = 0; } else { chatItem.editVisible = true; chat.x = -(editTools.implicitWidth + Tokens.spacing.medium + Tokens.padding.extraSmall); } } } } CustomClippingRect { anchors.left: chat.right anchors.right: parent.right anchors.rightMargin: Tokens.padding.extraSmall anchors.verticalCenter: chat.verticalCenter implicitHeight: editTools.implicitHeight RowLayout { id: editTools anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter IconButton { id: deleteChatBtn font.pointSize: Tokens.font.size.large icon: "close" radius: Tokens.rounding.full transform: [ Scale { property bool shouldBeActive: (chat.x < -deleteChatBtn.implicitWidth) && !chatItem.closing origin.x: deleteChatBtn.implicitWidth / 2 origin.y: deleteChatBtn.implicitWidth / 2 xScale: shouldBeActive ? 1 : 0 yScale: shouldBeActive ? 1 : 0 Behavior on xScale { Anim { } } Behavior on yScale { Anim { } } } ] onClicked: { removeAnim.start(); } } } } } } } IconButton { id: newChatBtn anchors.bottom: parent.bottom anchors.margins: Tokens.padding.small anchors.right: parent.right font.pointSize: Math.round(18 * 1.2) icon: "add" padding: 8 radius: Tokens.rounding.full onClicked: { root.newChatRequest(); } Elevation { anchors.fill: parent level: newChatBtn.stateLayer.containsMouse ? 4 : 3 radius: parent.radius z: -1 } } }