From d425af4cdfe16c7fba8bf33529c520e9844ebb00 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 27 Jan 2024 00:19:42 +0700 Subject: [PATCH] fancy expanding api input box --- .config/ags/scss/_lib_classes.scss | 8 ++ .config/ags/scss/_sidebars.scss | 8 ++ .config/ags/services/waifus.js | 6 +- .config/ags/widgets/sideleft/apiwidgets.js | 99 ++++++++++++++++++---- .config/ags/widgets/sideleft/sideleft.js | 15 ++-- 5 files changed, 110 insertions(+), 26 deletions(-) diff --git a/.config/ags/scss/_lib_classes.scss b/.config/ags/scss/_lib_classes.scss index 9de6194e..3025ab3b 100644 --- a/.config/ags/scss/_lib_classes.scss +++ b/.config/ags/scss/_lib_classes.scss @@ -420,6 +420,14 @@ margin-bottom: 1.023rem; } +.width-10 { + min-width: 0.682rem; +} + +.height-10 { + min-width: 0.682rem; +} + .invisible { opacity: 0; background-color: transparent; diff --git a/.config/ags/scss/_sidebars.scss b/.config/ags/scss/_sidebars.scss index fdec7bc8..a215f102 100644 --- a/.config/ags/scss/_sidebars.scss +++ b/.config/ags/scss/_sidebars.scss @@ -523,6 +523,14 @@ $onChatgpt: $onPrimary; min-width: 0rem; } +.sidebar-chat-wrapper { + @include element_easeInOut; +} + +.sidebar-chat-wrapper-extended { + min-height: 5.114rem; +} + .sidebar-chat-send { @include menu_decel; min-width: 1.705rem; diff --git a/.config/ags/services/waifus.js b/.config/ags/services/waifus.js index 11afb77d..30ba8e73 100644 --- a/.config/ags/services/waifus.js +++ b/.config/ags/services/waifus.js @@ -83,12 +83,14 @@ class WaifuService extends Service { async fetch(msg) { // Init - const userArgs = msg.split(' '); + const userArgs = msg.split(/\s+/); + let taglist = []; this._nsfw = false; // Construct body/headers for (let i = 0; i < userArgs.length; i++) { - const thisArg = userArgs[i]; + const thisArg = userArgs[i].trim(); + if(thisArg.length == 0) continue; if (thisArg == '--im') this._mode = 'im'; else if (thisArg == '--nekos') this._mode = 'nekos'; else if (thisArg.includes('pics')) this._mode = 'pics'; diff --git a/.config/ags/widgets/sideleft/apiwidgets.js b/.config/ags/widgets/sideleft/apiwidgets.js index 5963a12c..a2096bf4 100644 --- a/.config/ags/widgets/sideleft/apiwidgets.js +++ b/.config/ags/widgets/sideleft/apiwidgets.js @@ -1,16 +1,28 @@ +const { Gtk, Gdk } = imports.gi; import App from 'resource:///com/github/Aylur/ags/app.js'; import Widget from 'resource:///com/github/Aylur/ags/widget.js'; +import AgsWidget from "resource:///com/github/Aylur/ags/widgets/widget.js"; import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'; -const { Box, Button, CenterBox, Entry, EventBox, Icon, Label, Revealer, Scrollable, Stack } = Widget; +const { Box, Button, CenterBox, Entry, EventBox, Icon, Label, Overlay, Revealer, Scrollable, Stack } = Widget; const { execAsync, exec } = Utils; import { setupCursorHover, setupCursorHoverInfo } from "../../lib/cursorhover.js"; +import { contentStack } from './sideleft.js'; // APIs import ChatGPT from '../../services/chatgpt.js'; import Gemini from '../../services/gemini.js'; import { geminiView, geminiCommands, sendMessage as geminiSendMessage, geminiTabIcon } from './apis/gemini.js'; import { chatGPTView, chatGPTCommands, sendMessage as chatGPTSendMessage, chatGPTTabIcon } from './apis/chatgpt.js'; import { waifuView, waifuCommands, sendMessage as waifuSendMessage, waifuTabIcon } from './apis/waifu.js'; +class AgsTextView extends AgsWidget(Gtk.TextView, "AgsTextView") { + static { AgsWidget.register(this, {}); } + constructor(params) { + super(params); + } +} +const TextView = Widget.createCtor(AgsTextView); + +const EXPAND_INPUT_THRESHOLD = 30; const APIS = [ { name: 'Assistant (ChatGPT 3.5)', @@ -40,9 +52,27 @@ const APIS = [ let currentApiId = 0; APIS[currentApiId].tabIcon.toggleClassName('sidebar-chat-apiswitcher-icon-enabled', true); -export const chatEntry = Entry({ - className: 'sidebar-chat-entry', +function apiSendMessage(textView) { + // Get text + const buffer = textView.get_buffer(); + const [start, end] = buffer.get_bounds(); + const text = buffer.get_text(start, end, true).trimStart(); + if (!text || text.length == 0) return; + // Send + APIS[currentApiId].sendCommand(text) + // Reset + Utils.timeout(100, () => { + buffer.set_text("", -1); + chatEntryWrapper.toggleClassName('sidebar-chat-wrapper-extended', false); + chatEntry.set_valign(Gtk.Align.CENTER); + }); +} + +export const chatEntry = TextView({ hexpand: true, + wrapMode: Gtk.WrapMode.WORD_CHAR, + acceptsTab: false, + className: 'sidebar-chat-entry', setup: (self) => self .hook(ChatGPT, (self) => { if (APIS[currentApiId].name != 'Assistant (ChatGPT 3.5)') return; @@ -52,31 +82,66 @@ export const chatEntry = Entry({ if (APIS[currentApiId].name != 'Assistant (Gemini Pro)') return; self.placeholderText = (Gemini.key.length > 0 ? 'Message Gemini...' : 'Enter Google AI API Key...'); }, 'hasKey') + .on("key-press-event", (widget, event) => { + const keyval = event.get_keyval()[1]; + if (event.get_keyval()[1] === Gdk.KEY_Return && event.get_state()[1] == Gdk.ModifierType.MOD2_MASK) { + apiSendMessage(widget); + return true; + } + // Global keybinds + if (!(event.get_state()[1] & Gdk.ModifierType.CONTROL_MASK) && + event.get_keyval()[1] === Gdk.KEY_Page_Down) { + const toSwitchTab = contentStack.get_visible_child(); + toSwitchTab.attribute.nextTab(); + } + else if (!(event.get_state()[1] & Gdk.ModifierType.CONTROL_MASK) && + event.get_keyval()[1] === Gdk.KEY_Page_Up) { + const toSwitchTab = contentStack.get_visible_child(); + toSwitchTab.attribute.prevTab(); + } + }) , - onChange: (entry) => { - chatSendButton.toggleClassName('sidebar-chat-send-available', entry.text.length > 0); - }, - onAccept: (entry) => { - APIS[currentApiId].sendCommand(entry.text) - entry.text = ''; - }, +}); + +chatEntry.get_buffer().connect("changed", (buffer) => { + const bufferText = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter(), true); + chatSendButton.toggleClassName('sidebar-chat-send-available', bufferText.length > 0); + if (buffer.get_line_count() > 1 || bufferText.length > EXPAND_INPUT_THRESHOLD) { + chatEntryWrapper.toggleClassName('sidebar-chat-wrapper-extended', true); + chatEntry.set_valign(Gtk.Align.FILL); + } + else { + chatEntryWrapper.toggleClassName('sidebar-chat-wrapper-extended', false); + chatEntry.set_valign(Gtk.Align.CENTER); + } +}); + +const chatEntryWrapper = Scrollable({ + className: 'sidebar-chat-wrapper', + hscroll: 'never', + vscroll: 'never', + child: chatEntry, }); const chatSendButton = Button({ className: 'txt-norm icon-material sidebar-chat-send', - vpack: 'center', + vpack: 'end', label: 'arrow_upward', setup: setupCursorHover, onClicked: (self) => { - APIS[currentApiId].sendCommand(chatEntry.text); - chatEntry.text = ''; + APIS[currentApiId].sendCommand(chatEntry.get_buffer().text); + chatEntry.get_buffer().set_text("", -1); }, }); const textboxArea = Box({ // Entry area - className: 'sidebar-chat-textarea spacing-h-10', + className: 'sidebar-chat-textarea', children: [ - chatEntry, + Overlay({ + passThrough: true, + child: chatEntryWrapper, + }), + Box({ className: 'width-10' }), chatSendButton, ] }); @@ -97,8 +162,8 @@ function switchToTab(id) { APIS[id].tabIcon.toggleClassName('sidebar-chat-apiswitcher-icon-enabled', true); apiContentStack.shown = APIS[id].name; apiCommandStack.shown = APIS[id].name; - chatEntry.placeholderText = APIS[id].placeholderText, - currentApiId = id; + chatEntry.placeholderText = APIS[id].placeholderText; + currentApiId = id; } const apiSwitcher = CenterBox({ diff --git a/.config/ags/widgets/sideleft/sideleft.js b/.config/ags/widgets/sideleft/sideleft.js index fc1a6b69..0b854f92 100644 --- a/.config/ags/widgets/sideleft/sideleft.js +++ b/.config/ags/widgets/sideleft/sideleft.js @@ -27,7 +27,7 @@ const contents = [ ] let currentTabId = 0; -const contentStack = Stack({ +export const contentStack = Stack({ vexpand: true, transition: 'slide_left_right', items: contents.map(item => [item.name, item.content]), @@ -117,9 +117,9 @@ const pinButton = Button({ vpack: 'start', className: 'sidebar-pin', child: MaterialIcon('push_pin', 'larger'), - tooltipText: 'Pin sidebar', + tooltipText: 'Pin sidebar (Ctrl+P)', onClicked: (self) => self.attribute.toggle(self), - // QoL: Focus Pin button on open. Hit keybind -> space/enter = toggle pin state + // Focus Pin button on open. Hit keybind -> space/enter = toggle pin state setup: (self) => self .hook(App, (self, currentName, visible) => { if (currentName === 'sideleft' && visible) @@ -163,7 +163,7 @@ export default () => Box({ ], setup: (self) => self .on('key-press-event', (widget, event) => { // Handle keybinds - if (event.get_state()[1] & Gdk.ModifierType.CONTROL_MASK) { + if (event.get_state()[1] & Gdk.ModifierType.CONTROL_MASK) { // Ctrl held // Pin sidebar if (event.get_keyval()[1] == Gdk.KEY_p) pinButton.attribute.toggle(pinButton); @@ -176,7 +176,7 @@ export default () => Box({ switchToTab(Math.min(currentTabId + 1, contents.length - 1)); } if (contentStack.shown == 'apis') { // If api tab is focused - // Automatically focus entry when typing + // Focus entry when typing if (( !(event.get_state()[1] & Gdk.ModifierType.CONTROL_MASK) && event.get_keyval()[1] >= 32 && event.get_keyval()[1] <= 126 && @@ -186,8 +186,9 @@ export default () => Box({ event.get_keyval()[1] === Gdk.KEY_v) ) { chatEntry.grab_focus(); - chatEntry.set_text(chatEntry.text + String.fromCharCode(event.get_keyval()[1])); - chatEntry.set_position(-1); + const buffer = chatEntry.get_buffer(); + buffer.set_text(buffer.text + String.fromCharCode(event.get_keyval()[1]), -1); + buffer.place_cursor(buffer.get_iter_at_offset(-1)); } // Switch API type else if (!(event.get_state()[1] & Gdk.ModifierType.CONTROL_MASK) &&