forked from Shinonome/dots-hyprland
ai chat: action buttons
copy, edit, toggle markdown rendering, delete
This commit is contained in:
@@ -82,16 +82,24 @@ Item {
|
|||||||
Ai.addMessage("## ✏️ Markdown test\n"
|
Ai.addMessage("## ✏️ Markdown test\n"
|
||||||
+ "- **Bold**, *Italic*, `Monospace`, [Link](https://example.com)\n\n"
|
+ "- **Bold**, *Italic*, `Monospace`, [Link](https://example.com)\n\n"
|
||||||
+ "- Table:\n\n"
|
+ "- Table:\n\n"
|
||||||
+ "| | Quickshell | AGS/Astal |\n"
|
+ "| | Quickshell | AGS/Astal |\n"
|
||||||
+ "|:-----------|:----------:|:---------:|\n"
|
+ "|:-------------------------|:----------------:|:-----------------:|\n"
|
||||||
+ "| UI Toolkit | Qt | Gtk3/Gtk4 |\n"
|
+ "| UI Toolkit | Qt | Gtk3/Gtk4 |\n"
|
||||||
+ "| Language | QML | Js/Ts/Lua |\n"
|
+ "| Language | QML | Js/Ts/Lua |\n"
|
||||||
+ "| Reactivity | Implied | Needs declaration |\n"
|
+ "| Reactivity | Implied | Needs declaration |\n"
|
||||||
+ "| Widget placement | Mildly difficult | More intuitive |\n"
|
+ "| Widget placement | Mildly difficult | More intuitive |\n"
|
||||||
+ "| Bluetooth & Wifi support | ❌ | ✅ |\n"
|
+ "| Bluetooth & Wifi support | ❌ | ✅ |\n"
|
||||||
+ "| No-delay keybinds<br/><sup>(hyprland_global_shortcuts_v1)</sup> | ✅ | ❌ |\n"
|
+ "| No-delay keybinds | ✅ | ❌ |\n"
|
||||||
+ "| Development | New APIs | New syntax |\n"
|
+ "| Development | New APIs | New syntax |\n"
|
||||||
+ "- Code block"
|
+ "- Code block\n"
|
||||||
|
+ "```cpp\n"
|
||||||
|
+ "#include <bits/stdc++.h>\n"
|
||||||
|
+ "const std::string GREETING = \"UwU\";\n"
|
||||||
|
+ "int main(int argc, char* argv[]) {\n"
|
||||||
|
+ " std::cout << GREETING;\n"
|
||||||
|
+ "}\n"
|
||||||
|
+ "```\n"
|
||||||
|
|
||||||
|
|
||||||
, Ai.interfaceRole);
|
, Ai.interfaceRole);
|
||||||
}
|
}
|
||||||
@@ -157,6 +165,15 @@ Item {
|
|||||||
easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve
|
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 {
|
model: ScriptModel {
|
||||||
values: root.messages
|
values: root.messages
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import QtQuick
|
|||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Services.Notifications
|
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
id: button
|
id: button
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import "root:/services"
|
|||||||
import "root:/modules/common"
|
import "root:/modules/common"
|
||||||
import "root:/modules/common/widgets"
|
import "root:/modules/common/widgets"
|
||||||
import "../"
|
import "../"
|
||||||
|
import "root:/modules/common/functions/string_utils.js" as StringUtils
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
@@ -15,12 +16,16 @@ import Qt5Compat.GraphicalEffects
|
|||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
|
property int messageIndex
|
||||||
property var messageData
|
property var messageData
|
||||||
property var messageInputField
|
property var messageInputField
|
||||||
|
|
||||||
property real messagePadding: 7
|
property real messagePadding: 7
|
||||||
property real contentSpacing: 3
|
property real contentSpacing: 3
|
||||||
|
|
||||||
|
property bool renderMarkdown: true
|
||||||
|
property bool editing: false
|
||||||
|
|
||||||
anchors.left: parent?.left
|
anchors.left: parent?.left
|
||||||
anchors.right: parent?.right
|
anchors.right: parent?.right
|
||||||
implicitHeight: columnLayout.implicitHeight + root.messagePadding * 2
|
implicitHeight: columnLayout.implicitHeight + root.messagePadding * 2
|
||||||
@@ -38,6 +43,8 @@ Rectangle {
|
|||||||
spacing: root.contentSpacing
|
spacing: root.contentSpacing
|
||||||
|
|
||||||
RowLayout { // Header
|
RowLayout { // Header
|
||||||
|
spacing: 15
|
||||||
|
|
||||||
Rectangle { // Name
|
Rectangle { // Name
|
||||||
id: nameWrapper
|
id: nameWrapper
|
||||||
color: Appearance.m3colors.m3secondaryContainer
|
color: Appearance.m3colors.m3secondaryContainer
|
||||||
@@ -102,9 +109,10 @@ Rectangle {
|
|||||||
Item { Layout.fillWidth: true }
|
Item { Layout.fillWidth: true }
|
||||||
|
|
||||||
Button { // Not visible to model
|
Button { // Not visible to model
|
||||||
|
id: modelVisibilityIndicator
|
||||||
visible: messageData.role == 'interface'
|
visible: messageData.role == 'interface'
|
||||||
implicitWidth: Math.max(notVisibleToModelText.implicitWidth + 10 * 2, 30)
|
implicitWidth: 16
|
||||||
implicitHeight: notVisibleToModelText.implicitHeight + 5 * 2
|
implicitHeight: 30
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
|
||||||
background: Item
|
background: Item
|
||||||
@@ -120,21 +128,84 @@ Rectangle {
|
|||||||
content: qsTr("Not visible to model")
|
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
|
TextEdit { // Message
|
||||||
id: messageText
|
id: messageText
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.margins: messagePadding
|
Layout.margins: messagePadding
|
||||||
readOnly: true
|
readOnly: !root.editing
|
||||||
selectByMouse: true
|
selectByMouse: true
|
||||||
|
|
||||||
|
renderType: Text.NativeRendering
|
||||||
font.family: Appearance.font.family.reading
|
font.family: Appearance.font.family.reading
|
||||||
font.hintingPreference: Font.PreferNoHinting // Prevent weird bold text
|
font.hintingPreference: Font.PreferNoHinting // Prevent weird bold text
|
||||||
font.pixelSize: Appearance.font.pixelSize.small
|
font.pixelSize: Appearance.font.pixelSize.small
|
||||||
|
selectedTextColor: Appearance.m3colors.m3onPrimary
|
||||||
|
selectionColor: Appearance.m3colors.m3primary
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
color: messageData.thinking ? Appearance.colors.colSubtext : Appearance.colors.colOnLayer1
|
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
|
text: messageData.thinking ? qsTr("Waiting for response...") : root.messageData.content
|
||||||
|
|
||||||
Keys.onPressed: (event) => {
|
Keys.onPressed: (event) => {
|
||||||
@@ -155,7 +226,7 @@ Rectangle {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
acceptedButtons: Qt.NoButton // Only for hover
|
acceptedButtons: Qt.NoButton // Only for hover
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: parent.hoveredLink !== "" ? Qt.PointingHandCursor : Qt.ArrowCursor
|
cursorShape: parent.hoveredLink !== "" ? Qt.PointingHandCursor : Qt.IBeamCursor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user