many changed, added nixos-hardware
This commit is contained in:
parent
b0745b5bec
commit
b699fff171
149 changed files with 19124 additions and 238 deletions
293
modules/styling/config/widgets/sideleft/apis/ai_chatmessage.js
Normal file
293
modules/styling/config/widgets/sideleft/apis/ai_chatmessage.js
Normal file
|
@ -0,0 +1,293 @@
|
|||
const { Gdk, Gio, GLib, Gtk } = imports.gi;
|
||||
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, Scrollable } = Widget;
|
||||
const { execAsync, exec } = Utils;
|
||||
import { MaterialIcon } from "../../../lib/materialicon.js";
|
||||
import md2pango from "../../../lib/md2pango.js";
|
||||
|
||||
|
||||
const CUSTOM_SOURCEVIEW_SCHEME_PATH = `${App.configDir}/data/sourceviewtheme.xml`;
|
||||
const CUSTOM_SCHEME_ID = 'custom';
|
||||
const USERNAME = GLib.get_user_name();
|
||||
const CHATGPT_CURSOR = ' ...';
|
||||
|
||||
/////////////////////// Custom source view colorscheme /////////////////////////
|
||||
|
||||
function loadCustomColorScheme(filePath) {
|
||||
// Read the XML file content
|
||||
const file = Gio.File.new_for_path(filePath);
|
||||
const [success, contents] = file.load_contents(null);
|
||||
|
||||
if (!success) {
|
||||
logError('Failed to load the XML file.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse the XML content and set the Style Scheme
|
||||
const schemeManager = GtkSource.StyleSchemeManager.get_default();
|
||||
schemeManager.append_search_path(file.get_parent().get_path());
|
||||
}
|
||||
loadCustomColorScheme(CUSTOM_SOURCEVIEW_SCHEME_PATH);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
function copyToClipboard(text) {
|
||||
const clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD);
|
||||
const textVariant = new GLib.Variant('s', text);
|
||||
clipboard.set_text(textVariant, -1);
|
||||
clipboard.store();
|
||||
}
|
||||
|
||||
function substituteLang(str) {
|
||||
const subs = [
|
||||
{ from: 'javascript', to: 'js' },
|
||||
{ from: 'bash', to: 'sh' },
|
||||
];
|
||||
|
||||
for (const { from, to } of subs) {
|
||||
if (from === str)
|
||||
return to;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
const HighlightedCode = (content, lang) => {
|
||||
const buffer = new GtkSource.Buffer();
|
||||
const sourceView = new GtkSource.View({
|
||||
buffer: buffer,
|
||||
wrap_mode: Gtk.WrapMode.NONE
|
||||
});
|
||||
const langManager = GtkSource.LanguageManager.get_default();
|
||||
let displayLang = langManager.get_language(substituteLang(lang)); // Set your preferred language
|
||||
if (displayLang) {
|
||||
buffer.set_language(displayLang);
|
||||
}
|
||||
const schemeManager = GtkSource.StyleSchemeManager.get_default();
|
||||
buffer.set_style_scheme(schemeManager.get_scheme(CUSTOM_SCHEME_ID));
|
||||
buffer.set_text(content, -1);
|
||||
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 CodeBlock = (content = '', lang = 'txt') => {
|
||||
const topBar = Box({
|
||||
className: 'sidebar-chat-codeblock-topbar',
|
||||
children: [
|
||||
Label({
|
||||
label: lang,
|
||||
className: 'sidebar-chat-codeblock-topbar-txt',
|
||||
}),
|
||||
Box({
|
||||
hexpand: true,
|
||||
}),
|
||||
Button({
|
||||
className: 'sidebar-chat-codeblock-topbar-btn',
|
||||
child: Box({
|
||||
className: 'spacing-h-5',
|
||||
children: [
|
||||
MaterialIcon('content_copy', 'small'),
|
||||
Label({
|
||||
label: 'Copy',
|
||||
})
|
||||
]
|
||||
}),
|
||||
onClicked: (self) => {
|
||||
const buffer = sourceView.get_buffer();
|
||||
const copyContent = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter(), false); // TODO: fix this
|
||||
execAsync([`wl-copy`, `${copyContent}`]).catch(print);
|
||||
},
|
||||
}),
|
||||
]
|
||||
})
|
||||
// Source view
|
||||
const sourceView = HighlightedCode(content, lang);
|
||||
|
||||
const codeBlock = Box({
|
||||
attribute: {
|
||||
'updateText': (text) => {
|
||||
sourceView.get_buffer().set_text(text, -1);
|
||||
}
|
||||
},
|
||||
className: 'sidebar-chat-codeblock',
|
||||
vertical: true,
|
||||
children: [
|
||||
topBar,
|
||||
Box({
|
||||
className: 'sidebar-chat-codeblock-code',
|
||||
homogeneous: true,
|
||||
children: [Scrollable({
|
||||
vscroll: 'never',
|
||||
hscroll: 'automatic',
|
||||
child: sourceView,
|
||||
})],
|
||||
})
|
||||
]
|
||||
})
|
||||
|
||||
// const schemeIds = styleManager.get_scheme_ids();
|
||||
|
||||
// print("Available Style Schemes:");
|
||||
// for (let i = 0; i < schemeIds.length; i++) {
|
||||
// print(schemeIds[i]);
|
||||
// }
|
||||
return codeBlock;
|
||||
}
|
||||
|
||||
const Divider = () => Box({
|
||||
className: 'sidebar-chat-divider',
|
||||
})
|
||||
|
||||
const MessageContent = (content) => {
|
||||
const contentBox = Box({
|
||||
vertical: true,
|
||||
attribute: {
|
||||
'fullUpdate': (self, content, useCursor = false) => {
|
||||
// Clear and add first text widget
|
||||
const children = contentBox.get_children();
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i];
|
||||
child.destroy();
|
||||
}
|
||||
contentBox.add(TextBlock())
|
||||
// Loop lines. Put normal text in markdown parser
|
||||
// and put code into code highlighter (TODO)
|
||||
let lines = content.split('\n');
|
||||
let lastProcessed = 0;
|
||||
let inCode = false;
|
||||
for (const [index, line] of lines.entries()) {
|
||||
// Code blocks
|
||||
const codeBlockRegex = /^\s*```([a-zA-Z0-9]+)?\n?/;
|
||||
if (codeBlockRegex.test(line)) {
|
||||
const kids = self.get_children();
|
||||
const lastLabel = kids[kids.length - 1];
|
||||
const blockContent = lines.slice(lastProcessed, index).join('\n');
|
||||
if (!inCode) {
|
||||
lastLabel.label = md2pango(blockContent);
|
||||
contentBox.add(CodeBlock('', codeBlockRegex.exec(line)[1]));
|
||||
}
|
||||
else {
|
||||
lastLabel.attribute.updateText(blockContent);
|
||||
contentBox.add(TextBlock());
|
||||
}
|
||||
|
||||
lastProcessed = index + 1;
|
||||
inCode = !inCode;
|
||||
}
|
||||
// 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);
|
||||
contentBox.add(Divider());
|
||||
contentBox.add(TextBlock());
|
||||
lastProcessed = index + 1;
|
||||
}
|
||||
}
|
||||
if (lastProcessed < lines.length) {
|
||||
const kids = self.get_children();
|
||||
const lastLabel = kids[kids.length - 1];
|
||||
let blockContent = lines.slice(lastProcessed, lines.length).join('\n');
|
||||
if (!inCode)
|
||||
lastLabel.label = `${md2pango(blockContent)}${useCursor ? CHATGPT_CURSOR : ''}`;
|
||||
else
|
||||
lastLabel.attribute.updateText(blockContent);
|
||||
}
|
||||
// Debug: plain text
|
||||
// contentBox.add(Label({
|
||||
// hpack: 'fill',
|
||||
// className: 'txt sidebar-chat-txtblock sidebar-chat-txt',
|
||||
// useMarkup: false,
|
||||
// xalign: 0,
|
||||
// wrap: true,
|
||||
// selectable: true,
|
||||
// label: '------------------------------\n' + md2pango(content),
|
||||
// }))
|
||||
contentBox.show_all();
|
||||
}
|
||||
}
|
||||
});
|
||||
contentBox.attribute.fullUpdate(contentBox, content, false);
|
||||
return contentBox;
|
||||
}
|
||||
|
||||
export const ChatMessage = (message, modelName = 'Model') => {
|
||||
const messageContentBox = MessageContent(message.content);
|
||||
const thisMessage = Box({
|
||||
className: 'sidebar-chat-message',
|
||||
children: [
|
||||
Box({
|
||||
className: `sidebar-chat-indicator ${message.role == 'user' ? 'sidebar-chat-indicator-user' : 'sidebar-chat-indicator-bot'}`,
|
||||
}),
|
||||
Box({
|
||||
vertical: true,
|
||||
hpack: 'fill',
|
||||
hexpand: true,
|
||||
children: [
|
||||
Label({
|
||||
hpack: 'fill',
|
||||
xalign: 0,
|
||||
className: 'txt txt-bold sidebar-chat-name',
|
||||
wrap: true,
|
||||
useMarkup: true,
|
||||
label: (message.role == 'user' ? USERNAME : modelName),
|
||||
}),
|
||||
messageContentBox,
|
||||
],
|
||||
setup: (self) => self
|
||||
.hook(message, (self, isThinking) => {
|
||||
messageContentBox.toggleClassName('thinking', message.thinking);
|
||||
}, 'notify::thinking')
|
||||
.hook(message, (self) => { // Message update
|
||||
messageContentBox.attribute.fullUpdate(messageContentBox, message.content, message.role != 'user');
|
||||
}, 'notify::content')
|
||||
.hook(message, (label, isDone) => { // Remove the cursor
|
||||
messageContentBox.attribute.fullUpdate(messageContentBox, message.content, false);
|
||||
}, 'notify::done')
|
||||
,
|
||||
})
|
||||
]
|
||||
});
|
||||
return thisMessage;
|
||||
}
|
||||
|
||||
export const SystemMessage = (content, commandName, scrolledWindow) => {
|
||||
const messageContentBox = MessageContent(content);
|
||||
const thisMessage = Box({
|
||||
className: 'sidebar-chat-message',
|
||||
children: [
|
||||
Box({
|
||||
className: `sidebar-chat-indicator sidebar-chat-indicator-System`,
|
||||
}),
|
||||
Box({
|
||||
vertical: true,
|
||||
hpack: 'fill',
|
||||
hexpand: true,
|
||||
children: [
|
||||
Label({
|
||||
xalign: 0,
|
||||
className: 'txt txt-bold sidebar-chat-name',
|
||||
wrap: true,
|
||||
label: `System • ${commandName}`,
|
||||
}),
|
||||
messageContentBox,
|
||||
],
|
||||
})
|
||||
],
|
||||
});
|
||||
return thisMessage;
|
||||
}
|
272
modules/styling/config/widgets/sideleft/apis/chatgpt.js
Normal file
272
modules/styling/config/widgets/sideleft/apis/chatgpt.js
Normal file
|
@ -0,0 +1,272 @@
|
|||
const { Gtk } = imports.gi;
|
||||
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, Entry, EventBox, Icon, Label, Revealer, Scrollable, Stack } = Widget;
|
||||
const { execAsync, exec } = Utils;
|
||||
import ChatGPT from '../../../services/chatgpt.js';
|
||||
import { MaterialIcon } from "../../../lib/materialicon.js";
|
||||
import { setupCursorHover, setupCursorHoverInfo } from "../../../lib/cursorhover.js";
|
||||
import { SystemMessage, ChatMessage } from "./ai_chatmessage.js";
|
||||
import { ConfigToggle, ConfigSegmentedSelection, ConfigGap } from '../../../lib/configwidgets.js';
|
||||
import { markdownTest } from '../../../lib/md2pango.js';
|
||||
import { MarginRevealer } from '../../../lib/advancedwidgets.js';
|
||||
|
||||
Gtk.IconTheme.get_default().append_search_path(`${App.configDir}/assets`);
|
||||
|
||||
export const chatGPTTabIcon = Icon({
|
||||
hpack: 'center',
|
||||
className: 'sidebar-chat-apiswitcher-icon',
|
||||
icon: `openai-symbolic`,
|
||||
});
|
||||
|
||||
const ChatGPTInfo = () => {
|
||||
const openAiLogo = Icon({
|
||||
hpack: 'center',
|
||||
className: 'sidebar-chat-welcome-logo',
|
||||
icon: `openai-symbolic`,
|
||||
});
|
||||
return Box({
|
||||
vertical: true,
|
||||
className: 'spacing-v-15',
|
||||
children: [
|
||||
openAiLogo,
|
||||
Label({
|
||||
className: 'txt txt-title-small sidebar-chat-welcome-txt',
|
||||
wrap: true,
|
||||
justify: Gtk.Justification.CENTER,
|
||||
label: 'Assistant (ChatGPT 3.5)',
|
||||
}),
|
||||
Box({
|
||||
className: 'spacing-h-5',
|
||||
hpack: 'center',
|
||||
children: [
|
||||
Label({
|
||||
className: 'txt-smallie txt-subtext',
|
||||
wrap: true,
|
||||
justify: Gtk.Justification.CENTER,
|
||||
label: 'Powered by OpenAI',
|
||||
}),
|
||||
Button({
|
||||
className: 'txt-subtext txt-norm icon-material',
|
||||
label: 'info',
|
||||
tooltipText: 'Uses gpt-3.5-turbo.\nNot affiliated, endorsed, or sponsored by OpenAI.',
|
||||
setup: setupCursorHoverInfo,
|
||||
}),
|
||||
]
|
||||
}),
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
export const ChatGPTSettings = () => MarginRevealer({
|
||||
transition: 'slide_down',
|
||||
revealChild: true,
|
||||
extraSetup: (self) => self
|
||||
.hook(ChatGPT, (self) => Utils.timeout(200, () => {
|
||||
self.attribute.hide();
|
||||
}), 'newMsg')
|
||||
.hook(ChatGPT, (self) => Utils.timeout(200, () => {
|
||||
self.attribute.show();
|
||||
}), 'clear')
|
||||
,
|
||||
child: Box({
|
||||
vertical: true,
|
||||
className: 'sidebar-chat-settings',
|
||||
children: [
|
||||
ConfigSegmentedSelection({
|
||||
hpack: 'center',
|
||||
icon: 'casino',
|
||||
name: 'Randomness',
|
||||
desc: 'ChatGPT\'s temperature value.\n Precise = 0\n Balanced = 0.5\n Creative = 1',
|
||||
options: [
|
||||
{ value: 0.00, name: 'Precise', },
|
||||
{ value: 0.50, name: 'Balanced', },
|
||||
{ value: 1.00, name: 'Creative', },
|
||||
],
|
||||
initIndex: 2,
|
||||
onChange: (value, name) => {
|
||||
ChatGPT.temperature = value;
|
||||
},
|
||||
}),
|
||||
ConfigGap({ vertical: true, size: 10 }), // Note: size can only be 5, 10, or 15
|
||||
Box({
|
||||
vertical: true,
|
||||
hpack: 'fill',
|
||||
className: 'sidebar-chat-settings-toggles',
|
||||
children: [
|
||||
ConfigToggle({
|
||||
icon: 'cycle',
|
||||
name: 'Cycle models',
|
||||
desc: 'Helps avoid exceeding the API rate of 3 messages per minute.\nTurn this on if you message rapidly.',
|
||||
initValue: ChatGPT.cycleModels,
|
||||
onChange: (self, newValue) => {
|
||||
ChatGPT.cycleModels = newValue;
|
||||
},
|
||||
}),
|
||||
ConfigToggle({
|
||||
icon: 'model_training',
|
||||
name: 'Enhancements',
|
||||
desc: 'Tells ChatGPT:\n- It\'s a Linux sidebar assistant\n- Be brief and use bullet points',
|
||||
initValue: ChatGPT.assistantPrompt,
|
||||
onChange: (self, newValue) => {
|
||||
ChatGPT.assistantPrompt = newValue;
|
||||
},
|
||||
}),
|
||||
]
|
||||
})
|
||||
]
|
||||
})
|
||||
});
|
||||
|
||||
export const OpenaiApiKeyInstructions = () => Box({
|
||||
homogeneous: true,
|
||||
children: [Revealer({
|
||||
transition: 'slide_down',
|
||||
transitionDuration: 150,
|
||||
setup: (self) => self
|
||||
.hook(ChatGPT, (self, hasKey) => {
|
||||
self.revealChild = (ChatGPT.key.length == 0);
|
||||
}, 'hasKey')
|
||||
,
|
||||
child: Button({
|
||||
child: Label({
|
||||
useMarkup: true,
|
||||
wrap: true,
|
||||
className: 'txt sidebar-chat-welcome-txt',
|
||||
justify: Gtk.Justification.CENTER,
|
||||
label: 'An OpenAI API key is required\nYou can grab one <u>here</u>, then enter it below'
|
||||
}),
|
||||
setup: setupCursorHover,
|
||||
onClicked: () => {
|
||||
Utils.execAsync(['bash', '-c', `xdg-open https://platform.openai.com/api-keys &`]);
|
||||
}
|
||||
})
|
||||
})]
|
||||
});
|
||||
|
||||
const chatGPTWelcome = Box({
|
||||
vexpand: true,
|
||||
homogeneous: true,
|
||||
child: Box({
|
||||
className: 'spacing-v-15',
|
||||
vpack: 'center',
|
||||
vertical: true,
|
||||
children: [
|
||||
ChatGPTInfo(),
|
||||
OpenaiApiKeyInstructions(),
|
||||
ChatGPTSettings(),
|
||||
]
|
||||
})
|
||||
});
|
||||
|
||||
export const chatContent = Box({
|
||||
className: 'spacing-v-15',
|
||||
vertical: true,
|
||||
setup: (self) => self
|
||||
.hook(ChatGPT, (box, id) => {
|
||||
const message = ChatGPT.messages[id];
|
||||
if (!message) return;
|
||||
box.add(ChatMessage(message, 'ChatGPT'))
|
||||
}, 'newMsg')
|
||||
,
|
||||
});
|
||||
|
||||
const clearChat = () => {
|
||||
ChatGPT.clear();
|
||||
const children = chatContent.get_children();
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i];
|
||||
child.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
export const chatGPTView = Scrollable({
|
||||
className: 'sidebar-chat-viewport',
|
||||
vexpand: true,
|
||||
child: Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
chatGPTWelcome,
|
||||
chatContent,
|
||||
]
|
||||
}),
|
||||
setup: (scrolledWindow) => {
|
||||
// Show scrollbar
|
||||
scrolledWindow.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
|
||||
const vScrollbar = scrolledWindow.get_vscrollbar();
|
||||
vScrollbar.get_style_context().add_class('sidebar-scrollbar');
|
||||
// Avoid click-to-scroll-widget-to-view behavior
|
||||
Utils.timeout(1, () => {
|
||||
const viewport = scrolledWindow.child;
|
||||
viewport.set_focus_vadjustment(new Gtk.Adjustment(undefined));
|
||||
})
|
||||
// Always scroll to bottom with new content
|
||||
const adjustment = scrolledWindow.get_vadjustment();
|
||||
adjustment.connect("changed", () => {
|
||||
adjustment.set_value(adjustment.get_upper() - adjustment.get_page_size());
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
const CommandButton = (command) => Button({
|
||||
className: 'sidebar-chat-chip sidebar-chat-chip-action txt txt-small',
|
||||
onClicked: () => sendMessage(command),
|
||||
setup: setupCursorHover,
|
||||
label: command,
|
||||
});
|
||||
|
||||
export const chatGPTCommands = Box({
|
||||
className: 'spacing-h-5',
|
||||
children: [
|
||||
Box({ hexpand: true }),
|
||||
CommandButton('/key'),
|
||||
CommandButton('/model'),
|
||||
CommandButton('/clear'),
|
||||
]
|
||||
});
|
||||
|
||||
export const sendMessage = (text) => {
|
||||
// Check if text or API key is empty
|
||||
if (text.length == 0) return;
|
||||
if (ChatGPT.key.length == 0) {
|
||||
ChatGPT.key = text;
|
||||
chatContent.add(SystemMessage(`Key saved to\n\`${ChatGPT.keyPath}\``, 'API Key', chatGPTView));
|
||||
text = '';
|
||||
return;
|
||||
}
|
||||
// Commands
|
||||
if (text.startsWith('/')) {
|
||||
if (text.startsWith('/clear')) clearChat();
|
||||
else if (text.startsWith('/model')) chatContent.add(SystemMessage(`Currently using \`${ChatGPT.modelName}\``, '/model', chatGPTView))
|
||||
else if (text.startsWith('/prompt')) {
|
||||
const firstSpaceIndex = text.indexOf(' ');
|
||||
const prompt = text.slice(firstSpaceIndex + 1);
|
||||
if (firstSpaceIndex == -1 || prompt.length < 1) {
|
||||
chatContent.add(SystemMessage(`Usage: \`/prompt MESSAGE\``, '/prompt', chatGPTView))
|
||||
}
|
||||
else {
|
||||
ChatGPT.addMessage('user', prompt)
|
||||
}
|
||||
}
|
||||
else if (text.startsWith('/key')) {
|
||||
const parts = text.split(' ');
|
||||
if (parts.length == 1) chatContent.add(SystemMessage(
|
||||
`Key stored in:\n\`${ChatGPT.keyPath}\`\nTo update this key, type \`/key YOUR_API_KEY\``,
|
||||
'/key',
|
||||
chatGPTView));
|
||||
else {
|
||||
ChatGPT.key = parts[1];
|
||||
chatContent.add(SystemMessage(`Updated API Key at\n\`${ChatGPT.keyPath}\``, '/key', chatGPTView));
|
||||
}
|
||||
}
|
||||
else if (text.startsWith('/test'))
|
||||
chatContent.add(SystemMessage(markdownTest, `Markdown test`, chatGPTView));
|
||||
else
|
||||
chatContent.add(SystemMessage(`Invalid command.`, 'Error', chatGPTView))
|
||||
}
|
||||
else {
|
||||
ChatGPT.send(text);
|
||||
}
|
||||
}
|
264
modules/styling/config/widgets/sideleft/apis/gemini.js
Normal file
264
modules/styling/config/widgets/sideleft/apis/gemini.js
Normal file
|
@ -0,0 +1,264 @@
|
|||
const { Gtk } = imports.gi;
|
||||
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, Entry, EventBox, Icon, Label, Revealer, Scrollable, Stack } = Widget;
|
||||
const { execAsync, exec } = Utils;
|
||||
import Gemini from '../../../services/gemini.js';
|
||||
import { MaterialIcon } from "../../../lib/materialicon.js";
|
||||
import { setupCursorHover, setupCursorHoverInfo } from "../../../lib/cursorhover.js";
|
||||
import { SystemMessage, ChatMessage } from "./ai_chatmessage.js";
|
||||
import { ConfigToggle, ConfigSegmentedSelection, ConfigGap } from '../../../lib/configwidgets.js';
|
||||
import { markdownTest } from '../../../lib/md2pango.js';
|
||||
import { MarginRevealer } from '../../../lib/advancedwidgets.js';
|
||||
|
||||
Gtk.IconTheme.get_default().append_search_path(`${App.configDir}/assets`);
|
||||
const MODEL_NAME = `Gemini`;
|
||||
|
||||
export const geminiTabIcon = Icon({
|
||||
hpack: 'center',
|
||||
className: 'sidebar-chat-apiswitcher-icon',
|
||||
icon: `google-gemini-symbolic`,
|
||||
})
|
||||
|
||||
const GeminiInfo = () => {
|
||||
const geminiLogo = Icon({
|
||||
hpack: 'center',
|
||||
className: 'sidebar-chat-welcome-logo',
|
||||
icon: `google-gemini-symbolic`,
|
||||
});
|
||||
return Box({
|
||||
vertical: true,
|
||||
className: 'spacing-v-15',
|
||||
children: [
|
||||
geminiLogo,
|
||||
Label({
|
||||
className: 'txt txt-title-small sidebar-chat-welcome-txt',
|
||||
wrap: true,
|
||||
justify: Gtk.Justification.CENTER,
|
||||
label: 'Assistant (Gemini Pro)',
|
||||
}),
|
||||
Box({
|
||||
className: 'spacing-h-5',
|
||||
hpack: 'center',
|
||||
children: [
|
||||
Label({
|
||||
className: 'txt-smallie txt-subtext',
|
||||
wrap: true,
|
||||
justify: Gtk.Justification.CENTER,
|
||||
label: 'Powered by Google',
|
||||
}),
|
||||
Button({
|
||||
className: 'txt-subtext txt-norm icon-material',
|
||||
label: 'info',
|
||||
tooltipText: 'Uses gemini-pro.\nNot affiliated, endorsed, or sponsored by Google.',
|
||||
setup: setupCursorHoverInfo,
|
||||
}),
|
||||
]
|
||||
}),
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
export const GeminiSettings = () => MarginRevealer({
|
||||
transition: 'slide_down',
|
||||
revealChild: true,
|
||||
extraSetup: (self) => self
|
||||
.hook(Gemini, (self) => Utils.timeout(200, () => {
|
||||
self.attribute.hide();
|
||||
}), 'newMsg')
|
||||
.hook(Gemini, (self) => Utils.timeout(200, () => {
|
||||
self.attribute.show();
|
||||
}), 'clear')
|
||||
,
|
||||
child: Box({
|
||||
vertical: true,
|
||||
className: 'sidebar-chat-settings',
|
||||
children: [
|
||||
ConfigSegmentedSelection({
|
||||
hpack: 'center',
|
||||
icon: 'casino',
|
||||
name: 'Randomness',
|
||||
desc: 'Gemini\'s temperature value.\n Precise = 0\n Balanced = 0.5\n Creative = 1',
|
||||
options: [
|
||||
{ value: 0.00, name: 'Precise', },
|
||||
{ value: 0.50, name: 'Balanced', },
|
||||
{ value: 1.00, name: 'Creative', },
|
||||
],
|
||||
initIndex: 2,
|
||||
onChange: (value, name) => {
|
||||
Gemini.temperature = value;
|
||||
},
|
||||
}),
|
||||
ConfigGap({ vertical: true, size: 10 }), // Note: size can only be 5, 10, or 15
|
||||
Box({
|
||||
vertical: true,
|
||||
hpack: 'fill',
|
||||
className: 'sidebar-chat-settings-toggles',
|
||||
children: [
|
||||
ConfigToggle({
|
||||
icon: 'model_training',
|
||||
name: 'Enhancements',
|
||||
desc: 'Tells Gemini:\n- It\'s a Linux sidebar assistant\n- Be brief and use bullet points',
|
||||
initValue: Gemini.assistantPrompt,
|
||||
onChange: (self, newValue) => {
|
||||
Gemini.assistantPrompt = newValue;
|
||||
},
|
||||
}),
|
||||
]
|
||||
})
|
||||
]
|
||||
})
|
||||
});
|
||||
|
||||
export const GoogleAiInstructions = () => Box({
|
||||
homogeneous: true,
|
||||
children: [Revealer({
|
||||
transition: 'slide_down',
|
||||
transitionDuration: 150,
|
||||
setup: (self) => self
|
||||
.hook(Gemini, (self, hasKey) => {
|
||||
self.revealChild = (Gemini.key.length == 0);
|
||||
}, 'hasKey')
|
||||
,
|
||||
child: Button({
|
||||
child: Label({
|
||||
useMarkup: true,
|
||||
wrap: true,
|
||||
className: 'txt sidebar-chat-welcome-txt',
|
||||
justify: Gtk.Justification.CENTER,
|
||||
label: 'A Google AI API key is required\nYou can grab one <u>here</u>, then enter it below'
|
||||
}),
|
||||
setup: setupCursorHover,
|
||||
onClicked: () => {
|
||||
Utils.execAsync(['bash', '-c', `xdg-open https://makersuite.google.com/app/apikey &`]);
|
||||
}
|
||||
})
|
||||
})]
|
||||
});
|
||||
|
||||
const geminiWelcome = Box({
|
||||
vexpand: true,
|
||||
homogeneous: true,
|
||||
child: Box({
|
||||
className: 'spacing-v-15',
|
||||
vpack: 'center',
|
||||
vertical: true,
|
||||
children: [
|
||||
GeminiInfo(),
|
||||
GoogleAiInstructions(),
|
||||
GeminiSettings(),
|
||||
]
|
||||
})
|
||||
});
|
||||
|
||||
export const chatContent = Box({
|
||||
className: 'spacing-v-15',
|
||||
vertical: true,
|
||||
setup: (self) => self
|
||||
.hook(Gemini, (box, id) => {
|
||||
const message = Gemini.messages[id];
|
||||
if (!message) return;
|
||||
box.add(ChatMessage(message, MODEL_NAME))
|
||||
}, 'newMsg')
|
||||
,
|
||||
});
|
||||
|
||||
const clearChat = () => {
|
||||
Gemini.clear();
|
||||
const children = chatContent.get_children();
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i];
|
||||
child.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
export const geminiView = Scrollable({
|
||||
className: 'sidebar-chat-viewport',
|
||||
vexpand: true,
|
||||
child: Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
geminiWelcome,
|
||||
chatContent,
|
||||
]
|
||||
}),
|
||||
setup: (scrolledWindow) => {
|
||||
// Show scrollbar
|
||||
scrolledWindow.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
|
||||
const vScrollbar = scrolledWindow.get_vscrollbar();
|
||||
vScrollbar.get_style_context().add_class('sidebar-scrollbar');
|
||||
// Avoid click-to-scroll-widget-to-view behavior
|
||||
Utils.timeout(1, () => {
|
||||
const viewport = scrolledWindow.child;
|
||||
viewport.set_focus_vadjustment(new Gtk.Adjustment(undefined));
|
||||
})
|
||||
// Always scroll to bottom with new content
|
||||
const adjustment = scrolledWindow.get_vadjustment();
|
||||
adjustment.connect("changed", () => {
|
||||
adjustment.set_value(adjustment.get_upper() - adjustment.get_page_size());
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
const CommandButton = (command) => Button({
|
||||
className: 'sidebar-chat-chip sidebar-chat-chip-action txt txt-small',
|
||||
onClicked: () => sendMessage(command),
|
||||
setup: setupCursorHover,
|
||||
label: command,
|
||||
});
|
||||
|
||||
export const geminiCommands = Box({
|
||||
className: 'spacing-h-5',
|
||||
children: [
|
||||
Box({ hexpand: true }),
|
||||
CommandButton('/key'),
|
||||
CommandButton('/model'),
|
||||
CommandButton('/clear'),
|
||||
]
|
||||
});
|
||||
|
||||
export const sendMessage = (text) => {
|
||||
// Check if text or API key is empty
|
||||
if (text.length == 0) return;
|
||||
if (Gemini.key.length == 0) {
|
||||
Gemini.key = text;
|
||||
chatContent.add(SystemMessage(`Key saved to\n\`${Gemini.keyPath}\``, 'API Key', geminiView));
|
||||
text = '';
|
||||
return;
|
||||
}
|
||||
// Commands
|
||||
if (text.startsWith('/')) {
|
||||
if (text.startsWith('/clear')) clearChat();
|
||||
else if (text.startsWith('/model')) chatContent.add(SystemMessage(`Currently using \`${Gemini.modelName}\``, '/model', geminiView))
|
||||
else if (text.startsWith('/prompt')) {
|
||||
const firstSpaceIndex = text.indexOf(' ');
|
||||
const prompt = text.slice(firstSpaceIndex + 1);
|
||||
if (firstSpaceIndex == -1 || prompt.length < 1) {
|
||||
chatContent.add(SystemMessage(`Usage: \`/prompt MESSAGE\``, '/prompt', geminiView))
|
||||
}
|
||||
else {
|
||||
Gemini.addMessage('user', prompt)
|
||||
}
|
||||
}
|
||||
else if (text.startsWith('/key')) {
|
||||
const parts = text.split(' ');
|
||||
if (parts.length == 1) chatContent.add(SystemMessage(
|
||||
`Key stored in:\n\`${Gemini.keyPath}\`\nTo update this key, type \`/key YOUR_API_KEY\``,
|
||||
'/key',
|
||||
geminiView));
|
||||
else {
|
||||
Gemini.key = parts[1];
|
||||
chatContent.add(SystemMessage(`Updated API Key at\n\`${Gemini.keyPath}\``, '/key', geminiView));
|
||||
}
|
||||
}
|
||||
else if (text.startsWith('/test'))
|
||||
chatContent.add(SystemMessage(markdownTest, `Markdown test`, geminiView));
|
||||
else
|
||||
chatContent.add(SystemMessage(`Invalid command.`, 'Error', geminiView))
|
||||
}
|
||||
else {
|
||||
Gemini.send(text);
|
||||
}
|
||||
}
|
431
modules/styling/config/widgets/sideleft/apis/waifu.js
Normal file
431
modules/styling/config/widgets/sideleft/apis/waifu.js
Normal file
|
@ -0,0 +1,431 @@
|
|||
const { Gdk, GdkPixbuf, Gio, GLib, Gtk } = imports.gi;
|
||||
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, Overlay, Revealer, Scrollable, Stack } = Widget;
|
||||
const { execAsync, exec } = Utils;
|
||||
import { MaterialIcon } from "../../../lib/materialicon.js";
|
||||
import { MarginRevealer } from '../../../lib/advancedwidgets.js';
|
||||
import { setupCursorHover, setupCursorHoverInfo } from "../../../lib/cursorhover.js";
|
||||
import WaifuService from '../../../services/waifus.js';
|
||||
|
||||
async function getImageViewerApp(preferredApp) {
|
||||
Utils.execAsync(['bash', '-c', `command -v ${preferredApp}`])
|
||||
.then((output) => {
|
||||
if (output != '') return preferredApp;
|
||||
else return 'xdg-open';
|
||||
});
|
||||
}
|
||||
|
||||
const IMAGE_REVEAL_DELAY = 13; // Some wait for inits n other weird stuff
|
||||
const IMAGE_VIEWER_APP = getImageViewerApp('loupe'); // Gnome's image viewer cuz very comfortable zooming
|
||||
const USER_CACHE_DIR = GLib.get_user_cache_dir();
|
||||
|
||||
// Create cache folder and clear pics from previous session
|
||||
Utils.exec(`bash -c 'mkdir -p ${USER_CACHE_DIR}/ags/media/waifus'`);
|
||||
Utils.exec(`bash -c 'rm ${USER_CACHE_DIR}/ags/media/waifus/*'`);
|
||||
|
||||
export function fileExists(filePath) {
|
||||
let file = Gio.File.new_for_path(filePath);
|
||||
return file.query_exists(null);
|
||||
}
|
||||
|
||||
const CommandButton = (command) => Button({
|
||||
className: 'sidebar-chat-chip sidebar-chat-chip-action txt txt-small',
|
||||
onClicked: () => sendMessage(command),
|
||||
setup: setupCursorHover,
|
||||
label: command,
|
||||
});
|
||||
|
||||
export const waifuTabIcon = Box({
|
||||
hpack: 'center',
|
||||
className: 'sidebar-chat-apiswitcher-icon',
|
||||
homogeneous: true,
|
||||
children: [
|
||||
MaterialIcon('photo_library', 'norm'),
|
||||
]
|
||||
});
|
||||
|
||||
const WaifuImage = (taglist) => {
|
||||
const ImageState = (icon, name) => Box({
|
||||
className: 'spacing-h-5 txt',
|
||||
children: [
|
||||
Box({ hexpand: true }),
|
||||
Label({
|
||||
className: 'sidebar-waifu-txt txt-smallie',
|
||||
xalign: 0,
|
||||
label: name,
|
||||
}),
|
||||
MaterialIcon(icon, 'norm'),
|
||||
]
|
||||
})
|
||||
const ImageAction = ({ name, icon, action }) => Button({
|
||||
className: 'sidebar-waifu-image-action txt-norm icon-material',
|
||||
tooltipText: name,
|
||||
label: icon,
|
||||
onClicked: action,
|
||||
setup: setupCursorHover,
|
||||
})
|
||||
const colorIndicator = Box({
|
||||
className: `sidebar-chat-indicator`,
|
||||
});
|
||||
const downloadState = Stack({
|
||||
homogeneous: false,
|
||||
transition: 'slide_up_down',
|
||||
transitionDuration: 150,
|
||||
children: {
|
||||
'api': ImageState('api', 'Calling API'),
|
||||
'download': ImageState('downloading', 'Downloading image'),
|
||||
'done': ImageState('done', 'Finished!'),
|
||||
'error': ImageState('error', 'Error'),
|
||||
},
|
||||
});
|
||||
const downloadIndicator = MarginRevealer({
|
||||
vpack: 'center',
|
||||
transition: 'slide_left',
|
||||
revealChild: true,
|
||||
child: downloadState,
|
||||
});
|
||||
const blockHeading = Box({
|
||||
hpack: 'fill',
|
||||
className: 'sidebar-waifu-content spacing-h-5',
|
||||
children: [
|
||||
...taglist.map((tag) => CommandButton(tag)),
|
||||
Box({ hexpand: true }),
|
||||
downloadIndicator,
|
||||
]
|
||||
});
|
||||
const blockImageActions = Revealer({
|
||||
transition: 'crossfade',
|
||||
revealChild: false,
|
||||
child: Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
Box({
|
||||
className: 'sidebar-waifu-image-actions spacing-h-3',
|
||||
children: [
|
||||
Box({ hexpand: true }),
|
||||
ImageAction({
|
||||
name: 'Go to source',
|
||||
icon: 'link',
|
||||
action: () => execAsync(['xdg-open', `${thisBlock.attribute.imageData.source}`]).catch(print),
|
||||
}),
|
||||
ImageAction({
|
||||
name: 'Hoard',
|
||||
icon: 'save',
|
||||
action: () => execAsync(['bash', '-c', `mkdir -p ~/Pictures/homework${thisBlock.attribute.isNsfw ? '/🌶️' : ''} && cp ${thisBlock.attribute.imagePath} ~/Pictures/homework${thisBlock.attribute.isNsfw ? '/🌶️/' : ''}`]).catch(print),
|
||||
}),
|
||||
ImageAction({
|
||||
name: 'Open externally',
|
||||
icon: 'open_in_new',
|
||||
action: () => execAsync([IMAGE_VIEWER_APP, `${thisBlock.attribute.imagePath}`]).catch(print),
|
||||
}),
|
||||
]
|
||||
})
|
||||
],
|
||||
})
|
||||
})
|
||||
const blockImage = Widget.DrawingArea({
|
||||
className: 'sidebar-waifu-image',
|
||||
});
|
||||
const blockImageRevealer = Revealer({
|
||||
transition: 'slide_down',
|
||||
transitionDuration: 150,
|
||||
revealChild: false,
|
||||
child: Overlay({
|
||||
child: Box({
|
||||
homogeneous: true,
|
||||
className: 'sidebar-waifu-image',
|
||||
children: [blockImage],
|
||||
}),
|
||||
overlays: [blockImageActions],
|
||||
}),
|
||||
});
|
||||
const thisBlock = Box({
|
||||
className: 'sidebar-chat-message',
|
||||
attribute: {
|
||||
'imagePath': '',
|
||||
'isNsfw': false,
|
||||
'imageData': '',
|
||||
'update': (imageData, force = false) => {
|
||||
thisBlock.attribute.imageData = imageData;
|
||||
const { status, signature, url, extension, source, dominant_color, is_nsfw, width, height, tags } = thisBlock.attribute.imageData;
|
||||
thisBlock.attribute.isNsfw = is_nsfw;
|
||||
if (status != 200) {
|
||||
downloadState.shown = 'error';
|
||||
return;
|
||||
}
|
||||
thisBlock.attribute.imagePath = `${USER_CACHE_DIR}/ags/media/waifus/${signature}${extension}`;
|
||||
downloadState.shown = 'download';
|
||||
// Width/height
|
||||
const widgetWidth = Math.min(Math.floor(waifuContent.get_allocated_width() * 0.85), width);
|
||||
const widgetHeight = Math.ceil(widgetWidth * height / width);
|
||||
blockImage.set_size_request(widgetWidth, widgetHeight);
|
||||
const showImage = () => {
|
||||
downloadState.shown = 'done';
|
||||
const pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(thisBlock.attribute.imagePath, widgetWidth, widgetHeight);
|
||||
// const pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(thisBlock.attribute.imagePath, widgetWidth, widgetHeight, false);
|
||||
|
||||
blockImage.set_size_request(widgetWidth, widgetHeight);
|
||||
blockImage.connect("draw", (widget, cr) => {
|
||||
const borderRadius = widget.get_style_context().get_property('border-radius', Gtk.StateFlags.NORMAL);
|
||||
|
||||
// Draw a rounded rectangle
|
||||
cr.arc(borderRadius, borderRadius, borderRadius, Math.PI, 1.5 * Math.PI);
|
||||
cr.arc(widgetWidth - borderRadius, borderRadius, borderRadius, 1.5 * Math.PI, 2 * Math.PI);
|
||||
cr.arc(widgetWidth - borderRadius, widgetHeight - borderRadius, borderRadius, 0, 0.5 * Math.PI);
|
||||
cr.arc(borderRadius, widgetHeight - borderRadius, borderRadius, 0.5 * Math.PI, Math.PI);
|
||||
cr.closePath();
|
||||
cr.clip();
|
||||
|
||||
// Paint image as bg
|
||||
Gdk.cairo_set_source_pixbuf(cr, pixbuf, 0, 0);
|
||||
cr.paint();
|
||||
});
|
||||
|
||||
// Reveal stuff
|
||||
Utils.timeout(IMAGE_REVEAL_DELAY, () => {
|
||||
blockImageRevealer.revealChild = true;
|
||||
})
|
||||
Utils.timeout(IMAGE_REVEAL_DELAY + blockImageRevealer.transitionDuration,
|
||||
() => blockImageActions.revealChild = true
|
||||
);
|
||||
downloadIndicator.attribute.hide();
|
||||
}
|
||||
// Show
|
||||
if (!force && fileExists(thisBlock.attribute.imagePath)) showImage();
|
||||
else Utils.execAsync(['bash', '-c', `wget -O '${thisBlock.attribute.imagePath}' '${url}'`])
|
||||
.then(showImage)
|
||||
.catch(print);
|
||||
blockHeading.get_children().forEach((child) => {
|
||||
child.setCss(`border-color: ${dominant_color};`);
|
||||
})
|
||||
colorIndicator.css = `background-color: ${dominant_color};`;
|
||||
},
|
||||
},
|
||||
children: [
|
||||
colorIndicator,
|
||||
Box({
|
||||
vertical: true,
|
||||
className: 'spacing-v-5',
|
||||
children: [
|
||||
blockHeading,
|
||||
Box({
|
||||
vertical: true,
|
||||
hpack: 'start',
|
||||
children: [blockImageRevealer],
|
||||
})
|
||||
]
|
||||
})
|
||||
],
|
||||
});
|
||||
return thisBlock;
|
||||
}
|
||||
|
||||
const WaifuInfo = () => {
|
||||
const waifuLogo = Label({
|
||||
hpack: 'center',
|
||||
className: 'sidebar-chat-welcome-logo',
|
||||
label: 'photo_library',
|
||||
})
|
||||
return Box({
|
||||
vertical: true,
|
||||
vexpand: true,
|
||||
className: 'spacing-v-15',
|
||||
children: [
|
||||
waifuLogo,
|
||||
Label({
|
||||
className: 'txt txt-title-small sidebar-chat-welcome-txt',
|
||||
wrap: true,
|
||||
justify: Gtk.Justification.CENTER,
|
||||
label: 'Waifus',
|
||||
}),
|
||||
Box({
|
||||
className: 'spacing-h-5',
|
||||
hpack: 'center',
|
||||
children: [
|
||||
Label({
|
||||
className: 'txt-smallie txt-subtext',
|
||||
wrap: true,
|
||||
justify: Gtk.Justification.CENTER,
|
||||
label: 'Powered by waifu.im',
|
||||
}),
|
||||
Button({
|
||||
className: 'txt-subtext txt-norm icon-material',
|
||||
label: 'info',
|
||||
tooltipText: 'A free Waifu API. An alternative to waifu.pics.',
|
||||
setup: setupCursorHoverInfo,
|
||||
}),
|
||||
]
|
||||
}),
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
const waifuWelcome = Box({
|
||||
vexpand: true,
|
||||
homogeneous: true,
|
||||
child: Box({
|
||||
className: 'spacing-v-15',
|
||||
vpack: 'center',
|
||||
vertical: true,
|
||||
children: [
|
||||
WaifuInfo(),
|
||||
]
|
||||
})
|
||||
});
|
||||
|
||||
const waifuContent = Box({
|
||||
className: 'spacing-v-15',
|
||||
vertical: true,
|
||||
attribute: {
|
||||
'map': new Map(),
|
||||
},
|
||||
setup: (self) => self
|
||||
.hook(WaifuService, (box, id) => {
|
||||
if (id === undefined) return;
|
||||
const newImageBlock = WaifuImage(WaifuService.queries[id]);
|
||||
box.add(newImageBlock);
|
||||
box.show_all();
|
||||
box.attribute.map.set(id, newImageBlock);
|
||||
}, 'newResponse')
|
||||
.hook(WaifuService, (box, id) => {
|
||||
if (id === undefined) return;
|
||||
const data = WaifuService.responses[id];
|
||||
if (!data) return;
|
||||
const imageBlock = box.attribute.map.get(id);
|
||||
imageBlock.attribute.update(data);
|
||||
}, 'updateResponse')
|
||||
,
|
||||
});
|
||||
|
||||
export const waifuView = Scrollable({
|
||||
className: 'sidebar-chat-viewport',
|
||||
vexpand: true,
|
||||
child: Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
waifuWelcome,
|
||||
waifuContent,
|
||||
]
|
||||
}),
|
||||
setup: (scrolledWindow) => {
|
||||
// Show scrollbar
|
||||
scrolledWindow.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
|
||||
const vScrollbar = scrolledWindow.get_vscrollbar();
|
||||
vScrollbar.get_style_context().add_class('sidebar-scrollbar');
|
||||
// Avoid click-to-scroll-widget-to-view behavior
|
||||
Utils.timeout(1, () => {
|
||||
const viewport = scrolledWindow.child;
|
||||
viewport.set_focus_vadjustment(new Gtk.Adjustment(undefined));
|
||||
})
|
||||
// Always scroll to bottom with new content
|
||||
const adjustment = scrolledWindow.get_vadjustment();
|
||||
adjustment.connect("changed", () => {
|
||||
adjustment.set_value(adjustment.get_upper() - adjustment.get_page_size());
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
const waifuTags = Revealer({
|
||||
revealChild: false,
|
||||
transition: 'crossfade',
|
||||
transitionDuration: 150,
|
||||
child: Box({
|
||||
className: 'spacing-h-5',
|
||||
children: [
|
||||
Scrollable({
|
||||
vscroll: 'never',
|
||||
hscroll: 'automatic',
|
||||
hexpand: true,
|
||||
child: Box({
|
||||
className: 'spacing-h-5',
|
||||
children: [
|
||||
CommandButton('waifu'),
|
||||
CommandButton('maid'),
|
||||
CommandButton('uniform'),
|
||||
CommandButton('oppai'),
|
||||
CommandButton('selfies'),
|
||||
CommandButton('marin-kitagawa'),
|
||||
CommandButton('raiden-shogun'),
|
||||
CommandButton('mori-calliope'),
|
||||
]
|
||||
})
|
||||
}),
|
||||
Box({ className: 'separator-line' }),
|
||||
]
|
||||
})
|
||||
});
|
||||
|
||||
export const waifuCommands = Box({
|
||||
className: 'spacing-h-5',
|
||||
setup: (self) => {
|
||||
self.pack_end(CommandButton('/clear'), false, false, 0);
|
||||
self.pack_start(Button({
|
||||
className: 'sidebar-chat-chip-toggle',
|
||||
setup: setupCursorHover,
|
||||
label: 'Tags →',
|
||||
onClicked: () => {
|
||||
waifuTags.revealChild = !waifuTags.revealChild;
|
||||
}
|
||||
}), false, false, 0);
|
||||
self.pack_start(waifuTags, true, true, 0);
|
||||
}
|
||||
});
|
||||
|
||||
const clearChat = () => {
|
||||
waifuContent.attribute.map.clear();
|
||||
const kids = waifuContent.get_children();
|
||||
for (let i = 0; i < kids.length; i++) {
|
||||
const child = kids[i];
|
||||
if (child) child.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
const DummyTag = (width, height, url, color = '#9392A6') => {
|
||||
return { // Needs timeout or inits won't make it
|
||||
status: 200,
|
||||
url: url,
|
||||
extension: '',
|
||||
signature: 0,
|
||||
source: url,
|
||||
dominant_color: color,
|
||||
is_nsfw: false,
|
||||
width: width,
|
||||
height: height,
|
||||
tags: ['/test'],
|
||||
};
|
||||
}
|
||||
|
||||
export const sendMessage = (text) => {
|
||||
// Do something on send
|
||||
// Commands
|
||||
if (text.startsWith('/')) {
|
||||
if (text.startsWith('/clear')) clearChat();
|
||||
else if (text.startsWith('/test')) {
|
||||
const newImage = WaifuImage(['/test']);
|
||||
waifuContent.add(newImage);
|
||||
Utils.timeout(IMAGE_REVEAL_DELAY, () => newImage.attribute.update(
|
||||
DummyTag(300, 200, 'https://picsum.photos/600/400'),
|
||||
true
|
||||
));
|
||||
}
|
||||
else if (text.startsWith('/chino')) {
|
||||
const newImage = WaifuImage(['/chino']);
|
||||
waifuContent.add(newImage);
|
||||
Utils.timeout(IMAGE_REVEAL_DELAY, () => newImage.attribute.update(
|
||||
DummyTag(300, 400, 'https://chino.pages.dev/chino', '#B2AEF3'),
|
||||
true
|
||||
));
|
||||
}
|
||||
else if (text.startsWith('/place')) {
|
||||
const newImage = WaifuImage(['/place']);
|
||||
waifuContent.add(newImage);
|
||||
Utils.timeout(IMAGE_REVEAL_DELAY, () => newImage.attribute.update(
|
||||
DummyTag(400, 600, 'https://placewaifu.com/image/400/600', '#F0A235'),
|
||||
true
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
else WaifuService.fetch(text);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue