fancy expanding api input box

This commit is contained in:
end-4 2024-01-27 00:19:42 +07:00
parent 962e29d406
commit d425af4cdf
5 changed files with 110 additions and 26 deletions

View file

@ -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;

View file

@ -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;

View file

@ -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';

View file

@ -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({

View file

@ -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) &&