From 1555f95185af5b8319e73000a1843fd0db3ea399 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 8 Apr 2025 00:45:08 +0200 Subject: [PATCH] ai: add support --- .../modules/sideleft/apis/ai_chatmessage.js | 136 +++++++++++++++--- .config/ags/scss/_lib_mixins.scss | 2 +- .config/ags/scss/_sidebars.scss | 37 +++++ 3 files changed, 158 insertions(+), 17 deletions(-) diff --git a/.config/ags/modules/sideleft/apis/ai_chatmessage.js b/.config/ags/modules/sideleft/apis/ai_chatmessage.js index 9f9627ff6..cbf160e29 100644 --- a/.config/ags/modules/sideleft/apis/ai_chatmessage.js +++ b/.config/ags/modules/sideleft/apis/ai_chatmessage.js @@ -3,11 +3,12 @@ import GtkSource from "gi://GtkSource?version=3.0"; import App from 'resource:///com/github/Aylur/ags/app.js'; import Widget from 'resource:///com/github/Aylur/ags/widget.js'; import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'; -const { Box, Button, Label, Icon, Scrollable, Stack } = Widget; +const { Box, Button, Label, Icon, Revealer, Scrollable, Stack } = Widget; const { execAsync, exec } = Utils; import { MaterialIcon } from '../../.commonwidgets/materialicon.js'; import md2pango, { replaceInlineLatexWithCodeBlocks } from '../../.miscutils/md2pango.js'; import { darkMode } from "../../.miscutils/system.js"; +import { setupCursorHover } from "../../.widgetutils/cursorhover.js"; const LATEX_DIR = `${GLib.get_user_cache_dir()}/ags/media/latex`; const USERNAME = GLib.get_user_name(); @@ -45,20 +46,104 @@ const HighlightedCode = (content, lang) => { return sourceView; } -const TextBlock = (content = '') => Label({ - hpack: 'fill', - className: 'txt sidebar-chat-txtblock sidebar-chat-txt', - useMarkup: true, - xalign: 0, - wrap: true, - selectable: true, - label: content, -}); +const TextBlock = (content = '') => { + const widget = Label({ + attribute: { + 'updateTextPlain': (text) => { + widget.label = text; + }, + 'updateText': (text) => { + widget.attribute.updateTextPlain(md2pango(text)); + } + }, + hpack: 'fill', + className: 'txt sidebar-chat-txtblock sidebar-chat-txt', + useMarkup: true, + xalign: 0, + wrap: true, + selectable: true, + label: content, + }); + return widget; +} + +const ThinkBlock = (content = '', revealChild = true) => { + const revealThought = Variable(revealChild); + const mainText = Label({ + hpack: 'fill', + className: `txt sidebar-chat-txtblock-think sidebar-chat-txt`, + useMarkup: true, + xalign: 0, + wrap: true, + selectable: true, + label: content, + }); + const mainTextRevealer = Revealer({ + transition: 'slide_down', + revealChild: revealThought.value, + child: mainText, + setup: (self) => self.hook(revealThought, (self) => { + self.revealChild = revealThought.value; + }) + }) + const expandIcon = MaterialIcon(revealThought.value ? 'expand_less' : 'expand_more', 'norm', { + setup: (self) => self.hook(revealThought, (self) => { + self.label = revealThought.value ? 'expand_less' : 'expand_more'; + }) + }); + const widget = Box({ + attribute: { + 'updateTextPlain': (text) => { + mainText.label = text; + }, + 'updateText': (text) => { + widget.attribute.updateTextPlain(md2pango(text)); + }, + 'done': () => { + revealThought.value = false; + } + }, + className: 'sidebar-chat-thinkblock', + vertical: true, + children: [ + Button({ + onClicked: (self) => { + revealThought.value = !revealThought.value; + }, + child: Box({ + className: 'spacing-h-10 padding-10', + children: [ + Box({ + homogeneous: true, + valign: 'center', + className: 'sidebar-chat-thinkblock-icon', + children: [MaterialIcon('neurology', 'large')] + }), + Label({ + valign: 'center', + hexpand: true, + label: 'Chain of Thought', + className: 'txt sidebar-chat-thinkblock-txt', + }), + Box({ + className: 'sidebar-chat-thinkblock-btn-arrow', + homogeneous: true, + children: [expandIcon], + }), + ] + }), + setup: setupCursorHover, + }), + mainTextRevealer, + ] + }); + return widget; +} Utils.execAsync(['bash', '-c', `rm -rf ${LATEX_DIR}`]) .then(() => Utils.execAsync(['bash', '-c', `mkdir -p ${LATEX_DIR}`])) .catch(print); -const Latex = (content = '') => { +const LatexBlock = (content = '') => { const latexViewArea = Box({ // vscroll: 'never', // hscroll: 'automatic', @@ -122,7 +207,7 @@ sed -i 's/stroke="rgb(0%, 0%, 0%)"/stroke="${darkMode.value ? '#ffffff' : '#0000 const CodeBlock = (content = '', lang = 'txt') => { if (lang == 'tex' || lang == 'latex') { - return Latex(content); + return LatexBlock(content); } const topBar = Box({ className: 'sidebar-chat-codeblock-topbar', @@ -216,7 +301,7 @@ const MessageContent = (content) => { child.destroy(); } contentBox.add(TextBlock()) - + let lines = replaceInlineLatexWithCodeBlocks(content).split('\n'); let lastProcessed = 0; let inCode = false; @@ -228,7 +313,8 @@ const MessageContent = (content) => { const lastLabel = kids[kids.length - 1]; const blockContent = lines.slice(lastProcessed, index).join('\n'); if (!inCode) { - lastLabel.label = md2pango(blockContent); + lastLabel.attribute.updateText(blockContent); + if (lastLabel.label == '') lastLabel.destroy(); contentBox.add(CodeBlock('', codeBlockRegex.exec(line)[1])); } else { @@ -239,13 +325,31 @@ const MessageContent = (content) => { lastProcessed = index + 1; inCode = !inCode; } + // Think block + const thinkBlockStartRegex = /^\s*/; // Start: + const thinkBlockEndRegex = /<\/think>\s*$/; // End: + if (!inCode && (thinkBlockStartRegex.test(line) || thinkBlockEndRegex.test(line))) { + const kids = self.get_children(); + const lastLabel = kids[kids.length - 1]; + const blockContent = lines.slice(lastProcessed, index).join('\n'); + + lastLabel.attribute.updateTextPlain(blockContent); + if (lastLabel.label == '') lastLabel.destroy(); + if (thinkBlockStartRegex.test(line)) contentBox.add(ThinkBlock()); + else { + // lastLabel.attribute.done(); + contentBox.add(TextBlock()); + } + + lastProcessed = index + 1; + } // Breaks const dividerRegex = /^\s*---/; if (!inCode && dividerRegex.test(line)) { const kids = self.get_children(); const lastLabel = kids[kids.length - 1]; const blockContent = lines.slice(lastProcessed, index).join('\n'); - lastLabel.label = md2pango(blockContent); + lastLabel.attribute.updateTextPlain(blockContent); contentBox.add(Divider()); contentBox.add(TextBlock()); lastProcessed = index + 1; @@ -256,7 +360,7 @@ const MessageContent = (content) => { const lastLabel = kids[kids.length - 1]; let blockContent = lines.slice(lastProcessed, lines.length).join('\n'); if (!inCode) - lastLabel.label = `${md2pango(blockContent)}${useCursor ? userOptions.ai.writingCursor : ''}`; + lastLabel.attribute.updateTextPlain(`${md2pango(blockContent)}${useCursor ? userOptions.ai.writingCursor : ''}`); else lastLabel.attribute.updateText(blockContent); } diff --git a/.config/ags/scss/_lib_mixins.scss b/.config/ags/scss/_lib_mixins.scss index 0c5817505..3bae4553c 100644 --- a/.config/ags/scss/_lib_mixins.scss +++ b/.config/ags/scss/_lib_mixins.scss @@ -2,7 +2,7 @@ $hovercolor: $surfaceContainerHigh; $activecolor: $surfaceContainerHighest; $rounding_unsharpen: 0.136rem; -$rounding_verysmall: 0.477rem; +$rounding_verysmall: 0.545rem; $rounding_small: 0.818rem; $rounding_mediumsmall: 0.955rem; $rounding_medium: 1.159rem; diff --git a/.config/ags/scss/_sidebars.scss b/.config/ags/scss/_sidebars.scss index 3fa3f9ddb..c12bd44b2 100644 --- a/.config/ags/scss/_sidebars.scss +++ b/.config/ags/scss/_sidebars.scss @@ -701,6 +701,43 @@ $skeleton-accent: mix($secondary, $onSecondary, 50%); padding-left: 0.818rem; } +.sidebar-chat-txtblock-think { + margin-left: -0.136rem; + padding: 0.682rem; + padding-left: 1.159rem; +} + +.sidebar-chat-thinkblock { + @include small-rounding; + background-color: $layer2; + color: $onLayer3; +} + +.sidebar-chat-thinkblock-icon { + @include verysmall-rounding; + background-color: $secondaryContainer; + color: $onSecondaryContainer; + min-width: 2.045rem; + min-height: 2.045rem; +} + +.sidebar-chat-thinkblock-btn-arrow { + @include full-rounding; + @include icon-material; + min-width: 1.705rem; + min-height: 1.705rem; + background-color: $layer2; + + &:hover, + &:focus { + background-color: $layer2Hover; + } + + &:active { + background-color: $layer2Active; + } +} + .sidebar-chat-txt { @include readingfont; }