forked from Shinonome/dots-hyprland
ai chat: better code snippets
This commit is contained in:
@@ -32,4 +32,8 @@ function splitMarkdownBlocks(markdown) {
|
|||||||
result.push({ type: "text", content: markdown.slice(lastIndex) });
|
result.push({ type: "text", content: markdown.slice(lastIndex) });
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function unEscapeBackslashes(str) {
|
||||||
|
return str.replace(/\\\\/g, '\\');
|
||||||
}
|
}
|
||||||
@@ -79,29 +79,45 @@ Item {
|
|||||||
name: "test",
|
name: "test",
|
||||||
description: qsTr("Markdown test"),
|
description: qsTr("Markdown test"),
|
||||||
execute: () => {
|
execute: () => {
|
||||||
Ai.addMessage("## ✏️ Markdown test\n"
|
Ai.addMessage(`
|
||||||
+ "- **Bold**, *Italic*, `Monospace`, [Link](https://example.com)\n\n"
|
## ✏️ Markdown test
|
||||||
+ "- Table:\n\n"
|
### Formatting
|
||||||
+ "| | 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 <bits/stdc++.h>\n"
|
|
||||||
+ "const std::string GREETING = \"UwU\";\n"
|
|
||||||
+ "int main(int argc, char* argv[]) {\n"
|
|
||||||
+ " std::cout << GREETING;\n"
|
|
||||||
+ "}\n"
|
|
||||||
+ "```\n"
|
|
||||||
|
|
||||||
|
*Italic*, \`Monospace\`, **Bold**, [Link](https://example.com)
|
||||||
|
|
||||||
, Ai.interfaceRole);
|
### Table
|
||||||
|
|
||||||
|
Quickshell vs AGS/Astal
|
||||||
|
|
||||||
|
| | Quickshell | AGS/Astal |
|
||||||
|
|--------------------------|------------------|-------------------|
|
||||||
|
| UI Toolkit | Qt | Gtk3/Gtk4 |
|
||||||
|
| Language | QML | Js/Ts/Lua |
|
||||||
|
| Reactivity | Implied | Needs declaration |
|
||||||
|
| Widget placement | Mildly difficult | More intuitive |
|
||||||
|
| Bluetooth & Wifi support | ❌ | ✅ |
|
||||||
|
| No-delay keybinds | ✅ | ❌ |
|
||||||
|
| Development | New APIs | New syntax |
|
||||||
|
|
||||||
|
### Code block
|
||||||
|
|
||||||
|
Just a hello world...
|
||||||
|
|
||||||
|
\`\`\`cpp
|
||||||
|
#include <bits/stdc++.h>
|
||||||
|
// This is intentionally very long to test scrolling
|
||||||
|
const std::string GREETING = \"UwU\";
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
std::cout << GREETING;
|
||||||
|
}
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### LaTeX
|
||||||
|
|
||||||
|
Inline: $$\\int_0^\\infty e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}$$
|
||||||
|
|
||||||
|
`,
|
||||||
|
Ai.interfaceRole);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import Quickshell.Wayland
|
|||||||
import Quickshell.Hyprland
|
import Quickshell.Hyprland
|
||||||
import Qt5Compat.GraphicalEffects
|
import Qt5Compat.GraphicalEffects
|
||||||
import org.kde.syntaxhighlighting
|
import org.kde.syntaxhighlighting
|
||||||
// import org.kde.kirigami as Kirigami
|
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
@@ -25,6 +24,7 @@ Rectangle {
|
|||||||
property real messagePadding: 7
|
property real messagePadding: 7
|
||||||
property real contentSpacing: 3
|
property real contentSpacing: 3
|
||||||
property real codeBlockBackgroundRounding: Appearance.rounding.small
|
property real codeBlockBackgroundRounding: Appearance.rounding.small
|
||||||
|
property real codeBlockHeaderPadding: 3
|
||||||
property real codeBlockComponentSpacing: 2
|
property real codeBlockComponentSpacing: 2
|
||||||
|
|
||||||
property bool renderMarkdown: true
|
property bool renderMarkdown: true
|
||||||
@@ -37,6 +37,46 @@ Rectangle {
|
|||||||
radius: Appearance.rounding.normal
|
radius: Appearance.rounding.normal
|
||||||
color: Appearance.colors.colLayer1
|
color: Appearance.colors.colLayer1
|
||||||
|
|
||||||
|
function saveMessage() {
|
||||||
|
if (!root.editing) return;
|
||||||
|
// Get all Loader children (each represents a segment)
|
||||||
|
const segments = messageContentColumnLayout.children
|
||||||
|
.map(child => child.segment)
|
||||||
|
.filter(segment => (segment));
|
||||||
|
// console.log("Segments: " + JSON.stringify(segments))
|
||||||
|
|
||||||
|
// Reconstruct markdown
|
||||||
|
const newContent = segments.map(segment => {
|
||||||
|
if (segment.type === "code") {
|
||||||
|
const lang = segment.lang ? segment.lang : "";
|
||||||
|
// Remove trailing newlines
|
||||||
|
const code = segment.content.replace(/\n+$/, "");
|
||||||
|
return "```" + lang + "\n" + code + "\n```";
|
||||||
|
} else {
|
||||||
|
return segment.content;
|
||||||
|
}
|
||||||
|
}).join("");
|
||||||
|
|
||||||
|
root.editing = false
|
||||||
|
root.messageData.content = newContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys.onPressed: (event) => {
|
||||||
|
if ( // Prevent de-select
|
||||||
|
event.key === Qt.Key_Control ||
|
||||||
|
event.key == Qt.Key_Shift ||
|
||||||
|
event.key == Qt.Key_Alt ||
|
||||||
|
event.key == Qt.Key_Meta
|
||||||
|
) {
|
||||||
|
event.accepted = true
|
||||||
|
}
|
||||||
|
// Ctrl + S to save
|
||||||
|
if ((event.key === Qt.Key_S) && event.modifiers == Qt.ControlModifier) {
|
||||||
|
root.saveMessage();
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: columnLayout
|
id: columnLayout
|
||||||
|
|
||||||
@@ -149,29 +189,12 @@ Rectangle {
|
|||||||
AiMessageControlButton {
|
AiMessageControlButton {
|
||||||
id: editButton
|
id: editButton
|
||||||
activated: root.editing
|
activated: root.editing
|
||||||
|
enabled: root.messageData.done
|
||||||
buttonIcon: "edit"
|
buttonIcon: "edit"
|
||||||
onClicked: {
|
onClicked: {
|
||||||
root.editing = !root.editing
|
root.editing = !root.editing
|
||||||
if (!root.editing) { // Save changes
|
if (!root.editing) { // Save changes
|
||||||
// Get all Loader children (each represents a segment)
|
root.saveMessage()
|
||||||
const segments = messageContentColumnLayout.children
|
|
||||||
.map(child => child.segment)
|
|
||||||
.filter(segment => (segment));
|
|
||||||
// console.log("Segments: " + JSON.stringify(segments))
|
|
||||||
|
|
||||||
// Reconstruct markdown
|
|
||||||
const newContent = segments.map(segment => {
|
|
||||||
if (segment.type === "code") {
|
|
||||||
const lang = segment.lang ? segment.lang : "";
|
|
||||||
// Remove trailing newlines
|
|
||||||
const code = segment.content.replace(/\n+$/, "");
|
|
||||||
return "```" + lang + "\n" + code + "\n```";
|
|
||||||
} else {
|
|
||||||
return segment.content;
|
|
||||||
}
|
|
||||||
}).join("");
|
|
||||||
|
|
||||||
root.messageData.content = newContent;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
StyledToolTip {
|
StyledToolTip {
|
||||||
@@ -204,6 +227,8 @@ Rectangle {
|
|||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: messageContentColumnLayout
|
id: messageContentColumnLayout
|
||||||
|
|
||||||
|
spacing: 0
|
||||||
Repeater {
|
Repeater {
|
||||||
model: ScriptModel {
|
model: ScriptModel {
|
||||||
values: {
|
values: {
|
||||||
@@ -223,6 +248,7 @@ Rectangle {
|
|||||||
Component { // Text block
|
Component { // Text block
|
||||||
id: textBlockComponent
|
id: textBlockComponent
|
||||||
TextArea {
|
TextArea {
|
||||||
|
Layout.fillWidth: true
|
||||||
readOnly: !root.editing
|
readOnly: !root.editing
|
||||||
renderType: Text.NativeRendering
|
renderType: Text.NativeRendering
|
||||||
font.family: Appearance.font.family.reading
|
font.family: Appearance.font.family.reading
|
||||||
@@ -240,9 +266,6 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Keys.onPressed: (event) => {
|
Keys.onPressed: (event) => {
|
||||||
if (event.key === Qt.Key_Control || event.key == Qt.Key_Shift || event.key == Qt.Key_Alt || event.key == Qt.Key_Meta) { // Prevent de-select
|
|
||||||
event.accepted = true
|
|
||||||
}
|
|
||||||
if ((event.key === Qt.Key_C) && event.modifiers == Qt.ControlModifier) {
|
if ((event.key === Qt.Key_C) && event.modifiers == Qt.ControlModifier) {
|
||||||
messageText.copy()
|
messageText.copy()
|
||||||
event.accepted = true
|
event.accepted = true
|
||||||
@@ -267,7 +290,8 @@ Rectangle {
|
|||||||
id: codeBlockComponent
|
id: codeBlockComponent
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
spacing: codeBlockComponentSpacing
|
spacing: codeBlockComponentSpacing
|
||||||
Layout.fillWidth: true
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
|
||||||
Rectangle { // Code background
|
Rectangle { // Code background
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
@@ -276,10 +300,15 @@ Rectangle {
|
|||||||
bottomLeftRadius: Appearance.rounding.unsharpen
|
bottomLeftRadius: Appearance.rounding.unsharpen
|
||||||
bottomRightRadius: Appearance.rounding.unsharpen
|
bottomRightRadius: Appearance.rounding.unsharpen
|
||||||
color: Appearance.m3colors.m3surfaceContainerHighest
|
color: Appearance.m3colors.m3surfaceContainerHighest
|
||||||
implicitHeight: codeBlockTitleBarRowLayout.implicitHeight
|
implicitHeight: codeBlockTitleBarRowLayout.implicitHeight + codeBlockHeaderPadding * 2
|
||||||
|
|
||||||
RowLayout { // Language and buttons
|
RowLayout { // Language and buttons
|
||||||
id: codeBlockTitleBarRowLayout
|
id: codeBlockTitleBarRowLayout
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.leftMargin: codeBlockHeaderPadding
|
||||||
|
anchors.rightMargin: codeBlockHeaderPadding
|
||||||
spacing: 5
|
spacing: 5
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
@@ -296,6 +325,19 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Item { Layout.fillWidth: true }
|
Item { Layout.fillWidth: true }
|
||||||
|
|
||||||
|
AiMessageControlButton {
|
||||||
|
id: copyCodeButton
|
||||||
|
buttonIcon: "content_copy"
|
||||||
|
onClicked: {
|
||||||
|
Hyprland.dispatch(`exec wl-copy '${StringUtils.unEscapeBackslashes(
|
||||||
|
StringUtils.shellSingleQuoteEscape(segment.content)
|
||||||
|
)}'`)
|
||||||
|
}
|
||||||
|
StyledToolTip {
|
||||||
|
content: qsTr("Copy code")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,7 +384,6 @@ Rectangle {
|
|||||||
topRightRadius: Appearance.rounding.unsharpen
|
topRightRadius: Appearance.rounding.unsharpen
|
||||||
bottomRightRadius: codeBlockBackgroundRounding
|
bottomRightRadius: codeBlockBackgroundRounding
|
||||||
color: Appearance.colors.colLayer2
|
color: Appearance.colors.colLayer2
|
||||||
// implicitWidth: codeTextArea.implicitWidth
|
|
||||||
implicitHeight: codeTextArea.implicitHeight
|
implicitHeight: codeTextArea.implicitHeight
|
||||||
|
|
||||||
ScrollView {
|
ScrollView {
|
||||||
@@ -350,11 +391,35 @@ Rectangle {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
implicitWidth: parent.width
|
implicitWidth: parent.width
|
||||||
implicitHeight: codeTextArea.contentHeight
|
implicitHeight: codeTextArea.implicitHeight + 1
|
||||||
contentWidth: codeTextArea.contentWidth
|
contentWidth: codeTextArea.width - 1
|
||||||
contentHeight: codeTextArea.contentHeight
|
// contentHeight: codeTextArea.contentHeight
|
||||||
clip: true
|
clip: true
|
||||||
ScrollBar.vertical.policy: ScrollBar.AlwaysOff
|
ScrollBar.vertical.policy: ScrollBar.AlwaysOff
|
||||||
|
|
||||||
|
ScrollBar.horizontal: ScrollBar {
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
padding: 5
|
||||||
|
policy: ScrollBar.AsNeeded
|
||||||
|
opacity: visualSize == 1 ? 0 : 1
|
||||||
|
visible: opacity > 0
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Appearance.animation.elementMoveFast.duration
|
||||||
|
easing.type: Appearance.animation.elementMoveFast.type
|
||||||
|
easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem: Rectangle {
|
||||||
|
implicitHeight: 6
|
||||||
|
radius: Appearance.rounding.small
|
||||||
|
color: Appearance.colors.colLayer2Active
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TextArea { // Code
|
TextArea { // Code
|
||||||
|
|
||||||
@@ -382,13 +447,6 @@ Rectangle {
|
|||||||
codeTextArea.insert(cursor, " ");
|
codeTextArea.insert(cursor, " ");
|
||||||
codeTextArea.cursorPosition = cursor + 4;
|
codeTextArea.cursorPosition = cursor + 4;
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
} else if (
|
|
||||||
event.key === Qt.Key_Control ||
|
|
||||||
event.key == Qt.Key_Shift ||
|
|
||||||
event.key == Qt.Key_Alt ||
|
|
||||||
event.key == Qt.Key_Meta
|
|
||||||
) {
|
|
||||||
event.accepted = true;
|
|
||||||
} else if ((event.key === Qt.Key_C) && event.modifiers == Qt.ControlModifier) {
|
} else if ((event.key === Qt.Key_C) && event.modifiers == Qt.ControlModifier) {
|
||||||
messageText.copy();
|
messageText.copy();
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
|
|||||||
Reference in New Issue
Block a user