diff --git a/.config/quickshell/modules/sidebarLeft/AiChat.qml b/.config/quickshell/modules/sidebarLeft/AiChat.qml
index d587e2db..689a464e 100644
--- a/.config/quickshell/modules/sidebarLeft/AiChat.qml
+++ b/.config/quickshell/modules/sidebarLeft/AiChat.qml
@@ -82,16 +82,24 @@ Item {
Ai.addMessage("## ✏️ Markdown test\n"
+ "- **Bold**, *Italic*, `Monospace`, [Link](https://example.com)\n\n"
+ "- Table:\n\n"
- + "| | Quickshell | AGS/Astal |\n"
- + "|:-----------|:----------:|:---------:|\n"
- + "| UI Toolkit | Qt | Gtk3/Gtk4 |\n"
- + "| Language | QML | Js/Ts/Lua |\n"
- + "| Reactivity | Implied | Needs declaration |\n"
- + "| Widget placement | Mildly difficult | More intuitive |\n"
- + "| Bluetooth & Wifi support | ❌ | ✅ |\n"
- + "| No-delay keybinds
(hyprland_global_shortcuts_v1) | ✅ | ❌ |\n"
- + "| Development | New APIs | New syntax |\n"
- + "- Code block"
+ + "| | Quickshell | AGS/Astal |\n"
+ + "|:-------------------------|:----------------:|:-----------------:|\n"
+ + "| UI Toolkit | Qt | Gtk3/Gtk4 |\n"
+ + "| Language | QML | Js/Ts/Lua |\n"
+ + "| Reactivity | Implied | Needs declaration |\n"
+ + "| Widget placement | Mildly difficult | More intuitive |\n"
+ + "| Bluetooth & Wifi support | ❌ | ✅ |\n"
+ + "| No-delay keybinds | ✅ | ❌ |\n"
+ + "| Development | New APIs | New syntax |\n"
+ + "- Code block\n"
+ + "```cpp\n"
+ + "#include \n"
+ + "const std::string GREETING = \"UwU\";\n"
+ + "int main(int argc, char* argv[]) {\n"
+ + " std::cout << GREETING;\n"
+ + "}\n"
+ + "```\n"
+
, Ai.interfaceRole);
}
@@ -157,6 +165,15 @@ Item {
easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve
}
}
+ remove: Transition {
+ NumberAnimation {
+ property: "opacity"
+ from: 1; to: 0
+ duration: Appearance.animation.elementMoveEnter.duration
+ easing.type: Appearance.animation.elementMoveEnter.type
+ easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve
+ }
+ }
model: ScriptModel {
values: root.messages
diff --git a/.config/quickshell/modules/sidebarLeft/ApiCommandButton.qml b/.config/quickshell/modules/sidebarLeft/ApiCommandButton.qml
index 25bfdbb8..cd54e8a3 100644
--- a/.config/quickshell/modules/sidebarLeft/ApiCommandButton.qml
+++ b/.config/quickshell/modules/sidebarLeft/ApiCommandButton.qml
@@ -5,7 +5,6 @@ import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
-import Quickshell.Services.Notifications
Button {
id: button
diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml
index bb16cc38..35a10237 100644
--- a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml
+++ b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml
@@ -3,6 +3,7 @@ import "root:/services"
import "root:/modules/common"
import "root:/modules/common/widgets"
import "../"
+import "root:/modules/common/functions/string_utils.js" as StringUtils
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
@@ -15,12 +16,16 @@ import Qt5Compat.GraphicalEffects
Rectangle {
id: root
+ property int messageIndex
property var messageData
property var messageInputField
property real messagePadding: 7
property real contentSpacing: 3
+ property bool renderMarkdown: true
+ property bool editing: false
+
anchors.left: parent?.left
anchors.right: parent?.right
implicitHeight: columnLayout.implicitHeight + root.messagePadding * 2
@@ -38,6 +43,8 @@ Rectangle {
spacing: root.contentSpacing
RowLayout { // Header
+ spacing: 15
+
Rectangle { // Name
id: nameWrapper
color: Appearance.m3colors.m3secondaryContainer
@@ -102,9 +109,10 @@ Rectangle {
Item { Layout.fillWidth: true }
Button { // Not visible to model
+ id: modelVisibilityIndicator
visible: messageData.role == 'interface'
- implicitWidth: Math.max(notVisibleToModelText.implicitWidth + 10 * 2, 30)
- implicitHeight: notVisibleToModelText.implicitHeight + 5 * 2
+ implicitWidth: 16
+ implicitHeight: 30
Layout.alignment: Qt.AlignVCenter
background: Item
@@ -120,21 +128,84 @@ Rectangle {
content: qsTr("Not visible to model")
}
}
+
+ StyledText {
+ visible: modelVisibilityIndicator.visible
+ font.pixelSize: Appearance.font.pixelSize.larger
+ color: Appearance.colors.colOnLayer1
+ text: "•"
+ }
+
+ RowLayout {
+ spacing: 5
+
+ AiMessageControlButton {
+ id: copyButton
+ buttonIcon: "content_copy"
+ onClicked: {
+ Hyprland.dispatch(`exec wl-copy '${StringUtils.shellSingleQuoteEscape(root.messageData.content)}'`)
+ }
+ StyledToolTip {
+ content: qsTr("Copy")
+ }
+ }
+ AiMessageControlButton {
+ id: editButton
+ activated: root.editing
+ buttonIcon: "edit"
+ onClicked: {
+ root.editing = !root.editing
+ if (!root.editing) { // Save changes
+ root.messageData.content = messageText.text
+ }
+ }
+ StyledToolTip {
+ content: root.editing ? qsTr("Save") : qsTr("Edit")
+ }
+ }
+ AiMessageControlButton {
+ id: toggleMarkdownButton
+ activated: !root.renderMarkdown
+ buttonIcon: root.renderMarkdown ? "wysiwyg" : "code"
+ onClicked: {
+ root.renderMarkdown = !root.renderMarkdown
+ if (root.renderMarkdown && messageData.finished) {
+ messageText.text = root.messageData.content
+ }
+ }
+ StyledToolTip {
+ content: qsTr("Toggle Markdown rendering")
+ }
+ }
+ AiMessageControlButton {
+ id: deleteButton
+ buttonIcon: "close"
+ onClicked: {
+ Ai.removeMessage(root.messageIndex)
+ }
+ StyledToolTip {
+ content: qsTr("Delete")
+ }
+ }
+ }
}
TextEdit { // Message
id: messageText
Layout.fillWidth: true
Layout.margins: messagePadding
- readOnly: true
+ readOnly: !root.editing
selectByMouse: true
+ renderType: Text.NativeRendering
font.family: Appearance.font.family.reading
font.hintingPreference: Font.PreferNoHinting // Prevent weird bold text
font.pixelSize: Appearance.font.pixelSize.small
+ selectedTextColor: Appearance.m3colors.m3onPrimary
+ selectionColor: Appearance.m3colors.m3primary
wrapMode: Text.WordWrap
color: messageData.thinking ? Appearance.colors.colSubtext : Appearance.colors.colOnLayer1
- textFormat: Text.MarkdownText
+ textFormat: root.renderMarkdown ? TextEdit.MarkdownText : TextEdit.PlainText
text: messageData.thinking ? qsTr("Waiting for response...") : root.messageData.content
Keys.onPressed: (event) => {
@@ -155,7 +226,7 @@ Rectangle {
anchors.fill: parent
acceptedButtons: Qt.NoButton // Only for hover
hoverEnabled: true
- cursorShape: parent.hoveredLink !== "" ? Qt.PointingHandCursor : Qt.ArrowCursor
+ cursorShape: parent.hoveredLink !== "" ? Qt.PointingHandCursor : Qt.IBeamCursor
}
}
}
diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessageControlButton.qml b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessageControlButton.qml
new file mode 100644
index 00000000..003cc1ba
--- /dev/null
+++ b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessageControlButton.qml
@@ -0,0 +1,34 @@
+import "root:/modules/common"
+import "root:/modules/common/widgets"
+import "root:/services"
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Quickshell
+
+Button {
+ id: button
+ property string buttonIcon
+ property bool activated: false
+
+ implicitHeight: 30
+ implicitWidth: 30
+
+ PointingHandInteraction {}
+
+ background: Rectangle {
+ radius: Appearance.rounding.small
+ color: button.activated ? Appearance.m3colors.m3primary :
+ button.down ? Appearance.colors.colSurfaceContainerHighestActive :
+ button.hovered ? Appearance.colors.colSurfaceContainerHighestHover :
+ Appearance.m3colors.m3surfaceContainerHighest
+ }
+
+ contentItem: MaterialSymbol {
+ horizontalAlignment: Text.AlignHCenter
+ font.pixelSize: Appearance.font.pixelSize.large
+ color: button.activated ? Appearance.m3colors.m3onPrimary :
+ Appearance.m3colors.m3onSurface
+ text: buttonIcon
+ }
+}