diff --git a/Components/CircularIndicator.qml b/Components/CircularIndicator.qml index 1c9a97d..cf28beb 100644 --- a/Components/CircularIndicator.qml +++ b/Components/CircularIndicator.qml @@ -20,7 +20,7 @@ BusyIndicator { property int animState property color bgColor: Colors.palette.m3secondaryContainer property color fgColor: Colors.palette.m3primary - property real implicitSize: Tokens.font.size.normal * 3 + property real implicitSize: Tokens.font.body.medium.pointSize * 3 property real internalStrokeWidth: strokeWidth readonly property alias progress: manager.progress property real strokeWidth: Tokens.padding.extraSmall * 0.8 diff --git a/Components/ScreenState.qml b/Components/ScreenState.qml index f1b303f..38ec504 100644 --- a/Components/ScreenState.qml +++ b/Components/ScreenState.qml @@ -23,7 +23,7 @@ PersistentProperties { property date dashboardDate: new Date() // Sidebar state - property int sidebarTab + property int sidebarTab: 0 property list expandedNotifs: [] // Utilities state diff --git a/Components/TextAreaBase.qml b/Components/TextAreaBase.qml index a96b9c0..5408a2d 100644 --- a/Components/TextAreaBase.qml +++ b/Components/TextAreaBase.qml @@ -8,7 +8,7 @@ TextArea { color: Colors.palette.m3onSurface cursorVisible: !readOnly - font.pointSize: Tokens.font.size.small + font: Tokens.font.body.small implicitHeight: contentHeight + topPadding + bottomPadding implicitWidth: contentWidth + leftPadding + rightPadding placeholderTextColor: Colors.palette.m3onSurfaceVariant // No anim cause placeholder is custom diff --git a/Components/TextEditBase.qml b/Components/TextEditBase.qml index 8eb49a3..e0931f3 100644 --- a/Components/TextEditBase.qml +++ b/Components/TextEditBase.qml @@ -11,22 +11,18 @@ TextEdit { color: Colors.palette.m3onSurface cursorVisible: !readOnly - font.pointSize: Tokens.font.size.small - font.family: Config.appearance.font.family.sans + font: Tokens.font.body.small renderType: TextField.NativeRendering selectedTextColor: color selectionColor: Qt.alpha(Colors.palette.m3primary, 0.4) verticalAlignment: TextInput.AlignVCenter Behavior on color { - CAnim { - } - } - cursorDelegate: Item { + CAnim {} } + cursorDelegate: Item {} Behavior on selectionColor { - CAnim { - } + CAnim {} } CustomRect { diff --git a/Modules/Notifications/Sidebar/Chat/ChatContent.qml b/Modules/Notifications/Sidebar/Chat/ChatContent.qml index e4cbaf2..66cbf63 100644 --- a/Modules/Notifications/Sidebar/Chat/ChatContent.qml +++ b/Modules/Notifications/Sidebar/Chat/ChatContent.qml @@ -455,7 +455,7 @@ Item { MaterialIcon { anchors.centerIn: parent - font.pointSize: Tokens.font.size.large + fontStyle: Tokens.font.icon.large text: thumb.fileIcon(thumb.modelData?.mimeType ?? "") visible: !thumb.modelData?.isImage ?? false } diff --git a/Modules/Notifications/Sidebar/Chat/ChatDelegate.qml b/Modules/Notifications/Sidebar/Chat/ChatDelegate.qml index 5a2613f..b7a6721 100644 --- a/Modules/Notifications/Sidebar/Chat/ChatDelegate.qml +++ b/Modules/Notifications/Sidebar/Chat/ChatDelegate.qml @@ -67,7 +67,7 @@ CustomClippingRect { anchors.leftMargin: Tokens.padding.large anchors.top: parent.top anchors.topMargin: Tokens.padding.large - font.pointSize: Tokens.font.size.extraLarge + fontStyle: Tokens.font.icon.extraLarge text: root.modelData.icon || "check" color: Colors.palette.m3onSurface } @@ -134,7 +134,7 @@ CustomClippingRect { 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 + font: Tokens.font.body.medium text: root.modelData.title || qsTr("New chat") readOnly: true color: Colors.palette.m3onSurface @@ -192,7 +192,7 @@ CustomClippingRect { anchors.topMargin: Tokens.spacing.extraSmall anchors.left: parent.left anchors.right: parent.right - font.pointSize: Tokens.font.size.small + font: Tokens.font.label.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 @@ -208,7 +208,7 @@ CustomClippingRect { anchors.top: created.bottom anchors.topMargin: Tokens.spacing.small anchors.left: parent.left - font.pointSize: Tokens.font.size.small + font: Tokens.font.label.small color: Colors.palette.m3onSurfaceVariant text: qsTr("Updated on: ") opacity: root.expanded ? 1 : 0 @@ -225,7 +225,7 @@ CustomClippingRect { anchors.top: title.bottom anchors.topMargin: Tokens.spacing.extraSmall color: Colors.palette.m3onSurfaceVariant - font.pointSize: Tokens.font.size.small + font: Tokens.font.label.small text: root.modelData.updatedAt.toLocaleString(Qt.locale("en_US"), "MMM d, yyyy - h:mm AP") states: State { @@ -256,7 +256,7 @@ CustomClippingRect { y: infoContainer.implicitHeight + topMargin elide: Text.ElideRight color: Colors.palette.m3onSurfaceVariant - font.pointSize: Tokens.font.size.small + font: Tokens.font.body.small maximumLineCount: 1 opacity: !root.expanded && !root.slim ? 1 : 0 text: root.modelData.messagesModel.lastMessage?.activeGeneration.content.replace(/\s+/g, " ").trim() ?? qsTr("No messages yet") @@ -323,7 +323,7 @@ CustomClippingRect { fillWidth: true shapeMorph: true isRound: true - font.pointSize: Tokens.font.size.large + font: Tokens.font.icon.medium inactiveColor: Colors.layer(Colors.palette.m3surfaceContainerHighest, 3) inactiveOnColor: Colors.palette.m3onSurfaceVariant @@ -336,7 +336,7 @@ CustomClippingRect { isToggle: true fillWidth: true shapeMorph: true - font.pointSize: Tokens.font.size.large + font: Tokens.font.icon.medium isRound: true inactiveColor: Colors.layer(Colors.palette.m3surfaceContainerHighest, 3) inactiveOnColor: Colors.palette.m3onSurfaceVariant diff --git a/Modules/Notifications/Sidebar/Chat/ChatList.qml b/Modules/Notifications/Sidebar/Chat/ChatList.qml index d9a17fe..3b65771 100644 --- a/Modules/Notifications/Sidebar/Chat/ChatList.qml +++ b/Modules/Notifications/Sidebar/Chat/ChatList.qml @@ -156,7 +156,7 @@ Item { anchors.verticalCenter: parent.verticalCenter text: modelsContainer.expanded ? "unfold_less" : "unfold_more" animate: true - font.pointSize: Tokens.font.size.large + fontStyle: Tokens.font.icon.large visible: modelsList.count > 0 } @@ -229,7 +229,7 @@ Item { Layout.alignment: Qt.AlignRight visible: model.selected color: Colors.palette.m3onTertiaryContainer - font.pointSize: Tokens.font.size.normal + fontStyle: Tokens.font.icon.normal opacity: model.selected ? 1 : 0 text: "check" @@ -405,7 +405,7 @@ Item { MaterialIcon { text: fabMenuItem.modelData.icon color: Colors.palette.m3onPrimaryContainer - font.pointSize: Tokens.font.size.large + fontStyle: Tokens.font.icon.large fill: 1 } diff --git a/Modules/Notifications/Sidebar/Chat/ChatState.qml b/Modules/Notifications/Sidebar/Chat/ChatState.qml index df83c8e..0240a94 100644 --- a/Modules/Notifications/Sidebar/Chat/ChatState.qml +++ b/Modules/Notifications/Sidebar/Chat/ChatState.qml @@ -3,7 +3,7 @@ pragma Singleton import QtQuick import Quickshell import ZShell.Llm -import qs.Helpers +import qs.Services Singleton { id: root @@ -20,7 +20,7 @@ Singleton { property bool narrowSidebarExpanded: false property bool windowIsWide: false property ShellScreen screen - property bool sidebarVisible: Visibilities.getForActive().sidebar + property bool sidebarVisible: ShellState.forActive().sidebar onSidebarVisibleChanged: if (!sidebarVisible) fabExpanded = false diff --git a/Modules/Notifications/Sidebar/Chat/Content/Actions.qml b/Modules/Notifications/Sidebar/Chat/Content/Actions.qml index 1d4efdf..aa018d6 100644 --- a/Modules/Notifications/Sidebar/Chat/Content/Actions.qml +++ b/Modules/Notifications/Sidebar/Chat/Content/Actions.qml @@ -29,6 +29,7 @@ Item { enabled: root.message.generationCount > 1 && root.message.activeGenerationIndex !== 0 icon: "chevron_left" type: IconButton.Tonal + isRound: true onClicked: { if (!root.edit.readOnly) @@ -40,8 +41,7 @@ Item { IconButton { enabled: false icon: `${root.message.activeGenerationIndex + 1}` - label.font.family: Config.appearance.font.family.sans - label.font.pointSize: Tokens.font.size.small + label.font: Tokens.font.body.small type: IconButton.Text } @@ -49,6 +49,7 @@ Item { enabled: root.message.generationCount > 1 && root.message.activeGenerationIndex !== root.message.generationCount - 1 icon: "chevron_right" type: IconButton.Tonal + isRound: true onClicked: { if (!root.edit.readOnly) @@ -76,6 +77,7 @@ Item { inactiveOnColor: Colors.palette.m3onSecondary scale: root.edit.readOnly ? 1 : 0 visible: !root.isUser + isRound: true Behavior on scale { Anim {} @@ -91,6 +93,7 @@ Item { inactiveColor: Colors.palette.m3secondary inactiveOnColor: Colors.palette.m3onSecondary scale: root.edit.readOnly ? 1 : 0 + isRound: true Behavior on scale { Anim {} @@ -106,6 +109,7 @@ Item { inactiveColor: Colors.palette.m3tertiary inactiveOnColor: Colors.palette.m3onTertiary visible: root.isUser + isRound: true onClicked: { root.edit.readOnly = !root.edit.readOnly; diff --git a/Modules/Notifications/Sidebar/Chat/Content/BubbleEdit.qml b/Modules/Notifications/Sidebar/Chat/Content/BubbleEdit.qml index 4b96374..a3666f3 100644 --- a/Modules/Notifications/Sidebar/Chat/Content/BubbleEdit.qml +++ b/Modules/Notifications/Sidebar/Chat/Content/BubbleEdit.qml @@ -10,7 +10,7 @@ TextEditBase { color: Colors.palette.m3onSurface readOnly: true textFormat: Text.MarkdownText - font.pointSize: Tokens.font.size.smaller + font: Tokens.font.body.small wrapMode: Text.WrapAtWordBoundaryOrAnywhere onLinkActivated: link => { diff --git a/Modules/Notifications/Sidebar/Chat/Content/ChatHost.qml b/Modules/Notifications/Sidebar/Chat/Content/ChatHost.qml index 9869855..d708ce5 100644 --- a/Modules/Notifications/Sidebar/Chat/Content/ChatHost.qml +++ b/Modules/Notifications/Sidebar/Chat/Content/ChatHost.qml @@ -6,138 +6,138 @@ import qs.Services import qs.Modules.Notifications.Sidebar.Chat Item { - id: root + id: root - property int animOff - property Item currentItem - property bool noAnim: false - property bool loading: false - property int lastIdx: -1 + property int animOff + property Item currentItem + property bool noAnim: false + property bool loading: false + property int lastIdx: -1 - readonly property Component conversationComp: ChatContent {} + readonly property Component conversationComp: ChatContent {} - signal requestClose + signal requestClose - function loadConversation(chat): void { - if (currentItem) { - currentItem.destroy(); - currentItem = null; - } + function loadConversation(chat): void { + if (currentItem) { + currentItem.destroy(); + currentItem = null; + } - if (!chat) - return; + if (!chat) + return; - root.loading = true; + root.loading = true; - const incubator = root.conversationComp.incubateObject(container, { - chatData: chat - }); + const incubator = root.conversationComp.incubateObject(container, { + chatData: chat + }); - const attach = () => { - incubator.object.anchors.fill = container; - incubator.object.requestClose.connect(root.requestClose); - currentItem = incubator.object; - root.loading = false; - if (!noAnim) - enterAnim.start(); - }; + const attach = () => { + incubator.object.anchors.fill = container; + incubator.object.requestClose.connect(root.requestClose); + currentItem = incubator.object; + root.loading = false; + if (!noAnim) + enterAnim.start(); + }; - if (incubator.status === Component.Ready) - attach(); - else - incubator.onStatusChanged = status => { - if (status === Component.Ready) - attach(); - }; - } + if (incubator.status === Component.Ready) + attach(); + else + incubator.onStatusChanged = status => { + if (status === Component.Ready) + attach(); + }; + } - Item { - id: container + Item { + id: container - anchors.fill: parent - layer.enabled: opacity < 1 - objectName: "ConversationContainer" + anchors.fill: parent + layer.enabled: opacity < 1 + objectName: "ConversationContainer" - Component.onCompleted: { - if (ChatState.chatSession) - root.loadConversation(ChatState.chatSession); - } - } + Component.onCompleted: { + if (ChatState.chatSession) + root.loadConversation(ChatState.chatSession); + } + } - LoadingIndicator { - anchors.centerIn: parent - implicitSize: Tokens.font.size.extraLarge * 4 - opacity: root.loading ? 1 : 0 - visible: opacity > 0 + LoadingIndicator { + anchors.centerIn: parent + implicitSize: Tokens.font.icon.extraLarge.pointSize * 4 + opacity: root.loading ? 1 : 0 + visible: opacity > 0 - Behavior on opacity { - Anim {} - } - } + Behavior on opacity { + Anim {} + } + } - Connections { - function onChatSessionChanged(): void { - exitAnim.complete(); - enterAnim.complete(); + Connections { + function onChatSessionChanged(): void { + exitAnim.complete(); + enterAnim.complete(); - if (root.noAnim) { - root.loadConversation(ChatState.chatSession); - return; - } + if (root.noAnim) { + root.loadConversation(ChatState.chatSession); + return; + } - root.animOff = Tokens.padding.small * (ChatState.currentIdx > root.lastIdx ? 1 : -1); - root.lastIdx = ChatState.currentIdx; - exitAnim.start(); - } + root.animOff = Tokens.padding.small * (ChatState.currentIdx > root.lastIdx ? 1 : -1); + root.lastIdx = ChatState.currentIdx; + exitAnim.start(); + } - target: ChatState - } + target: ChatState + } - SequentialAnimation { - id: exitAnim + SequentialAnimation { + id: exitAnim - Anim { - property: "opacity" - target: container - to: 0 - type: Anim.DefaultEffects - } + Anim { + property: "opacity" + target: container + to: 0 + type: Anim.DefaultEffects + } - ScriptAction { - script: root.loadConversation(ChatState.chatSession) - } - } + ScriptAction { + script: root.loadConversation(ChatState.chatSession) + } + } - SequentialAnimation { - id: enterAnim + SequentialAnimation { + id: enterAnim - PropertyAction { - property: "topMargin" - target: container.anchors - value: root.animOff - } + PropertyAction { + property: "topMargin" + target: container.anchors + value: root.animOff + } - PropertyAction { - property: "bottomMargin" - target: container.anchors - value: -root.animOff - } + PropertyAction { + property: "bottomMargin" + target: container.anchors + value: -root.animOff + } - ParallelAnimation { - Anim { - from: 0 - property: "opacity" - target: container - to: 1 - type: Anim.SlowEffects - } + ParallelAnimation { + Anim { + from: 0 + property: "opacity" + target: container + to: 1 + type: Anim.SlowEffects + } - Anim { - properties: "topMargin,bottomMargin" - target: container.anchors - to: 0 - type: Anim.SlowEffects - } - } - } + Anim { + properties: "topMargin,bottomMargin" + target: container.anchors + to: 0 + type: Anim.SlowEffects + } + } + } } diff --git a/Modules/Notifications/Sidebar/Chat/Content/CodeBlockView.qml b/Modules/Notifications/Sidebar/Chat/Content/CodeBlockView.qml index d199797..993c5e8 100644 --- a/Modules/Notifications/Sidebar/Chat/Content/CodeBlockView.qml +++ b/Modules/Notifications/Sidebar/Chat/Content/CodeBlockView.qml @@ -32,8 +32,7 @@ CustomRect { function onHighlightSpans(token, spans) { if (token !== root.highlightToken) return; - if (root.code !== root.requestedCode || - root.language !== root.requestedLanguage) + if (root.code !== root.requestedCode || root.language !== root.requestedLanguage) return; html = spans.length ? root.highlightedHtml(root.code, spans) : ""; colored = root.visible && html.length > 0; @@ -84,15 +83,13 @@ CustomRect { // whitespace kept, mid-line runs collapsed, tabs as single spaces) so the // plain and colored layouts are identical. function plainForDisplay(code): string { - return code.split("\n") - .map(line => { - const i = line.search(/\S/); - if (i < 0) - return ""; - const lead = line.slice(0, i).replace(/\t/g, " "); - return lead + line.slice(i).replace(/\s+/g, " ").replace(/\s+$/, ""); - }) - .join("\n"); + return code.split("\n").map(line => { + const i = line.search(/\S/); + if (i < 0) + return ""; + const lead = line.slice(0, i).replace(/\t/g, " "); + return lead + line.slice(i).replace(/\s+/g, " ").replace(/\s+$/, ""); + }).join("\n"); } function highlightedHtml(code, spans): string { @@ -136,7 +133,7 @@ CustomRect { refreshTimer.restart(); } onVisibleChanged: if (root.visible) - refreshTimer.restart(); + refreshTimer.restart() Timer { id: refreshTimer @@ -223,7 +220,7 @@ CustomRect { Layout.alignment: Qt.AlignVCenter color: root.codeHeaderColor - font.pointSize: Tokens.font.size.small + font: Tokens.font.label.small text: CodeIcons.name(root.language) visible: root.language.length > 0 } @@ -289,8 +286,7 @@ CustomRect { color: CodeColors.active.normal layer.enabled: true clip: false - font.family: Config.appearance.font.family.mono - font.pointSize: Tokens.font.size.small + font: Tokens.font.mono.small textFormat: root.colored ? Text.RichText : Text.PlainText text: root.colored ? root.html : root.plainForDisplay(root.code) readOnly: true diff --git a/Modules/Notifications/Sidebar/Chat/Content/ContentBubble.qml b/Modules/Notifications/Sidebar/Chat/Content/ContentBubble.qml index 86581c4..1c4669c 100644 --- a/Modules/Notifications/Sidebar/Chat/Content/ContentBubble.qml +++ b/Modules/Notifications/Sidebar/Chat/Content/ContentBubble.qml @@ -85,7 +85,7 @@ Item { animateCursor: false color: root.isUser ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface cursor.color: root.isUser ? Colors.palette.m3onPrimary : Colors.palette.m3primary - font.pointSize: Tokens.font.size.smaller + font: Tokens.font.body.small readOnly: true selectionColor: Qt.alpha((root.isUser ? Colors.palette.m3onPrimary : Colors.palette.m3primary), 0.4) text: root.isUser ? root.segment.text : "" diff --git a/Modules/Notifications/Sidebar/Chat/Content/ErrorBubble.qml b/Modules/Notifications/Sidebar/Chat/Content/ErrorBubble.qml index 503df55..f3cfca7 100644 --- a/Modules/Notifications/Sidebar/Chat/Content/ErrorBubble.qml +++ b/Modules/Notifications/Sidebar/Chat/Content/ErrorBubble.qml @@ -93,7 +93,7 @@ Item { Layout.fillWidth: true - font.pointSize: Tokens.font.size.large + font: Tokens.font.headline.small elide: CustomText.ElideRight color: Colors.palette.m3onErrorContainer @@ -120,7 +120,7 @@ Item { isRound: true icon: "close" type: IconButton.Text - font.pointSize: Tokens.font.size.large + font: Tokens.font.icon.medium onClicked: exitAnim.start() } diff --git a/Modules/Notifications/Sidebar/Chat/Content/MarkdownBlocks.qml b/Modules/Notifications/Sidebar/Chat/Content/MarkdownBlocks.qml index 32377ad..877f7e7 100644 --- a/Modules/Notifications/Sidebar/Chat/Content/MarkdownBlocks.qml +++ b/Modules/Notifications/Sidebar/Chat/Content/MarkdownBlocks.qml @@ -62,13 +62,12 @@ Column { text: modelData.text textFormat: Text.MarkdownText readOnly: true - font.bold: true - font.pointSize: { + font: { if (modelData.level <= 1) - return Tokens.font.size.large; + return Tokens.font.title.builders.large.weight(Font.Bold).build(); if (modelData.level === 2) - return Tokens.font.size.normal; - return Tokens.font.size.smaller; + return Tokens.font.title.builders.medium.weight(Font.Bold).build(); + return Tokens.font.title.builders.small.weight(Font.Bold).build(); } wrapMode: Text.WrapAtWordBoundaryOrAnywhere } diff --git a/Modules/Notifications/Sidebar/Chat/Content/MathBlockView.qml b/Modules/Notifications/Sidebar/Chat/Content/MathBlockView.qml index b07d49f..8680312 100644 --- a/Modules/Notifications/Sidebar/Chat/Content/MathBlockView.qml +++ b/Modules/Notifications/Sidebar/Chat/Content/MathBlockView.qml @@ -9,7 +9,7 @@ Item { required property string latex property color mathColor: Colors.palette.m3onSurface - property real fontSize: Tokens.font.size.large + property font font: Tokens.font.headline.medium implicitHeight: Math.max(equationImage.height, fallbackText.contentHeight) + Tokens.padding.small * 2 @@ -18,7 +18,7 @@ Item { latex: root.latex color: root.mathColor - fontPointSize: root.fontSize + fontPointSize: root.font.pointSize devicePixelRatio: Screen.devicePixelRatio } @@ -39,8 +39,7 @@ Item { anchors.centerIn: parent color: root.mathColor - font.family: Config.appearance.font.family.mono - font.pointSize: Tokens.font.size.small + font: Tokens.font.mono.medium text: root.latex wrapMode: Text.WrapAtWordBoundaryOrAnywhere visible: !math.ok diff --git a/Modules/Notifications/Sidebar/Chat/Content/MessageDelegate.qml b/Modules/Notifications/Sidebar/Chat/Content/MessageDelegate.qml index af362f7..6d37c87 100644 --- a/Modules/Notifications/Sidebar/Chat/Content/MessageDelegate.qml +++ b/Modules/Notifications/Sidebar/Chat/Content/MessageDelegate.qml @@ -231,7 +231,7 @@ MouseArea { MaterialIcon { anchors.centerIn: parent - font.pointSize: Tokens.font.size.extraLarge + fontStyle: Tokens.font.icon.extraLarge text: thumb.fileIcon(thumb.modelData?.mimeType ?? "") visible: !thumb.modelData?.isImage ?? false } diff --git a/Modules/Notifications/Sidebar/Chat/Content/ProcessBlock.qml b/Modules/Notifications/Sidebar/Chat/Content/ProcessBlock.qml index 70bff51..4bcfdc7 100644 --- a/Modules/Notifications/Sidebar/Chat/Content/ProcessBlock.qml +++ b/Modules/Notifications/Sidebar/Chat/Content/ProcessBlock.qml @@ -70,7 +70,7 @@ Item { IconButton { id: expandBtn - font.pointSize: Tokens.font.size.large + font: Tokens.font.icon.medium icon: "keyboard_arrow_down" inactiveOnColor: hovered ? Colors.palette.m3onSurface : Colors.palette.m3outline anchors.centerIn: parent @@ -91,7 +91,7 @@ Item { id: collapsedText color: Colors.palette.m3outline - font.pointSize: Tokens.font.size.small + font: Tokens.font.label.small Layout.fillWidth: true Layout.leftMargin: Tokens.spacing.medium text: { @@ -139,7 +139,7 @@ Item { CustomText { id: requestHeader text: qsTr("Tool call request") - font.pointSize: Tokens.font.size.large + font: Tokens.font.headline.medium color: Colors.palette.m3onSurface } @@ -420,7 +420,7 @@ Item { IconButton { id: toolExpandBtn - font.pointSize: Tokens.font.size.large + font: Tokens.font.icon.medium icon: "keyboard_arrow_down" inactiveOnColor: hovered ? Colors.palette.m3onSurface : Colors.palette.m3outline rotation: toolCall.expanded ? 180 : 0 @@ -609,7 +609,7 @@ Item { anchors.topMargin: Tokens.spacing.small anchors.right: parent.right color: Colors.palette.m3outline - font.pointSize: Tokens.font.size.small + font: Tokens.font.body.small wrapMode: CustomText.WrapAtWordBoundaryOrAnywhere text: segment.modelData.type === LlmSegment.Type.Reasoning ? segment.modelData.text : qsTr("%1 %2").arg(content.prettyTool(segment.modelData)).arg(content.toolTarget(segment.modelData)) } diff --git a/Modules/Notifications/Sidebar/Chat/Detach.qml b/Modules/Notifications/Sidebar/Chat/Detach.qml index c0ab82d..128202d 100644 --- a/Modules/Notifications/Sidebar/Chat/Detach.qml +++ b/Modules/Notifications/Sidebar/Chat/Detach.qml @@ -11,7 +11,7 @@ Singleton { function create(parent: Item, props: var): void { chatComp.createObject(parent ?? dummy, props); - Visibilities.getForActive().sidebar = false; + ShellState.forActive().sidebar = false; ChatState.isWindow = true; } diff --git a/Modules/Notifications/Sidebar/Chat/SidebarView.qml b/Modules/Notifications/Sidebar/Chat/SidebarView.qml index 499b669..afc93d9 100644 --- a/Modules/Notifications/Sidebar/Chat/SidebarView.qml +++ b/Modules/Notifications/Sidebar/Chat/SidebarView.qml @@ -16,7 +16,7 @@ Item { property int breakpoint: Math.round(Config.sidebar.sizes.width * 2) + Tokens.spacing.medium * 2 + Tokens.padding.medium * 3 property color blobColor: Colors.tPalette.m3surfaceContainerLow - readonly property int iconSize: Math.round(Tokens.font.size.extraLarge * (96 / 72)) + Tokens.padding.large * 2 + readonly property int iconSize: Math.round(Tokens.font.icon.extraLarge.pointSize * (96 / 72)) + Tokens.padding.large * 2 property alias conversationModel: sidebar.model property bool chatOpen: ChatState.chatSession !== null @@ -174,7 +174,7 @@ Item { MaterialIcon { Layout.alignment: Qt.AlignCenter text: "chat_bubble_off" - font.pointSize: Tokens.font.size.extraLarge + fontStyle: Tokens.font.icon.extraLarge color: Colors.tPalette.m3onSurfaceVariant } diff --git a/Modules/Notifications/Sidebar/Content.qml b/Modules/Notifications/Sidebar/Content.qml index e72853c..1a6cea8 100644 --- a/Modules/Notifications/Sidebar/Content.qml +++ b/Modules/Notifications/Sidebar/Content.qml @@ -15,7 +15,7 @@ Item { Connections { function onIsWindowChanged(): void { if (ChatState.isWindow) - root.screenState.dashboardTab = 0; + root.screenState.sidebarTab = 0; } target: ChatState @@ -46,9 +46,9 @@ Item { anchors.bottom: parent.bottom anchors.top: parent.top implicitWidth: parent.width - opacity: root.screenState.dashboardTab === 0 ? 1 : 0 + opacity: root.screenState.sidebarTab === 0 ? 1 : 0 visible: opacity > 0 - x: root.screenState.dashboardTab === 0 ? 0 : -root.width + x: root.screenState.sidebarTab === 0 ? 0 : -root.width Behavior on opacity { Anim {} @@ -80,9 +80,9 @@ Item { sourceComponent: Item { id: chatPage - opacity: root.screenState.dashboardTab === 1 ? 1 : 0 + opacity: root.screenState.sidebarTab === 1 ? 1 : 0 visible: opacity > 0 - x: root.screenState.dashboardTab === 0 ? root.width : 0 + x: root.screenState.sidebarTab === 0 ? root.width : 0 z: 1 Behavior on opacity { diff --git a/Modules/Notifications/Sidebar/Tabs.qml b/Modules/Notifications/Sidebar/Tabs.qml index d650550..3dbd448 100644 --- a/Modules/Notifications/Sidebar/Tabs.qml +++ b/Modules/Notifications/Sidebar/Tabs.qml @@ -24,7 +24,7 @@ Item { anchors.right: parent.right anchors.top: parent.top background: null - currentIndex: root.screenState.dashboardTab + currentIndex: root.screenState.sidebarTab implicitHeight: contentHeight contentItem: RowLayout { @@ -108,7 +108,7 @@ Item { color: tab.current ? Colors.palette.m3onPrimary : Colors.palette.m3onSurface radius: Tokens.rounding.small - onClicked: root.screenState.dashboardTab = tab.TabBar.index + onClicked: root.screenState.sidebarTab = tab.TabBar.index } MaterialIcon { @@ -118,7 +118,7 @@ Item { anchors.horizontalCenter: parent.horizontalCenter color: tab.current ? Colors.palette.m3primary : Colors.palette.m3onSurfaceVariant fill: tab.current ? 1 : 0 - font.pointSize: 18 + fontStyle: Tokens.font.icon.large text: tab.iconName Behavior on fill { diff --git a/Modules/Settings/Common/DialogSelectButton.qml b/Modules/Settings/Common/DialogSelectButton.qml index ad133f4..c9bf846 100644 --- a/Modules/Settings/Common/DialogSelectButton.qml +++ b/Modules/Settings/Common/DialogSelectButton.qml @@ -12,7 +12,6 @@ DialogRowButton { property var initialSelect property var selectedItem - property var initialSelect function keyFor(item: var): string { return item.id; diff --git a/Modules/Settings/Pages/Panels/Bar/BarStatusIcons.qml b/Modules/Settings/Pages/Panels/Bar/BarStatusIcons.qml index 73f2eaa..7b0aea9 100644 --- a/Modules/Settings/Pages/Panels/Bar/BarStatusIcons.qml +++ b/Modules/Settings/Pages/Panels/Bar/BarStatusIcons.qml @@ -57,7 +57,6 @@ PageBase { icon: "add" last: true label: qsTr("Add entry") - last: true model: { const present = new Set(Config.bar.tray.statusIcons.values.map(item => item.id)); return Object.keys(root.builtinIcons).filter(id => !present.has(id)).map(id => ({ diff --git a/Plugins/ZShell/CMakeLists.txt b/Plugins/ZShell/CMakeLists.txt index 8cecf26..7d1bbaf 100644 --- a/Plugins/ZShell/CMakeLists.txt +++ b/Plugins/ZShell/CMakeLists.txt @@ -1,5 +1,6 @@ qml_module(ZShell URI ZShell + INCLUDE_PREFIX core SOURCES writefile.hpp writefile.cpp appdb.hpp appdb.cpp diff --git a/Plugins/ZShell/Config/llm.hpp b/Plugins/ZShell/Config/llm.hpp index bdc5742..aabd24e 100644 --- a/Plugins/ZShell/Config/llm.hpp +++ b/Plugins/ZShell/Config/llm.hpp @@ -1,36 +1,29 @@ #pragma once -#include "configobject.hpp" + #include #include #include +#include "common.hpp" + namespace ZShell::config { -class LlmAppearance : public ConfigObject { - Q_OBJECT - QML_ANONYMOUS +class LlmAppearance : public settings::ObjectNode { + CONFIG_NODE(LlmAppearance, settings::ObjectNode) - CFG_PROPERTY(QString, scheme, QStringLiteral("tokyoNight")) - - public: - explicit LlmAppearance(QObject* parent = nullptr) : ConfigObject(parent) {} + CONFIG_PROPERTY(QString, scheme, QStringLiteral("tokyoNight")) }; -class Llm : public ConfigObject { - Q_OBJECT - QML_ANONYMOUS +class Llm : public settings::ObjectNode { + CONFIG_NODE(Llm, settings::ObjectNode) - CFG_PROPERTY(QString, endpoint, "http://localhost:8080") - CFG_PROPERTY(QString, model, "") - CFG_PROPERTY(double, temperature, 0.7) - CFG_PROPERTY(bool, tools, true) - CFG_PROPERTY(bool, sandbox, true) - CFG_PROPERTY(bool, enabled, true) + CONFIG_PROPERTY(QString, endpoint, "http://localhost:8080") + CONFIG_PROPERTY(QString, model, "") + CONFIG_PROPERTY(double, temperature, 0.7) + CONFIG_PROPERTY(bool, tools, true) + CONFIG_PROPERTY(bool, sandbox, true) + CONFIG_PROPERTY(bool, enabled, true) CONFIG_SUBOBJECT(LlmAppearance, appearance) - - public: - explicit Llm(QObject* parent = nullptr) - : ConfigObject(parent), m_appearance(new LlmAppearance(this)) {} }; } // namespace ZShell::config diff --git a/Plugins/ZShell/Config/rootnodes.hpp b/Plugins/ZShell/Config/rootnodes.hpp index bbbe3c5..308e90a 100644 --- a/Plugins/ZShell/Config/rootnodes.hpp +++ b/Plugins/ZShell/Config/rootnodes.hpp @@ -16,6 +16,7 @@ #include "dock.hpp" #include "general.hpp" #include "launcher.hpp" +#include "llm.hpp" #include "lock.hpp" #include "notifs.hpp" #include "osd.hpp" @@ -42,6 +43,7 @@ class ConfigRoot : public settings::RootNode { CONFIG_SUBOBJECT(Dock, dock) CONFIG_SUBOBJECT(General, general) CONFIG_SUBOBJECT(Launcher, launcher) + CONFIG_SUBOBJECT(Llm, llm) CONFIG_SUBOBJECT(Lock, lock) CONFIG_SUBOBJECT(Notifs, notifs) CONFIG_SUBOBJECT(Osd, osd) diff --git a/Plugins/ZShell/Llm/CMakeLists.txt b/Plugins/ZShell/Llm/CMakeLists.txt index 6433ee0..5501b34 100644 --- a/Plugins/ZShell/Llm/CMakeLists.txt +++ b/Plugins/ZShell/Llm/CMakeLists.txt @@ -1,38 +1,14 @@ -# cmark-gfm: markdown -> block structure (pkg-config; no CMake config -# package is installed). pkg_check_modules(CMARK_GFM REQUIRED IMPORTED_TARGET libcmark-gfm) -# tree-sitter runtime for code block highlighting (grammars are -# dlopen()'d at runtime and optional). pkg_check_modules(TREE_SITTER REQUIRED IMPORTED_TARGET tree-sitter) -# JKQTMathText (JKQtPlotter) for LaTeX rendering. The config files live -# in a shared JKQTPlotter6 directory, not one named after the package. find_path(JKQTPlotter6_CMAKE_DIR NAMES JKQTMathText6Config.cmake HINTS /usr/lib/cmake/JKQTPlotter6 /usr/local/lib/cmake/JKQTPlotter6 DOC "Directory containing the JKQtPlotter cmake package files") find_package(JKQTMathText6 REQUIRED PATHS "${JKQTPlotter6_CMAKE_DIR}") -# --- tree-sitter grammar discovery (configure time) --- -# -# Discover installed tree-sitter grammars — system packages -# (libtree-sitter-.so) and the parsers Neovim's nvim-treesitter -# installs (~/.local/share/nvim/site/parser/*.so) — and pair each with -# highlight queries. Query sources, in priority order: -# 1. vendored highlight-queries/*.scm (version-pinned; see the -# per-file source headers, MIT), -# 2. Neovim's own queries (version-matched to its parsers), -# 3. tree-sitter/highlighting from GitHub (cached in the build dir). -# The result is embedded as highlight-queries.hpp. Re-run cmake to pick -# up grammars installed later. -# -# Candidate entry points are read from the .so with `nm` rather than -# assumed to be tree_sitter_; a missing entry point just makes that -# candidate fail at runtime. function(_ts_entry_point out file) - # OUTPUT_VARIABLE + OUTPUT_QUIET loses the output on CMake 4, so - # capture through a temp file. get_filename_component(_ts_nm_base "${file}" NAME) set(_ts_nm_file "${CMAKE_CURRENT_BINARY_DIR}/ts-entry-${_ts_nm_base}") execute_process( @@ -44,8 +20,6 @@ function(_ts_entry_point out file) if(_ts_nm_rc EQUAL 0 AND EXISTS "${_ts_nm_file}") file(READ "${_ts_nm_file}" _ts_nm_out) file(REMOVE "${_ts_nm_file}" "${_ts_nm_file}.err") - # The library also exports the external scanner functions; the - # entry point is the one without the _external suffix. string(REGEX MATCHALL " T tree_sitter_[A-Za-z0-9_]+" _ts_syms "${_ts_nm_out}") foreach(_ts_s IN LISTS _ts_syms) string(REPLACE " T " "" _ts_s "${_ts_s}") @@ -58,22 +32,11 @@ function(_ts_entry_point out file) set(${out} "${_ts_sym}" PARENT_SCOPE) endfunction() -# Append a query text to (PARENT_SCOPE) unless it duplicates a -# hash in . The text is written to the build tree right away: -# the s-expression `;` comments would split CMake list items and the -# query `(` `)` break bracket arguments, so query text only ever lives -# in variables and files, never in lists. -# Expand Neovim's "; inherits:" directives by appending the inherited -# query set's highlights file (recursively; a visited set prevents -# cycles). Several languages are stubs that inherit the real query -# (html inherits html_tags, qmljs inherits ecma, ...). function(_ts_expand_inherits query_dir text outVar) set(result "${text}") set(_visited "") set(_depth 0) while(_depth LESS 8) - # Do not match the leading `;`: a MATCHALL result that itself - # contains a semicolon is re-split into list items. string(REGEX MATCHALL "inherits:[ \t]*[A-Za-z0-9_]+" _inh "${result}") if(NOT _inh) break() @@ -99,7 +62,6 @@ function(_ts_expand_inherits query_dir text outVar) endfunction() function(_ts_add_query sid text filesVar hashesVar) - # filesVar/hashesVar hold the caller's variable names. set(files "${${filesVar}}") set(hashes "${${hashesVar}}") string(SHA256 _ts_qh "${text}") @@ -115,7 +77,7 @@ function(_ts_add_query sid text filesVar hashesVar) set(${hashesVar} "${hashes}" PARENT_SCOPE) endfunction() -set(_ts_candidates "") # entries: ||| +set(_ts_candidates "") file(GLOB _ts_sys_files "/usr/lib/libtree-sitter-*.so" "/usr/local/lib/libtree-sitter-*.so") foreach(_ts_file IN LISTS _ts_sys_files) @@ -164,20 +126,13 @@ set(_ts_vendored foreach(_ts_id IN LISTS _ts_ids) string(REPLACE "-" "_" _ts_sid "${_ts_id}") - # Query candidates in priority order (deduped; identical texts are - # skipped, the nvim site and plugin copies are usually the same - # file). set(_ts_qfiles "") set(_ts_qhashes "") if(_ts_id STREQUAL "cpp") - # The C++ grammar is a superset of C and its query only covers - # the C++ delta; base C coverage comes from the C query. file(READ "${CMAKE_CURRENT_SOURCE_DIR}/highlight-queries/c.scm" _ts_qa) file(READ "${CMAKE_CURRENT_SOURCE_DIR}/highlight-queries/cpp.scm" _ts_qb) _ts_add_query(${_ts_sid} "${_ts_qa}\n${_ts_qb}" _ts_qfiles _ts_qhashes) elseif(_ts_id STREQUAL "typescript" OR _ts_id STREQUAL "tsx") - # The TS grammars reuse the JS node names; the JS query usually - # compiles against them and gives full coverage. file(READ "${CMAKE_CURRENT_SOURCE_DIR}/highlight-queries/javascript.scm" _ts_qa) file(READ "${CMAKE_CURRENT_SOURCE_DIR}/highlight-queries/typescript.scm" _ts_qb) _ts_add_query(${_ts_sid} "${_ts_qa}\n${_ts_qb}" _ts_qfiles _ts_qhashes) @@ -195,10 +150,6 @@ foreach(_ts_id IN LISTS _ts_ids) endif() endforeach() if(NOT _ts_qfiles) - # Last resort: fetch from the tree-sitter/highlighting repo. - # Version-skewed against locally installed grammars, so only - # used when nothing local exists. Cached; offline builds simply - # drop the language. set(_ts_dl "${CMAKE_CURRENT_BINARY_DIR}/ts-query-downloads/${_ts_sid}.scm") if(NOT EXISTS "${_ts_dl}") file(DOWNLOAD @@ -219,7 +170,6 @@ foreach(_ts_id IN LISTS _ts_ids) continue() endif() - # Emit query sources. set(_ts_qn 0) set(_ts_q_ptrs "") foreach(_ts_qfile IN LISTS _ts_qfiles) @@ -232,7 +182,6 @@ foreach(_ts_id IN LISTS _ts_ids) string(APPEND _hl_header "inline constexpr const char* const q_${_ts_sid}[] = { ${_ts_q_ptrs} };\n") - # Emit library candidates (system package first, then Neovim). string(APPEND _hl_header "inline constexpr Candidate cand_${_ts_sid}[] = {\n") set(_ts_nc 0) foreach(_ts_c IN LISTS _ts_candidates) @@ -248,8 +197,7 @@ foreach(_ts_id IN LISTS _ts_ids) endforeach() string(APPEND _hl_header "};\n") if(_ts_nc EQUAL 0) - # Queries but no library: pointless, drop the language. - string(APPEND _hl_header "") # (arrays stay; grammar row is skipped) + string(APPEND _hl_header "") continue() endif() @@ -263,8 +211,6 @@ endforeach() string(APPEND _hl_header "};\n}\n") file(WRITE "${HIGHLIGHT_QUERIES_HPP}" "${_hl_header}") -# Embed the vendored Latin Modern fonts (GUST Font License; provenance -# in fonts/latinmodern/GUST-FONT-LICENSE.txt) as byte arrays. set(LM_FONTS lmroman10-regular lmroman10-italic @@ -314,4 +260,3 @@ qml_module(ZShell-llm ) target_include_directories(ZShell-llm PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) -target_include_directories(ZShell-llm PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../Config) diff --git a/Plugins/ZShell/Llm/chat.cpp b/Plugins/ZShell/Llm/chat.cpp index 9e28bed..4440386 100644 --- a/Plugins/ZShell/Llm/chat.cpp +++ b/Plugins/ZShell/Llm/chat.cpp @@ -1,12 +1,13 @@ #include "chat.hpp" -#include "config.hpp" #include "filetool.hpp" -#include "llm.hpp" #include "llmclient.hpp" #include "shelltool.hpp" #include "webfetchtool.hpp" +#include "config/rootnodes.hpp" +#include "config/llm.hpp" + #include namespace ZShell::llm { @@ -15,14 +16,12 @@ Chat::Chat(QObject* parent) : QObject(parent) , m_store(new ChatStore(this)) , m_client(new LlmClient(this)) { - if (!config::Config::instance()) new config::Config(); - m_store->setLlmClient(m_client); m_client->tools()->registerTool(new WebFetchTool(m_client->tools())); m_client->tools()->registerTool(new FileReadTool(m_client->tools())); m_client->tools()->registerTool(new ShellTool(m_client, m_client->tools())); - const auto* llm = config::Config::instance()->llm(); + const auto* llm = config::ConfigSingleton::instance()->llm(); m_client->setEndpoint(llm->endpoint()); m_client->setModel(llm->model()); m_client->setTemperature(llm->temperature()); @@ -144,7 +143,7 @@ bool Chat::toolsEnabled() const { void Chat::setToolsEnabled(bool value) { m_client->setToolsEnabled(value); - if (auto* config = config::Config::instance()) + if (auto* config = config::ConfigSingleton::instance()) config->llm()->set_tools(value); } @@ -154,7 +153,7 @@ bool Chat::sandboxEnabled() const { void Chat::setSandboxEnabled(bool value) { m_client->setSandboxEnabled(value); - if (auto* config = config::Config::instance()) + if (auto* config = config::ConfigSingleton::instance()) config->llm()->set_sandbox(value); } @@ -195,7 +194,8 @@ void Chat::refreshFromServer() { void Chat::selectModel(const QString& id) { if (id.isEmpty()) return; m_client->setModel(id); - if (auto* config = config::Config::instance()) config->llm()->set_model(id); + if (auto* config = config::ConfigSingleton::instance()) + config->llm()->set_model(id); } } // namespace ZShell::llm