From ff377a58261183168fc4ffc52c5dda3f550e26e6 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 1 Jan 2024 01:52:25 +0700 Subject: [PATCH] add waifu image fetch --- .config/ags/config.js | 9 +- .config/ags/lib/md2pango.js | 29 +-- .config/ags/scss/_sidebars.scss | 61 +++++-- .config/ags/services/chatgpt.js | 2 +- .config/ags/services/todo.js | 2 +- .config/ags/services/waifus.js | 167 +++++++----------- .config/ags/style.css | 25 ++- .config/ags/widgets/dock/dock.js | 1 + .../ags/widgets/indicators/musiccontrols.js | 4 +- .config/ags/widgets/overview/miscfunctions.js | 8 +- .config/ags/widgets/sideleft/apis/chatgpt.js | 37 ++-- .../sideleft/apis/chatgpt_chatmessage.js | 10 +- .config/ags/widgets/sideleft/apis/waifu.js | 119 +++++++++++-- .config/ags/widgets/sideleft/apiwidgets.js | 6 +- 14 files changed, 269 insertions(+), 211 deletions(-) diff --git a/.config/ags/config.js b/.config/ags/config.js index 25df63fb..65093724 100644 --- a/.config/ags/config.js +++ b/.config/ags/config.js @@ -1,5 +1,6 @@ -"strict mode"; +"use strict"; // Import +const { GLib } = imports.gi; import { App, Utils } from './imports.js'; // Widgets import Bar from './widgets/bar/main.js'; @@ -16,8 +17,6 @@ import SideRight from './widgets/sideright/main.js'; const CLOSE_ANIM_TIME = 210; // Longer than actual anim time (see styles) to make sure widgets animate fully -// Init cache -Utils.exec(`bash -c 'mkdir -p ~/.cache/ags/user/colorschemes'`); // SCSS compilation Utils.exec(`bash -c 'echo "" > ${App.configDir}/scss/_musicwal.scss'`); // reset music styles Utils.exec(`bash -c 'echo "" > ${App.configDir}/scss/_musicmaterial.scss'`); // reset music styles @@ -43,8 +42,8 @@ export default { CornerTopright(), CornerBottomleft(), CornerBottomright(), - // DesktopBackground(), - // Dock(), // Buggy + // DesktopBackground(), // If you're going to uncomment these, + // Dock(), // Buggy // uncomment the import statement too. Overview(), Indicator(), Cheatsheet(), diff --git a/.config/ags/lib/md2pango.js b/.config/ags/lib/md2pango.js index e14a5cb6..ec94dd1f 100644 --- a/.config/ags/lib/md2pango.js +++ b/.config/ags/lib/md2pango.js @@ -57,7 +57,7 @@ const pad = (lines, start = 1, end = 1) => { return lines.map((l) => l.padEnd(len + end, ' ').padStart(len + end + start, ' ')) } -export function convert(text) { +export default (text) => { let lines = text.split('\n') // Indicates if the current line is within a code block @@ -205,33 +205,6 @@ export function convert(text) { return output.join('\n') } -const readFile = (f) => { - // node.js only and when running from the command line - const fs = require('fs') - return fs.readFileSync(f, 'utf8') -} - -let __is_nodejs_main = false -try { - // node.js specific checks and exports - __is_nodejs_main = (require.main === module) - exports.convert = convert -} catch (e) { } - -if (__is_nodejs_main) { - // running in node.js called from the CLI - let args = process.argv.slice(2) - if (args.length == 0 || args.find((a) => a == '-h')) { - console.log(`Usage: ${process.argv[1]} FILE [FILE...]`) - process.exit(0) - } - for (let i = 0; i < args.length; i++) { - const f = args[i]; - process.stdout.write(convert(readFile(f))); - } - -} - export const markdownTest = `# Heading 1 ## Heading 2 ### Heading 3 diff --git a/.config/ags/scss/_sidebars.scss b/.config/ags/scss/_sidebars.scss index 6eb0abb2..7321978b 100644 --- a/.config/ags/scss/_sidebars.scss +++ b/.config/ags/scss/_sidebars.scss @@ -166,27 +166,27 @@ $onChatgpt: $onPrimary; padding: 0rem $rounding_medium; } -.sidebar-navrail-btn>box>label { +.sidebar-navrail-btn > box > label { @include full-rounding; @include menu_decel; } -.sidebar-navrail-btn:hover>box>label:first-child, -.sidebar-navrail-btn:focus>box>label:first-child { +.sidebar-navrail-btn:hover > box > label:first-child, +.sidebar-navrail-btn:focus > box > label:first-child { background-color: mix($t_surfaceVariant, $onSurfaceVariant, 90%); } -.sidebar-navrail-btn:active>box>label:first-child { +.sidebar-navrail-btn:active > box > label:first-child { background-color: mix($surfaceVariant, $onSurfaceVariant, 75%); } -.sidebar-navrail-btn-active>box>label:first-child { +.sidebar-navrail-btn-active > box > label:first-child { background-color: $secondaryContainer; color: $onSecondaryContainer; } -.sidebar-navrail-btn-active:hover>box>label:first-child, -.sidebar-navrail-btn-active:focus>box>label:first-child { +.sidebar-navrail-btn-active:hover > box > label:first-child, +.sidebar-navrail-btn-active:focus > box > label:first-child { background-color: mix($secondaryContainer, $hovercolor, 90%); color: mix($onSecondaryContainer, $hovercolor, 90%); } @@ -346,7 +346,7 @@ $onChatgpt: $onPrimary; background-color: mix($surfaceVariant, $onSurfaceVariant, 75%); } -.sidebar-selector-tab-active>box>label { +.sidebar-selector-tab-active > box > label { color: $primary; } @@ -509,7 +509,7 @@ $onChatgpt: $onPrimary; .sidebar-chat-apiswitcher-icon { @include menu_decel; @include full-rounding; - min-width: 2.182rem; + min-width: 2.182rem; min-height: 2.182rem; color: $onSurface; } @@ -548,11 +548,19 @@ $onChatgpt: $onPrimary; .sidebar-chat-send:hover, .sidebar-chat-send:focus { - background-color: mix($sidebar_chat_textboxareaColor, $t_onSecondaryContainer, 97%); + background-color: mix( + $sidebar_chat_textboxareaColor, + $t_onSecondaryContainer, + 97% + ); } .sidebar-chat-send:active { - background-color: mix($sidebar_chat_textboxareaColor, $t_onSecondaryContainer, 80%); + background-color: mix( + $sidebar_chat_textboxareaColor, + $t_onSecondaryContainer, + 80% + ); } .sidebar-chat-send-available { @@ -576,6 +584,7 @@ $onChatgpt: $onPrimary; .sidebar-chat-indicator { @include full-rounding; min-width: 0.136rem; + background-color: $onBackground; } .sidebar-chat-indicator-user { @@ -594,7 +603,6 @@ $onChatgpt: $onPrimary; @include titlefont; padding: 0.341rem; margin-left: -0.136rem; - padding: 0.341rem; padding-left: 0.818rem; } @@ -699,7 +707,11 @@ $onChatgpt: $onPrimary; } .sidebar-chat-chip-action:active { - background-color: mix($sidebar_chat_textboxareaColor, $onSurfaceVariant, 70%); + background-color: mix( + $sidebar_chat_textboxareaColor, + $onSurfaceVariant, + 70% + ); color: mix($sidebar_chat_textboxareaColor, $surfaceVariant, 70%); } @@ -741,3 +753,26 @@ $onChatgpt: $onPrimary; background-color: mix($primary, $onPrimary, 80%); } +.sidebar-waifu-heading { + @include titlefont; + padding: 0.341rem; + margin-left: -0.136rem; + padding-left: 0.818rem; +} + +.sidebar-waifu-content { + margin-left: 0.682rem; +} + +.sidebar-waifu-txt { + @include readingfont; + margin-left: 0.682rem; +} + +.sidebar-waifu-image { + margin-left: 0.682rem; + @include normal-rounding; + background-size: cover; + background-repeat: no-repeat; + background-position: center; +} diff --git a/.config/ags/services/chatgpt.js b/.config/ags/services/chatgpt.js index 28729f8d..b5089223 100644 --- a/.config/ags/services/chatgpt.js +++ b/.config/ags/services/chatgpt.js @@ -44,7 +44,7 @@ function expandTilde(path) { // We're using many models to not be restricted to 3 messages per minute. // The whole chat will be sent every request anyway. -const KEY_FILE_LOCATION = `~/.cache/ags/user/openai_api_key.txt`; +const KEY_FILE_LOCATION = `${GLib.get_user_cache_dir()}/ags/user/openai_api_key.txt`; const CHAT_MODELS = ["gpt-3.5-turbo-1106", "gpt-3.5-turbo", "gpt-3.5-turbo-16k", "gpt-3.5-turbo-0613"] const ONE_CYCLE_COUNT = 3; diff --git a/.config/ags/services/todo.js b/.config/ags/services/todo.js index d6489f33..f0dabb47 100644 --- a/.config/ags/services/todo.js +++ b/.config/ags/services/todo.js @@ -63,7 +63,7 @@ class TodoService extends Service { super(); this._todoPath = `${GLib.get_user_cache_dir()}/ags/user/todo.json`; if (!fileExists(this._todoPath)) { // No? create file with empty array - Utils.exec(`bash -c 'mkdir -p ~/.cache/ags/user'`); + Utils.exec(`bash -c 'mkdir -p ${GLib.get_user_cache_dir()}/ags/user'`); Utils.exec(`touch ${this._todoPath}`); Utils.writeFile("[]", this._todoPath).then(() => { this._todoJson = JSON.parse(Utils.readFile(this._todoPath)) diff --git a/.config/ags/services/waifus.js b/.config/ags/services/waifus.js index 3674713e..f7f9b28c 100644 --- a/.config/ags/services/waifus.js +++ b/.config/ags/services/waifus.js @@ -5,64 +5,20 @@ import GLib from 'gi://GLib'; import Soup from 'gi://Soup?version=3.0'; import { fileExists } from './messages.js'; -class WaifuResponse extends Service { - static { - Service.register(this, - { - 'delta': ['string'], - }, - { - 'content': ['string'], - 'thinking': ['boolean'], - 'done': ['boolean'], - }); - } - - _role = ''; - _content = ''; - _thinking = false; - _done = false; - - constructor(role, content, thinking = false, done = false) { - super(); - this._role = role; - this._content = content; - this._thinking = thinking; - this._done = done; - } - - get done() { return this._done } - set done(isDone) { this._done = isDone; this.notify('done') } - - get role() { return this._role } - set role(role) { this._role = role; this.emit('changed') } - - get content() { return this._content } - set content(content) { - this._content = content; - this.notify('content') - this.emit('changed') - } - - get label() { return this._parserState.parsed + this._parserState.stack.join('') } - - get thinking() { return this._thinking } - set thinking(thinking) { - this._thinking = thinking; - this.notify('thinking') - this.emit('changed') - } - - addDelta(delta) { - if (this.thinking) { - this.thinking = false; - this.content = delta; - } - else { - this.content += delta; - } - this.emit('delta', delta); - } +function paramStringFromObj(params) { + return Object.entries(params) + .map(([key, value]) => { + if (Array.isArray(value)) { // If it's an array, repeat + if (value.length == 0) return ''; + let thisKey = `${encodeURIComponent(key)}=${encodeURIComponent(value[0])}` + for (let i = 1; i < value.length; i++) { + thisKey += `&${encodeURIComponent(key)}=${encodeURIComponent(value[i])}`; + } + return thisKey; + } + return `${key}=${value}`; + }) + .join('&'); } class WaifuService extends Service { @@ -76,9 +32,10 @@ class WaifuService extends Service { 'nekos': {}, 'pics': {}, } - _url = 'https://api.waifu.im/search'; + _baseUrl = 'https://api.waifu.im/search'; _mode = 'im'; // Allowed: im _responses = []; + _queries = []; _nsfw = false; _minHeight = 600; @@ -86,7 +43,8 @@ class WaifuService extends Service { Service.register(this, { 'initialized': [], 'clear': [], - 'newResponse': ['string'], + 'newResponse': ['int'], + 'updateResponse': ['int'], }); } @@ -97,71 +55,68 @@ class WaifuService extends Service { clear() { this._responses = []; + this._queries = []; this.emit('clear'); } get mode() { return this._mode } set mode(value) { this._mode = value; - this._url = this._endpoints[this._mode]; + this._baseUrl = this._endpoints[this._mode]; } get nsfw() { return this._nsfw } set nsfw(value) { this._nsfw = value } + get queries() { return this._queries } get responses() { return this._responses } - readResponseRecursive(stream, response) { - stream.read_line_async( - 0, null, - (stream, res) => { - if (!stream) return; - const [bytes] = stream.read_line_finish(res); - const line = this._decoder.decode(bytes); - if (line && line != '') { - let data = line.substr(6); - if (data == '[DONE]') return; - try { - const result = JSON.parse(data); - if (result.choices[0].finish_reason === 'stop') { - response.done = true; - return; - } - response.addDelta(result.choices[0].delta.content); - } - catch { - response.addDelta(line + '\n'); - } - } - this.readResponseRecursive(stream, response); - }); - } - fetch(msg) { + const newMessageId = this._responses.length; const taglist = msg.split(' '); - this.emit('newResponse', msg); - this._responses.push(msg); - + this._queries.push(taglist); + this.emit('newResponse', newMessageId); + // Construct body/headers const params = { 'included_tags': taglist, 'height': `>=${this._minHeight}`, 'nsfw': this._nsfw, }; - - const session = new Soup.Session(); - const message = new Soup.Message({ + const paramString = paramStringFromObj(params); + console.log(paramString); + // Fetch + const options = { method: 'GET', - uri: GLib.Uri.parse(this._url, GLib.UriFlags.NONE), - }); - session.send_message(message, (session, message) => { - if (message.status_code === 200) { - const responseBody = message.response_body.data; - const data = JSON.parse(responseBody); - // Process the response data as needed - console.log(data); - log(data); - } else { - logError('Request failed with status code: ' + message.status_code); - } - }); + headers: this._headers[this._mode], + }; + Utils.fetch(`${this._baseUrl}?${paramString}`, options) + .then(result => result.text()) // Parse + .then((dataString) => { // Store interesting stuff and emit + const parsedData = JSON.parse(dataString); + if (!parsedData.images) this._responses.push({ + signature: -1, + url: '', + source: '', + dominant_color: '#383A40', + is_nsfw: false, + width: 0, + height: 0, + tags: [], + }); + else { + const imageData = parsedData.images[0]; + this._responses.push({ + signature: imageData?.signature || -1, + url: imageData?.url || undefined, + source: imageData?.source, + dominant_color: imageData?.dominant_color || '#000000', + is_nsfw: imageData?.is_nsfw || false, + width: imageData?.width || 0, + height: imageData?.height || 0, + tags: imageData?.tags.map(obj => obj["name"]) || [], + }); + } + this.emit('updateResponse', newMessageId); + }) + .catch(console.error) } } diff --git a/.config/ags/style.css b/.config/ags/style.css index 4378de07..4582f992 100644 --- a/.config/ags/style.css +++ b/.config/ags/style.css @@ -1839,7 +1839,8 @@ tooltip { .sidebar-chat-indicator { border-radius: 9999px; -gtk-outline-radius: 9999px; - min-width: 0.136rem; } + min-width: 0.136rem; + background-color: #eae0e4; } .sidebar-chat-indicator-user { background-color: #eae0e4; } @@ -1854,7 +1855,6 @@ tooltip { font-family: 'Gabarito', 'Poppins', 'Lexend', sans-serif; padding: 0.341rem; margin-left: -0.136rem; - padding: 0.341rem; padding-left: 0.818rem; } .sidebar-chat-txtblock { @@ -1980,6 +1980,27 @@ tooltip { .sidebar-pin-enabled:active { background-color: #dda0d4; } +.sidebar-waifu-heading { + font-family: 'Gabarito', 'Poppins', 'Lexend', sans-serif; + padding: 0.341rem; + margin-left: -0.136rem; + padding-left: 0.818rem; } + +.sidebar-waifu-content { + margin-left: 0.682rem; } + +.sidebar-waifu-txt { + font-family: 'Lexend', 'Noto Sans', sans-serif; + margin-left: 0.682rem; } + +.sidebar-waifu-image { + margin-left: 0.682rem; + border-radius: 1.159rem; + -gtk-outline-radius: 1.159rem; + background-size: cover; + background-repeat: no-repeat; + background-position: center; } + .session-bg { margin-top: -2.727rem; background-color: rgba(16, 13, 16, 0.64); } diff --git a/.config/ags/widgets/dock/dock.js b/.config/ags/widgets/dock/dock.js index 4714d268..c2ac0801 100644 --- a/.config/ags/widgets/dock/dock.js +++ b/.config/ags/widgets/dock/dock.js @@ -129,6 +129,7 @@ const Taskbar = () => Widget.Box({ if (!address) return; const removedButton = box._map.get(address); + if (!removedButton) return; removedButton.revealChild = false; Utils.timeout(ANIMATION_TIME, () => { diff --git a/.config/ags/widgets/indicators/musiccontrols.js b/.config/ags/widgets/indicators/musiccontrols.js index 7e59f5a5..ce590691 100644 --- a/.config/ags/widgets/indicators/musiccontrols.js +++ b/.config/ags/widgets/indicators/musiccontrols.js @@ -17,7 +17,7 @@ function expandTilde(path) { } } -const LIGHTDARK_FILE_LOCATION = '~/.cache/ags/user/colormode.txt' +const LIGHTDARK_FILE_LOCATION = `${GLib.get_user_cache_dir()}/ags/user/colormode.txt`; const lightDark = Utils.readFile(expandTilde(LIGHTDARK_FILE_LOCATION)).trim(); const COVER_COLORSCHEME_SUFFIX = '_colorscheme.css'; const PREFERRED_PLAYER = 'plasma-browser-integration'; @@ -165,7 +165,7 @@ const CoverArt = ({ player, ...rest }) => Box({ `${App.configDir}/scripts/color_generation/generate_colors_material.py --path '${coverPath}' > ${App.configDir}/scss/_musicmaterial.scss ${lightDark}`]) .then(() => { exec(`wal -i "${player.coverPath}" -n -t -s -e -q ${lightDark}`) - exec(`bash -c "cp ~/.cache/wal/colors.scss ${App.configDir}/scss/_musicwal.scss"`) + exec(`cp ${GLib.get_user_cache_dir()}/wal/colors.scss ${App.configDir}/scss/_musicwal.scss`); exec(`sassc ${App.configDir}/scss/_music.scss ${stylePath}`); self.css = `background-image: url('${coverPath}');`; App.applyCss(`${stylePath}`); diff --git a/.config/ags/widgets/overview/miscfunctions.js b/.config/ags/widgets/overview/miscfunctions.js index 8cc5d4b8..8a376742 100644 --- a/.config/ags/widgets/overview/miscfunctions.js +++ b/.config/ags/widgets/overview/miscfunctions.js @@ -21,12 +21,12 @@ export function launchCustomCommand(command) { execAsync([`bash`, `-c`, `${App.configDir}/scripts/color_generation/switchcolor.sh`, `&`]).catch(print); } else if (args[0] == '>light') { // Light mode - execAsync([`bash`, `-c`, `mkdir -p ~/.cache/ags/user && echo "-l" > ~/.cache/ags/user/colormode.txt`]) + execAsync([`bash`, `-c`, `mkdir -p ${GLib.get_user_cache_dir()}/ags/user && echo "-l" > ${GLib.get_user_cache_dir()}/ags/user/colormode.txt`]) .then(execAsync(['bash', '-c', `${App.configDir}/scripts/color_generation/switchwall.sh --noswitch`]).catch(print)) .catch(print); } else if (args[0] == '>dark') { // Dark mode - execAsync([`bash`, `-c`, `mkdir -p ~/.cache/ags/user && echo "" > ~/.cache/ags/user/colormode.txt`]) + execAsync([`bash`, `-c`, `mkdir -p ${GLib.get_user_cache_dir()}/ags/user && echo "" > ${GLib.get_user_cache_dir()}/ags/user/colormode.txt`]) .then(execAsync(['bash', '-c', `${App.configDir}/scripts/color_generation/switchwall.sh --noswitch`]).catch(print)) .catch(print); } @@ -34,10 +34,10 @@ export function launchCustomCommand(command) { execAsync([`bash`, `-c`, `${App.configDir}/scripts/color_generation/applycolor.sh --bad-apple`]).catch(print); } else if (args[0] == '>material') { // Light mode - execAsync([`bash`, `-c`, `mkdir -p ~/.cache/ags/user && echo "material" > ~/.cache/ags/user/colorbackend.txt`]).catch(print); + execAsync([`bash`, `-c`, `mkdir -p ${GLib.get_user_cache_dir()}/ags/user && echo "material" > ${GLib.get_user_cache_dir()}/ags/user/colorbackend.txt`]).catch(print); } else if (args[0] == '>pywal') { // Dark mode - execAsync([`bash`, `-c`, `mkdir -p ~/.cache/ags/user && echo "pywal" > ~/.cache/ags/user/colorbackend.txt`]).catch(print); + execAsync([`bash`, `-c`, `mkdir -p ${GLib.get_user_cache_dir()}/ags/user && echo "pywal" > ${GLib.get_user_cache_dir()}/ags/user/colorbackend.txt`]).catch(print); } else if (args[0] == '>todo') { // Todo Todo.add(args.slice(1).join(' ')); diff --git a/.config/ags/widgets/sideleft/apis/chatgpt.js b/.config/ags/widgets/sideleft/apis/chatgpt.js index 898a6a3f..8faf0383 100644 --- a/.config/ags/widgets/sideleft/apis/chatgpt.js +++ b/.config/ags/widgets/sideleft/apis/chatgpt.js @@ -204,39 +204,24 @@ export const chatGPTView = Scrollable({ } }); +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 }), - Button({ - className: 'sidebar-chat-chip sidebar-chat-chip-action txt txt-small', - onClicked: () => chatContent.add(SystemMessage( - `Key stored in:\n\`${ChatGPT.keyPath}\`\nTo update this key, type \`/key YOUR_API_KEY\``, - '/key', - chatGPTView)), - setup: setupCursorHover, - label: '/key', - }), - Button({ - className: 'sidebar-chat-chip sidebar-chat-chip-action txt txt-small', - onClicked: () => chatContent.add(SystemMessage( - `Currently using \`${ChatGPT.modelName}\``, - '/model', - chatGPTView - )), - setup: setupCursorHover, - label: '/model', - }), - Button({ - className: 'sidebar-chat-chip sidebar-chat-chip-action txt txt-small', - onClicked: () => clearChat(), - setup: setupCursorHover, - label: '/clear', - }), + CommandButton('/key'), + CommandButton('/model'), + CommandButton('/clear'), ] }); -export const chatGPTSendMessage = (text) => { +export const sendMessage = (text) => { // Check if text or API key is empty if (text.length == 0) return; if (ChatGPT.key.length == 0) { diff --git a/.config/ags/widgets/sideleft/apis/chatgpt_chatmessage.js b/.config/ags/widgets/sideleft/apis/chatgpt_chatmessage.js index f6f396b4..2532ea45 100644 --- a/.config/ags/widgets/sideleft/apis/chatgpt_chatmessage.js +++ b/.config/ags/widgets/sideleft/apis/chatgpt_chatmessage.js @@ -3,7 +3,7 @@ import { App, Utils, Widget } from '../../../imports.js'; const { Box, Button, Entry, EventBox, Icon, Label, Revealer, Scrollable, Stack } = Widget; const { execAsync, exec } = Utils; import { MaterialIcon } from "../../../lib/materialicon.js"; -import { convert } from "../../../lib/md2pango.js"; +import md2pango from "../../../lib/md2pango.js"; import GtkSource from "gi://GtkSource?version=3.0"; const CUSTOM_SOURCEVIEW_SCHEME_PATH = `${App.configDir}/data/sourceviewtheme.xml`; @@ -168,7 +168,7 @@ const MessageContent = (content) => { const lastLabel = kids[kids.length - 1]; const blockContent = lines.slice(lastProcessed, index).join('\n'); if (!inCode) { - lastLabel.label = convert(blockContent); + lastLabel.label = md2pango(blockContent); contentBox.add(CodeBlock('', codeBlockRegex.exec(line)[1])); } else { @@ -185,7 +185,7 @@ const MessageContent = (content) => { const kids = self.get_children(); const lastLabel = kids[kids.length - 1]; const blockContent = lines.slice(lastProcessed, index).join('\n'); - lastLabel.label = convert(blockContent); + lastLabel.label = md2pango(blockContent); contentBox.add(Divider()); contentBox.add(TextBlock()); lastProcessed = index + 1; @@ -196,7 +196,7 @@ const MessageContent = (content) => { const lastLabel = kids[kids.length - 1]; let blockContent = lines.slice(lastProcessed, lines.length).join('\n'); if (!inCode) - lastLabel.label = `${convert(blockContent)}${useCursor ? CHATGPT_CURSOR : ''}`; + lastLabel.label = `${md2pango(blockContent)}${useCursor ? CHATGPT_CURSOR : ''}`; else lastLabel._updateText(blockContent); } @@ -208,7 +208,7 @@ const MessageContent = (content) => { // xalign: 0, // wrap: true, // selectable: true, - // label: '------------------------------\n' + convert(content), + // label: '------------------------------\n' + md2pango(content), // })) contentBox.show_all(); }] diff --git a/.config/ags/widgets/sideleft/apis/waifu.js b/.config/ags/widgets/sideleft/apis/waifu.js index e4e7dd63..b4bb650b 100644 --- a/.config/ags/widgets/sideleft/apis/waifu.js +++ b/.config/ags/widgets/sideleft/apis/waifu.js @@ -6,6 +6,19 @@ import { MaterialIcon } from "../../../lib/materialicon.js"; import { setupCursorHover, setupCursorHoverInfo } from "../../../lib/cursorhover.js"; import WaifuService from '../../../services/waifus.js'; +const MESSAGE_SCROLL_DELAY = 13; // In milliseconds, the time before an updated message scrolls to bottom + +// Create cache folder and clear pics from previous session +Utils.exec(`bash -c 'mkdir -p ${GLib.get_user_cache_dir()}/ags/media/waifus'`); +Utils.exec(`bash -c 'rm ${GLib.get_user_cache_dir()}/ags/media/waifus/*'`); + +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', @@ -15,17 +28,90 @@ export const waifuTabIcon = Box({ ] }); +const WaifuImage = (taglist) => { + const colorIndicator = Box({ + className: `sidebar-chat-indicator`, + }); + const downloadIndicator = Label({ + className: 'sidebar-waifu-txt txt-smallie txt', + xalign: 0, + label: 'Downloading image...', + }); + const blockHeading = Box({ + className: 'sidebar-waifu-content', + vertical: true, + children: [ + Box({ + children: taglist.map((tag) => CommandButton(tag)) + }), + downloadIndicator, + ] + }); + const blockImage = Box({ + hpack: 'start', + className: 'sidebar-waifu-image', + }) + const thisBlock = Box({ + className: 'sidebar-chat-message', + properties: [ + ['update', (imageData) => { + const { signature, url, source, dominant_color, is_nsfw, width, height, tags } = imageData; + const imagePath = `${GLib.get_user_cache_dir()}/ags/media/waifus/${signature}`; + // Start download + Utils.execAsync(['bash', '-c', `wget -O '${imagePath}' '${url}'`]) + .then(() => { + blockImage.css = `background-image:url('${imagePath}');`; + downloadIndicator.destroy(); + }) + .catch(print); + colorIndicator.css = `background-color: ${dominant_color};`; + // Width allocation + const widgetWidth = Math.floor(waifuContent.get_allocated_width() * 0.75); // idk tbh + blockImage.set_size_request(widgetWidth, Math.ceil(widgetWidth * height / width)); + }], + ], + children: [ + colorIndicator, + Box({ + vertical: true, + className: 'spacing-v-10', + children: [ + blockHeading, + blockImage, + ] + }) + ], + setup: (self) => Utils.timeout(MESSAGE_SCROLL_DELAY, () => { + var adjustment = waifuView.get_vadjustment(); + adjustment.set_value(adjustment.get_upper() - adjustment.get_page_size()); + }) + }); + return thisBlock; +} + const waifuContent = Box({ className: 'spacing-v-15', vertical: true, + vexpand: true, + properties: [ + ['map', new Map()], + ], connections: [ [WaifuService, (box, id) => { - const message = WaifuService.responses[id]; - if (!message) return; - box.add(Label({ - label: message, - })) + if (id === undefined) return; + console.log('new', WaifuService.queries[id]); + const newImageBlock = WaifuImage(WaifuService.queries[id]); + box.add(newImageBlock); + box.show_all(); + box._map.set(id, newImageBlock); }, 'newResponse'], + [WaifuService, (box, id) => { + if (id === undefined) return; + const data = WaifuService.responses[id]; + if (!data) return; + const imageBlock = box._map.get(id); + imageBlock._update(data); + }, 'updateResponse'], ] }); @@ -55,18 +141,21 @@ export const waifuCommands = Box({ className: 'spacing-h-5', children: [ Box({ hexpand: true }), - Button({ - className: 'sidebar-chat-chip sidebar-chat-chip-action txt txt-small', - onClicked: () => { - // command do something - }, - setup: setupCursorHover, - label: '/call', - }), + CommandButton('/clear'), ] }); -export const waifuCallAPI = (text) => { +export const sendMessage = (text) => { // Do something on send - WaifuService.fetch(text); + // Commands + if (text.startsWith('/')) { + if (text.startsWith('/clear')) { + const kids = waifuContent.get_children(); + for (let i = 0; i < kids.length; i++) { + const child = kids[i]; + child.destroy(); + } + } + } + else WaifuService.fetch(text); } \ No newline at end of file diff --git a/.config/ags/widgets/sideleft/apiwidgets.js b/.config/ags/widgets/sideleft/apiwidgets.js index 573b65da..3c9b565d 100644 --- a/.config/ags/widgets/sideleft/apiwidgets.js +++ b/.config/ags/widgets/sideleft/apiwidgets.js @@ -5,8 +5,8 @@ const { execAsync, exec } = Utils; import { setupCursorHover, setupCursorHoverInfo } from "../../lib/cursorhover.js"; // APIs import ChatGPT from '../../services/chatgpt.js'; -import { chatGPTView, chatGPTCommands, chatGPTSendMessage, chatGPTTabIcon } from './apis/chatgpt.js'; -import { waifuView, waifuCommands, waifuCallAPI, waifuTabIcon } from './apis/waifu.js'; +import { chatGPTView, chatGPTCommands, sendMessage as chatGPTSendMessage, chatGPTTabIcon } from './apis/chatgpt.js'; +import { waifuView, waifuCommands, sendMessage as waifuSendMessage, waifuTabIcon } from './apis/waifu.js'; const APIS = [ { @@ -19,7 +19,7 @@ const APIS = [ }, { name: 'Waifus', - sendCommand: waifuCallAPI, + sendCommand: waifuSendMessage, contentWidget: waifuView, commandBar: waifuCommands, tabIcon: waifuTabIcon,