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 { Item {
id: root id: root
required property ChatSession chatData property ChatSession chatData
property bool following: true property bool following: true
readonly property int messageCount: chatData.messages.length readonly property int messageCount: chatData.messages.length
@@ -257,7 +257,7 @@ Item {
anchors.fill: parent anchors.fill: parent
spacing: Tokens.spacing.small spacing: Tokens.spacing.small
visible: root.messageCount === 0 && !Chat.busy visible: (root.messageCount === 0 && !Chat.busy) || !root.chatData
Item { Item {
Layout.fillHeight: true Layout.fillHeight: true
@@ -273,7 +273,7 @@ Item {
CustomText { CustomText {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
color: Colors.tPalette.m3onSurfaceVariant 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 { Item {
@@ -8,6 +8,7 @@ import qs.Services
CustomRect { CustomRect {
id: root id: root
required property Item chatItem
property alias layout: layout property alias layout: layout
required property ChatSession modelData required property ChatSession modelData
@@ -20,6 +21,7 @@ CustomRect {
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Tokens.padding.large anchors.leftMargin: Tokens.padding.large
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
color: root.chatItem.ListView.isCurrentItem ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
font.pointSize: Tokens.font.size.extraLarge font.pointSize: Tokens.font.size.extraLarge
text: root.modelData.icon || "check" text: root.modelData.icon || "check"
} }
@@ -48,6 +50,7 @@ CustomRect {
CustomText { CustomText {
id: title id: title
color: root.chatItem.ListView.isCurrentItem ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface
text: root.modelData.title text: root.modelData.title
} }
@@ -57,7 +60,7 @@ CustomRect {
anchors.left: title.right anchors.left: title.right
anchors.leftMargin: Tokens.spacing.small anchors.leftMargin: Tokens.spacing.small
anchors.verticalCenter: title.verticalCenter anchors.verticalCenter: title.verticalCenter
color: Colors.palette.m3onSurfaceVariant color: root.chatItem.ListView.isCurrentItem ? Qt.alpha(Colors.palette.m3onPrimary, 0.9) : Colors.palette.m3onSurfaceVariant
text: "•" text: "•"
visible: titleContainer.enoughSpace visible: titleContainer.enoughSpace
} }
@@ -70,7 +73,7 @@ CustomRect {
anchors.top: titleContainer.enoughSpace ? undefined : title.bottom anchors.top: titleContainer.enoughSpace ? undefined : title.bottom
anchors.topMargin: Tokens.spacing.extraSmall anchors.topMargin: Tokens.spacing.extraSmall
anchors.verticalCenter: titleContainer.enoughSpace ? title.verticalCenter : undefined 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 font.pointSize: Tokens.font.size.small
text: root.modelData.updatedAt.toLocaleString(Qt.locale("en_US"), "MMM d, yyyy - h:mm AP") text: root.modelData.updatedAt.toLocaleString(Qt.locale("en_US"), "MMM d, yyyy - h:mm AP")
} }
@@ -78,7 +81,7 @@ CustomRect {
CustomText { CustomText {
Layout.fillWidth: true 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 elide: Text.ElideRight
font.pointSize: Tokens.font.size.small font.pointSize: Tokens.font.size.small
maximumLineCount: 1 maximumLineCount: 1
@@ -11,10 +11,11 @@ import qs.Components
Item { Item {
id: root id: root
property alias list: list
property alias model: list.model property alias model: list.model
signal deleteChatRequest(content: ChatSession) signal deleteChatRequest(content: ChatSession)
signal loadChatRequest(content: ChatSession) signal loadChatRequest(content: ChatSession, index: int)
signal newChatRequest signal newChatRequest
CustomText { CustomText {
@@ -47,8 +48,10 @@ Item {
Item { Item {
id: chatItem id: chatItem
property alias chat: chat
property bool closing: false property bool closing: false
property bool editVisible: false property bool editVisible: false
required property int index
required property var modelData required property var modelData
property real startY: 0.0 property real startY: 0.0
@@ -87,6 +90,7 @@ Item {
ChatDelegate { ChatDelegate {
id: chat id: chat
chatItem: chatItem
implicitWidth: chatItem.implicitWidth implicitWidth: chatItem.implicitWidth
modelData: chatItem.modelData modelData: chatItem.modelData
radius: Tokens.rounding.medium radius: Tokens.rounding.medium
@@ -111,7 +115,7 @@ Item {
if (chatItem.editVisible) if (chatItem.editVisible)
chatItem.editVisible = false; chatItem.editVisible = false;
else else
root.loadChatRequest(chatItem.modelData); root.loadChatRequest(chatItem.modelData, chatItem.index);
} }
onPressed: event => { onPressed: event => {
chatItem.startY = event.y; chatItem.startY = event.y;
@@ -5,6 +5,7 @@ import ZShell.Llm
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import Quickshell import Quickshell
import qs.Helpers
import qs.Modules.Notifications.Sidebar import qs.Modules.Notifications.Sidebar
import qs.Components import qs.Components
import qs.Services import qs.Services
@@ -12,26 +13,49 @@ import qs.Services
Item { Item {
id: root id: root
property bool chatOpen: false
required property Props props
anchors.fill: parent anchors.fill: parent
anchors.margins: Tokens.padding.small anchors.margins: Tokens.padding.small
Component.onCompleted: { Component.onCompleted: {
if (root.props.inChat) { if (ChatState.inChat) {
stack.push(chatList); stackLoader.item?.push(chatList);
stack.push(chatContent, { stackLoader.item?.push(chatContent, {
"chatData": root.props.chatSession "chatData": ChatState.chatSession
}); });
} else } else
stack.push(chatList); stackLoader.item?.push(chatList);
} }
StackView { Loader {
id: stack id: stackLoader
active: !ChatState.isWindow || root.width <= (ChatState.screen.width / 4)
anchors.fill: parent 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 { Component {
@@ -46,19 +70,19 @@ Item {
Chat.chats.remove(chat); Chat.chats.remove(chat);
} }
onLoadChatRequest: chat => { onLoadChatRequest: chat => {
stack.push(chatContent, { stackLoader.item?.push(chatContent, {
"chatData": chat "chatData": chat
}); });
root.props.inChat = true; ChatState.inChat = true;
root.props.chatSession = chat; ChatState.chatSession = chat;
} }
onNewChatRequest: { onNewChatRequest: {
const data = Chat.chats.insert(); const data = Chat.chats.insert();
stack.push(chatContent, { stackLoader.item?.push(chatContent, {
"chatData": data "chatData": data
}); });
root.props.inChat = true; ChatState.inChat = true;
root.props.chatSession = data; ChatState.chatSession = data;
} }
} }
} }
@@ -68,8 +92,8 @@ Item {
ChatContent { ChatContent {
onRequestClose: { onRequestClose: {
stack.pop(); stackLoader.item?.pop();
root.props.inChat = false; 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 Props props
required property var visibilities required property var visibilities
Connections {
function onIsWindowChanged(): void {
if (ChatState.isWindow)
root.props.currentTab = 0;
}
target: ChatState
}
ColumnLayout { ColumnLayout {
id: layout id: layout
@@ -22,8 +31,10 @@ Item {
Tabs { Tabs {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: ChatState.isWindow ? 0 : implicitHeight
dashState: root.props dashState: root.props
nonAnimWidth: layout.width nonAnimWidth: layout.width
visible: height > 0
} }
CustomClippingRect { CustomClippingRect {
@@ -75,7 +86,6 @@ Item {
ChatPanel { ChatPanel {
anchors.fill: parent anchors.fill: parent
props: root.props
} }
} }
} }
-2
View File
@@ -2,10 +2,8 @@ import Quickshell
import ZShell.Llm import ZShell.Llm
PersistentProperties { PersistentProperties {
property ChatSession chatSession
property int currentTab: 0 property int currentTab: 0
property list<string> expandedNotifs: [] property list<string> expandedNotifs: []
property bool inChat: false
reloadableId: "sidebar" reloadableId: "sidebar"
} }