start initial work on detaching chat to floating window

This commit is contained in:
2026-08-20 20:24:54 +02:00
parent 9657e5092a
commit 37c6a9986e
9 changed files with 190 additions and 29 deletions
@@ -11,7 +11,7 @@ import qs.Services
Item {
id: root
required property ChatSession chatData
property ChatSession chatData
property bool following: true
readonly property int messageCount: chatData.messages.length
@@ -257,7 +257,7 @@ Item {
anchors.fill: parent
spacing: Tokens.spacing.small
visible: root.messageCount === 0 && !Chat.busy
visible: (root.messageCount === 0 && !Chat.busy) || !root.chatData
Item {
Layout.fillHeight: true
@@ -273,7 +273,7 @@ Item {
CustomText {
Layout.alignment: Qt.AlignHCenter
color: Colors.tPalette.m3onSurfaceVariant
text: qsTr("No messages yet")
text: !root.chatData ? qsTr("Open a previous chat or start a new one") : qsTr("No messages yet")
}
Item {
@@ -8,6 +8,7 @@ import qs.Services
CustomRect {
id: root
required property Item chatItem
property alias layout: layout
required property ChatSession modelData
@@ -20,6 +21,7 @@ CustomRect {
anchors.left: parent.left
anchors.leftMargin: Tokens.padding.large
anchors.verticalCenter: parent.verticalCenter
color: root.chatItem.ListView.isCurrentItem ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
font.pointSize: Tokens.font.size.extraLarge
text: root.modelData.icon || "check"
}
@@ -48,6 +50,7 @@ CustomRect {
CustomText {
id: title
color: root.chatItem.ListView.isCurrentItem ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
text: root.modelData.title
}
@@ -57,7 +60,7 @@ CustomRect {
anchors.left: title.right
anchors.leftMargin: Tokens.spacing.small
anchors.verticalCenter: title.verticalCenter
color: Colors.palette.m3onSurfaceVariant
color: root.chatItem.ListView.isCurrentItem ? Qt.alpha(Colors.palette.m3onPrimary, 0.9) : Colors.palette.m3onSurfaceVariant
text: "•"
visible: titleContainer.enoughSpace
}
@@ -70,7 +73,7 @@ CustomRect {
anchors.top: titleContainer.enoughSpace ? undefined : title.bottom
anchors.topMargin: Tokens.spacing.extraSmall
anchors.verticalCenter: titleContainer.enoughSpace ? title.verticalCenter : undefined
color: Colors.palette.m3onSurfaceVariant
color: root.chatItem.ListView.isCurrentItem ? Qt.alpha(Colors.palette.m3onPrimary, 0.9) : Colors.palette.m3onSurfaceVariant
font.pointSize: Tokens.font.size.small
text: root.modelData.updatedAt.toLocaleString(Qt.locale("en_US"), "MMM d, yyyy - h:mm AP")
}
@@ -78,7 +81,7 @@ CustomRect {
CustomText {
Layout.fillWidth: true
color: Colors.palette.m3outline
color: root.chatItem.ListView.isCurrentItem ? Qt.alpha(Colors.palette.m3onPrimary, 0.9) : Colors.palette.m3onSurfaceVariant
elide: Text.ElideRight
font.pointSize: Tokens.font.size.small
maximumLineCount: 1
@@ -11,10 +11,11 @@ import qs.Components
Item {
id: root
property alias list: list
property alias model: list.model
signal deleteChatRequest(content: ChatSession)
signal loadChatRequest(content: ChatSession)
signal loadChatRequest(content: ChatSession, index: int)
signal newChatRequest
CustomText {
@@ -47,8 +48,10 @@ Item {
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
@@ -87,6 +90,7 @@ Item {
ChatDelegate {
id: chat
chatItem: chatItem
implicitWidth: chatItem.implicitWidth
modelData: chatItem.modelData
radius: Tokens.rounding.medium
@@ -111,7 +115,7 @@ Item {
if (chatItem.editVisible)
chatItem.editVisible = false;
else
root.loadChatRequest(chatItem.modelData);
root.loadChatRequest(chatItem.modelData, chatItem.index);
}
onPressed: event => {
chatItem.startY = event.y;
@@ -5,6 +5,7 @@ import ZShell.Llm
import QtQuick
import QtQuick.Controls
import Quickshell
import qs.Helpers
import qs.Modules.Notifications.Sidebar
import qs.Components
import qs.Services
@@ -12,26 +13,49 @@ import qs.Services
Item {
id: root
property bool chatOpen: false
required property Props props
anchors.fill: parent
anchors.margins: Tokens.padding.small
Component.onCompleted: {
if (root.props.inChat) {
stack.push(chatList);
stack.push(chatContent, {
"chatData": root.props.chatSession
if (ChatState.inChat) {
stackLoader.item?.push(chatList);
stackLoader.item?.push(chatContent, {
"chatData": ChatState.chatSession
});
} else
stack.push(chatList);
stackLoader.item?.push(chatList);
}
StackView {
id: stack
Loader {
id: stackLoader
active: !ChatState.isWindow || root.width <= (ChatState.screen.width / 4)
anchors.fill: parent
sourceComponent: StackView {
id: stack
}
}
Loader {
id: sidebarViewLoader
active: ChatState.isWindow && root.width > (ChatState.screen.width / 4)
anchors.fill: parent
sourceComponent: SidebarView {
}
}
IconButton {
anchors.right: parent.right
icon: "close"
visible: !ChatState.isWindow
onClicked: {
Detach.create();
Visibilities.getForActive().sidebar = false;
}
}
Component {
@@ -46,19 +70,19 @@ Item {
Chat.chats.remove(chat);
}
onLoadChatRequest: chat => {
stack.push(chatContent, {
stackLoader.item?.push(chatContent, {
"chatData": chat
});
root.props.inChat = true;
root.props.chatSession = chat;
ChatState.inChat = true;
ChatState.chatSession = chat;
}
onNewChatRequest: {
const data = Chat.chats.insert();
stack.push(chatContent, {
stackLoader.item?.push(chatContent, {
"chatData": data
});
root.props.inChat = true;
root.props.chatSession = data;
ChatState.inChat = true;
ChatState.chatSession = data;
}
}
}
@@ -68,8 +92,8 @@ Item {
ChatContent {
onRequestClose: {
stack.pop();
root.props.inChat = false;
stackLoader.item?.pop();
ChatState.inChat = false;
}
}
}
@@ -0,0 +1,14 @@
pragma Singleton
import QtQuick
import Quickshell
import ZShell.Llm
Singleton {
id: root
property ChatSession chatSession
property bool inChat: false
property bool isWindow: false
property ShellScreen screen
}
@@ -0,0 +1,51 @@
pragma Singleton
import Quickshell
import QtQuick
import ZShell.Config
import qs.Services
Singleton {
id: root
function create(parent: Item, props: var): void {
chatComp.createObject(parent ?? dummy, props);
ChatState.isWindow = true;
}
QtObject {
id: dummy
}
Component {
id: chatComp
FloatingWindow {
id: win
property var props
color: Colors.tPalette.m3surface
implicitHeight: chat.implicitHeight
implicitWidth: chat.implicitWidth
minimumSize.height: Config.sidebar.sizes.width
minimumSize.width: Config.sidebar.sizes.width
surfaceFormat.opaque: false
title: qsTr("ZShell - Chat")
Component.onCompleted: ChatState.screen = screen
onVisibleChanged: {
if (!visible) {
destroy();
ChatState.isWindow = false;
}
}
ChatPanel {
id: chat
anchors.fill: parent
}
}
}
}
@@ -0,0 +1,57 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import ZShell.Config
import ZShell.Llm
import qs.Components
import qs.Services
RowLayout {
id: root
ChatList {
id: chatList
Layout.fillHeight: true
Layout.fillWidth: true
Layout.maximumWidth: Config.sidebar.sizes.width
Layout.minimumWidth: Config.sidebar.sizes.width / 2
list.currentIndex: -1
list.highlightFollowsCurrentItem: false
list.highlight: CustomRect {
color: Colors.palette.m3primary
implicitHeight: chatList.list.currentItem?.implicitHeight ?? 0
implicitWidth: chatList.list.width
radius: Tokens.rounding.medium
x: chatList.list.currentItem?.chat.x ?? 0
y: chatList.list.currentItem?.y ?? 0
Behavior on y {
Anim {
duration: Tokens.anim.durations.small
easing: Tokens.anim.expressiveDefaultEffects
}
}
}
model: ScriptModel {
values: Chat.chats.values
}
onLoadChatRequest: (chat, index) => {
chatList.list.currentIndex = index;
chatContent.chatData = chat;
}
}
ChatContent {
id: chatContent
Layout.fillHeight: true
Layout.fillWidth: true
Layout.minimumWidth: Config.sidebar.sizes.width
Layout.preferredWidth: Math.round(Config.sidebar.sizes.width * 1.5)
}
}
+11 -1
View File
@@ -14,6 +14,15 @@ Item {
required property Props props
required property var visibilities
Connections {
function onIsWindowChanged(): void {
if (ChatState.isWindow)
root.props.currentTab = 0;
}
target: ChatState
}
ColumnLayout {
id: layout
@@ -22,8 +31,10 @@ Item {
Tabs {
Layout.fillWidth: true
Layout.preferredHeight: ChatState.isWindow ? 0 : implicitHeight
dashState: root.props
nonAnimWidth: layout.width
visible: height > 0
}
CustomClippingRect {
@@ -75,7 +86,6 @@ Item {
ChatPanel {
anchors.fill: parent
props: root.props
}
}
}
-2
View File
@@ -2,10 +2,8 @@ import Quickshell
import ZShell.Llm
PersistentProperties {
property ChatSession chatSession
property int currentTab: 0
property list<string> expandedNotifs: []
property bool inChat: false
reloadableId: "sidebar"
}