diff --git a/.config/ags/assets/icons/ai-openai-symbolic.svg b/.config/ags/assets/icons/ai-openai-symbolic.svg
new file mode 120000
index 000000000..c9ee0b32f
--- /dev/null
+++ b/.config/ags/assets/icons/ai-openai-symbolic.svg
@@ -0,0 +1 @@
+openai-symbolic.svg
\ No newline at end of file
diff --git a/.config/ags/assets/icons/ai-oxygen-symbolic.svg b/.config/ags/assets/icons/ai-oxygen-symbolic.svg
new file mode 100644
index 000000000..5e1cc1937
--- /dev/null
+++ b/.config/ags/assets/icons/ai-oxygen-symbolic.svg
@@ -0,0 +1,54 @@
+
+
diff --git a/.config/ags/assets/icons/ai-zukijourney.png b/.config/ags/assets/icons/ai-zukijourney.png
new file mode 100644
index 000000000..917335e72
Binary files /dev/null and b/.config/ags/assets/icons/ai-zukijourney.png differ
diff --git a/.config/ags/config.js b/.config/ags/config.js
index 2e13709ef..59d5e74ba 100644
--- a/.config/ags/config.js
+++ b/.config/ags/config.js
@@ -17,6 +17,7 @@ import Session from './modules/session/main.js';
import SideLeft from './modules/sideleft/main.js';
import SideRight from './modules/sideright/main.js';
+const COMPILED_STYLE_DIR = `${GLib.get_user_cache_dir()}/ags/user/generated`
const range = (length, start = 1) => Array.from({ length }, (_, i) => i + start);
function forMonitors(widget) {
const n = Gdk.Display.get_default()?.get_n_monitors() || 1;
@@ -27,9 +28,8 @@ function forMonitors(widget) {
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
async function applyStyle() {
- const COMPILED_STYLE_DIR = `${GLib.get_user_cache_dir()}/ags/user/generated`
Utils.exec(`mkdir -p ${COMPILED_STYLE_DIR}`);
- Utils.exec(`sassc ${App.configDir}/scss/main.scss ${COMPILED_STYLE_DIR}/style.css`);
+ Utils.exec(`sass ${App.configDir}/scss/main.scss ${COMPILED_STYLE_DIR}/style.css`);
App.resetCss();
App.applyCss(`${COMPILED_STYLE_DIR}/style.css`);
console.log('[LOG] Styles loaded')
@@ -56,7 +56,7 @@ const Windows = () => [
];
const CLOSE_ANIM_TIME = 210; // Longer than actual anim time to make sure widgets animate fully
export default {
- css: `${App.configDir}/style.css`,
+ css: `${COMPILED_STYLE_DIR}/style.css`,
stackTraceOnError: true,
closeWindowDelay: { // For animations
'sideright': CLOSE_ANIM_TIME,
diff --git a/.config/ags/modules/.miscutils/md2pango.js b/.config/ags/modules/.miscutils/md2pango.js
index 054752b14..1c868757b 100644
--- a/.config/ags/modules/.miscutils/md2pango.js
+++ b/.config/ags/modules/.miscutils/md2pango.js
@@ -83,4 +83,7 @@ console.log('uwu');
\`\`\`
- Random instruction thing
- To update arch lincox, run \`sudo pacman -Syu\`
+\`\`\`tex
+\\frac{d}{dx} \\left( \\frac{x-438}{x^2+23x-7} \\right) = \\frac{-x^2 + 869}{(x^2+23x-7)^2} hmmmmmm \\frac{d}{dx} \\left( \\frac{x-438}{x^2+23x-7} \\right) = \\frac{-x^2 + 869}{(x^2+23x-7)^2}
+\`\`\`
`;
\ No newline at end of file
diff --git a/.config/ags/modules/bar/music.js b/.config/ags/modules/bar/music.js
index f0865b879..9dc4b013a 100644
--- a/.config/ags/modules/bar/music.js
+++ b/.config/ags/modules/bar/music.js
@@ -1,12 +1,21 @@
+const { GLib } = imports.gi;
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
import Mpris from 'resource:///com/github/Aylur/ags/service/mpris.js';
-const { Box, Label, Overlay, Revealer } = Widget;
+const { Box, Button, EventBox, Label, Overlay, Revealer, Scrollable } = Widget;
const { execAsync, exec } = Utils;
import { AnimatedCircProg } from "../.commonwidgets/cairo_circularprogress.js";
import { MaterialIcon } from '../.commonwidgets/materialicon.js';
import { showMusicControls } from '../../variables.js';
+const CUSTOM_MODULE_CONTENT_INTERVAL_FILE = `${GLib.get_home_dir()}/.cache/ags/user/scripts/custom-module-interval.txt`;
+const CUSTOM_MODULE_CONTENT_SCRIPT = `${GLib.get_home_dir()}/.cache/ags/user/scripts/custom-module-poll.sh`;
+const CUSTOM_MODULE_LEFTCLICK_SCRIPT = `${GLib.get_home_dir()}/.cache/ags/user/scripts/custom-module-leftclick.sh`;
+const CUSTOM_MODULE_RIGHTCLICK_SCRIPT = `${GLib.get_home_dir()}/.cache/ags/user/scripts/custom-module-rightclick.sh`;
+const CUSTOM_MODULE_MIDDLECLICK_SCRIPT = `${GLib.get_home_dir()}/.cache/ags/user/scripts/custom-module-middleclick.sh`;
+const CUSTOM_MODULE_SCROLLUP_SCRIPT = `${GLib.get_home_dir()}/.cache/ags/user/scripts/custom-module-scrollup.sh`;
+const CUSTOM_MODULE_SCROLLDOWN_SCRIPT = `${GLib.get_home_dir()}/.cache/ags/user/scripts/custom-module-scrolldown.sh`;
+
function trimTrackTitle(title) {
if (!title) return '';
const cleanPatterns = [
@@ -17,10 +26,10 @@ function trimTrackTitle(title) {
return title;
}
-const BarGroup = ({ child }) => Widget.Box({
+const BarGroup = ({ child }) => Box({
className: 'bar-group-margin bar-sides',
children: [
- Widget.Box({
+ Box({
className: 'bar-group bar-group-standalone bar-group-pad-system',
children: [child],
}),
@@ -34,7 +43,7 @@ const BarResource = (name, icon, command) => {
hpack: 'center',
});
const resourceProgress = Overlay({
- child: Widget.Box({
+ child: Box({
vpack: 'center',
className: 'bar-batt',
homogeneous: true,
@@ -93,14 +102,14 @@ const switchToRelativeWorkspace = async (self, num) => {
export default () => {
// TODO: use cairo to make button bounce smaller on click, if that's possible
- const playingState = Widget.Box({ // Wrap a box cuz overlay can't have margins itself
+ const playingState = Box({ // Wrap a box cuz overlay can't have margins itself
homogeneous: true,
- children: [Widget.Overlay({
- child: Widget.Box({
+ children: [Overlay({
+ child: Box({
vpack: 'center',
className: 'bar-music-playstate',
homogeneous: true,
- children: [Widget.Label({
+ children: [Label({
vpack: 'center',
className: 'bar-music-playstate-txt',
justification: 'center',
@@ -121,9 +130,9 @@ export default () => {
]
})]
});
- const trackTitle = Widget.Scrollable({
+ const trackTitle = Scrollable({
hexpand: true,
- child: Widget.Label({
+ child: Label({
className: 'txt-smallie txt-onSurfaceVariant',
setup: (self) => self.hook(Mpris, label => {
const mpris = Mpris.getPlayer('');
@@ -142,30 +151,57 @@ export default () => {
trackTitle,
]
})
- const systemResources = BarGroup({
- child: Box({
- children: [
- BarResource('RAM Usage', 'memory', `LANG=C free | awk '/^Mem/ {printf("%.2f\\n", ($3/$2) * 100)}'`),
- Revealer({
- revealChild: true,
- transition: 'slide_left',
- transitionDuration: 200,
- child: Box({
- className: 'spacing-h-10 margin-left-10',
- children: [
- BarResource('Swap Usage', 'swap_horiz', `LANG=C free | awk '/^Swap/ {if ($2 > 0) printf("%.2f\\n", ($3/$2) * 100); else print "0";}'`),
- BarResource('CPU Usage', 'settings_motion_mode', `LANG=C top -bn1 | grep Cpu | sed 's/\\,/\\./g' | awk '{print $2}'`),
- ]
- }),
- setup: (self) => self.hook(Mpris, label => {
- const mpris = Mpris.getPlayer('');
- self.revealChild = (!mpris);
+ const SystemResourcesOrCustomModule = () => {
+ // Check if ~/.cache/ags/user/scripts/custom-module-poll.sh exists
+ if (GLib.file_test(CUSTOM_MODULE_CONTENT_SCRIPT, GLib.FileTest.EXISTS)) {
+ const interval = Number(Utils.readFile(CUSTOM_MODULE_CONTENT_INTERVAL_FILE)) || 5000;
+ return BarGroup({
+ child: Button({
+ child: Label({
+ className: 'txt-smallie txt-onSurfaceVariant',
+ useMarkup: true,
+ setup: (self) => Utils.timeout(1, () => {
+ self.label = exec(CUSTOM_MODULE_CONTENT_SCRIPT);
+ self.poll(interval, (self) => {
+ const content = exec(CUSTOM_MODULE_CONTENT_SCRIPT);
+ self.label = content;
+ })
+ })
}),
+ onPrimaryClickRelease: () => execAsync(CUSTOM_MODULE_LEFTCLICK_SCRIPT).catch(print),
+ onSecondaryClickRelease: () => execAsync(CUSTOM_MODULE_RIGHTCLICK_SCRIPT).catch(print),
+ onMiddleClickRelease: () => execAsync(CUSTOM_MODULE_MIDDLECLICK_SCRIPT).catch(print),
+ onScrollUp: () => execAsync(CUSTOM_MODULE_SCROLLUP_SCRIPT).catch(print),
+ onScrollDown: () => execAsync(CUSTOM_MODULE_SCROLLDOWN_SCRIPT).catch(print),
})
- ],
- })
- });
- return Widget.EventBox({
+ });
+ } else {
+ return BarGroup({
+ child: Box({
+ children: [
+ BarResource('RAM Usage', 'memory', `LANG=C free | awk '/^Mem/ {printf("%.2f\\n", ($3/$2) * 100)}'`),
+ Revealer({
+ revealChild: true,
+ transition: 'slide_left',
+ transitionDuration: 200,
+ child: Box({
+ className: 'spacing-h-10 margin-left-10',
+ children: [
+ BarResource('Swap Usage', 'swap_horiz', `LANG=C free | awk '/^Swap/ {if ($2 > 0) printf("%.2f\\n", ($3/$2) * 100); else print "0";}'`),
+ BarResource('CPU Usage', 'settings_motion_mode', `LANG=C top -bn1 | grep Cpu | sed 's/\\,/\\./g' | awk '{print $2}'`),
+ ]
+ }),
+ setup: (self) => self.hook(Mpris, label => {
+ const mpris = Mpris.getPlayer('');
+ self.revealChild = (!mpris);
+ }),
+ })
+ ],
+ })
+ });
+ }
+ }
+ return EventBox({
onScrollUp: (self) => switchToRelativeWorkspace(self, -1),
onScrollDown: (self) => switchToRelativeWorkspace(self, +1),
onPrimaryClickRelease: () => showMusicControls.setValue(!showMusicControls.value),
@@ -175,7 +211,7 @@ export default () => {
className: 'spacing-h-5',
children: [
BarGroup({ child: musicStuff }),
- systemResources,
+ SystemResourcesOrCustomModule(),
]
})
});
diff --git a/.config/ags/modules/bar/system.js b/.config/ags/modules/bar/system.js
index 9a8fc86a8..5efdc0fdc 100644
--- a/.config/ags/modules/bar/system.js
+++ b/.config/ags/modules/bar/system.js
@@ -75,7 +75,7 @@ const Utilities = () => Box({
children: [
UtilButton({
name: 'Screen snip', icon: 'screenshot_region', onClicked: () => {
- Utils.execAsync(['bash', '-c', `grim -g "$(slurp -d -c e2e2e2BB -b 31313122 -s 00000000)" - | wl-copy &`])
+ Utils.execAsync(`${App.configDir}/scripts/grimblast.sh`)
.catch(print)
}
}),
diff --git a/.config/ags/modules/indicators/musiccontrols.js b/.config/ags/modules/indicators/musiccontrols.js
index 4cf052135..6575c69ff 100644
--- a/.config/ags/modules/indicators/musiccontrols.js
+++ b/.config/ags/modules/indicators/musiccontrols.js
@@ -9,6 +9,8 @@ const { Box, EventBox, Icon, Scrollable, Label, Button, Revealer } = Widget;
import { AnimatedCircProg } from "../.commonwidgets/cairo_circularprogress.js";
import { showMusicControls } from '../../variables.js';
+const COMPILED_STYLE_DIR = `${GLib.get_user_cache_dir()}/ags/user/generated`
+
function expandTilde(path) {
if (path.startsWith('~')) {
return GLib.get_home_dir() + path.slice(1);
@@ -186,22 +188,19 @@ const CoverArt = ({ player, ...rest }) => {
// Player closed
// Note that cover path still remains, so we're checking title
if (!player || player.trackTitle == "") {
- self.css = `background-image: none;`;
- App.applyCss(`${App.configDir}/style.css`);
+ App.applyCss(`${COMPILED_STYLE_DIR}/style.css`);
return;
}
const coverPath = player.coverPath;
const stylePath = `${player.coverPath}${lightDark}${COVER_COLORSCHEME_SUFFIX}`;
if (player.coverPath == lastCoverPath) { // Since 'notify::cover-path' emits on cover download complete
- // Utils.timeout(200, () => { self.css = `background-image: url('${coverPath}');`; });
Utils.timeout(200, () => self.attribute.showImage(self, coverPath));
}
lastCoverPath = player.coverPath;
// If a colorscheme has already been generated, skip generation
if (fileExists(stylePath)) {
- // Utils.timeout(200, () => { self.css = `background-image: url('${coverPath}');`; });
self.attribute.showImage(self, coverPath)
App.applyCss(stylePath);
return;
@@ -213,8 +212,7 @@ const CoverArt = ({ player, ...rest }) => {
.then(() => {
exec(`wal -i "${player.coverPath}" -n -t -s -e -q ${lightDark}`)
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}');`;
+ exec(`sass ${App.configDir}/scss/_music.scss ${stylePath}`);
Utils.timeout(200, () => self.attribute.showImage(self, coverPath));
App.applyCss(`${stylePath}`);
})
diff --git a/.config/ags/modules/sideleft/apis/ai_chatmessage.js b/.config/ags/modules/sideleft/apis/ai_chatmessage.js
index 5bc44f847..1c79b3a1c 100644
--- a/.config/ags/modules/sideleft/apis/ai_chatmessage.js
+++ b/.config/ags/modules/sideleft/apis/ai_chatmessage.js
@@ -3,16 +3,16 @@ 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 { Box, Button, Label, Icon, Scrollable } = Widget;
const { execAsync, exec } = Utils;
import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
import md2pango from '../../.miscutils/md2pango.js';
-
+const LATEX_DIR = `${GLib.get_user_cache_dir()}/ags/media/latex`;
const CUSTOM_SOURCEVIEW_SCHEME_PATH = `${App.configDir}/assets/themes/sourceviewtheme.xml`;
const CUSTOM_SCHEME_ID = 'custom';
const USERNAME = GLib.get_user_name();
-const CHATGPT_CURSOR = ' ...';
+const AI_MESSAGE_CURSOR = ' ...';
/////////////////////// Custom source view colorscheme /////////////////////////
@@ -34,13 +34,6 @@ 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' },
@@ -82,7 +75,69 @@ const TextBlock = (content = '') => Label({
label: content,
});
+Utils.execAsync(['bash', '-c', `rm ${LATEX_DIR}/*`])
+ .then(() => Utils.execAsync(['bash', '-c', `mkdir -p ${LATEX_DIR}`]))
+ .catch(() => {});
+const Latex = (content = '') => {
+ const latexViewArea = Box({
+ // vscroll: 'never',
+ // hscroll: 'automatic',
+ attribute: {
+ render: async (self, text) => {
+ if (text.length == 0) return;
+ const styleContext = self.get_style_context();
+ const fontSize = styleContext.get_property('font-size', Gtk.StateFlags.NORMAL);
+
+ const timeSinceEpoch = Date.now();
+ const fileName = `${timeSinceEpoch}.tex`;
+ const outFileName = `${timeSinceEpoch}-symbolic.svg`;
+ const scriptFileName = `${timeSinceEpoch}-render.sh`;
+ const filePath = `${LATEX_DIR}/${fileName}`;
+ const outFilePath = `${LATEX_DIR}/${outFileName}`;
+ const scriptFilePath = `${LATEX_DIR}/${scriptFileName}`;
+
+ Utils.writeFile(text, filePath).catch(print);
+ // Since MicroTex doesn't support file path input properly, we gotta cat it
+ // And escaping such a command is a fucking pain so I decided to just generate a script
+ // Note: MicroTex doesn't support `&=`
+ // You can add this line in the middle for debugging: echo "$text" > ${filePath}.tmp
+ const renderScript = `#!/usr/bin/env bash
+text=$(cat ${filePath} | sed 's/$/ \\\\\\\\/g' | sed 's/&=/=/g')
+LaTeX -headless -input="$text" -output=${outFilePath} -textsize=${fontSize * 1.1} -padding=0 -maxwidth=${latexViewArea.get_allocated_width() * 0.85}
+`;
+ Utils.writeFile(renderScript, scriptFilePath).catch(print);
+ Utils.exec(`chmod a+x ${scriptFilePath}`)
+ Utils.timeout(100, () => {
+ Utils.exec(`bash ${scriptFilePath}`);
+ Gtk.IconTheme.get_default().append_search_path(LATEX_DIR);
+ self.child?.destroy();
+ self.child = Gtk.Image.new_from_file(outFilePath);
+ })
+ }
+ },
+ setup: (self) => self.attribute.render(self, content).catch(print),
+ });
+ const wholeThing = Box({
+ className: 'sidebar-chat-latex',
+ homogeneous: true,
+ attribute: {
+ 'updateText': (text) => {
+ latexViewArea.attribute.render(latexViewArea, text).catch(print);
+ }
+ },
+ children: [Scrollable({
+ vscroll: 'never',
+ hscroll: 'automatic',
+ child: latexViewArea
+ })]
+ })
+ return wholeThing;
+}
+
const CodeBlock = (content = '', lang = 'txt') => {
+ if (lang == 'tex' || lang == 'latex') {
+ return Latex(content);
+ }
const topBar = Box({
className: 'sidebar-chat-codeblock-topbar',
children: [
@@ -203,7 +258,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 ? CHATGPT_CURSOR : ''}`;
+ lastLabel.label = `${md2pango(blockContent)}${useCursor ? AI_MESSAGE_CURSOR : ''}`;
else
lastLabel.attribute.updateText(blockContent);
}
diff --git a/.config/ags/modules/sideleft/apis/chatgpt.js b/.config/ags/modules/sideleft/apis/chatgpt.js
index b4ffaf869..c3e657aab 100644
--- a/.config/ags/modules/sideleft/apis/chatgpt.js
+++ b/.config/ags/modules/sideleft/apis/chatgpt.js
@@ -4,12 +4,13 @@ import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { Box, Button, Icon, Label, Revealer, Scrollable } = Widget;
-import ChatGPT from '../../../services/chatgpt.js';
+import GPTService from '../../../services/gpt.js';
import { setupCursorHover, setupCursorHoverInfo } from '../../.widgetutils/cursorhover.js';
import { SystemMessage, ChatMessage } from "./ai_chatmessage.js";
import { ConfigToggle, ConfigSegmentedSelection, ConfigGap } from '../../.commonwidgets/configwidgets.js';
import { markdownTest } from '../../.miscutils/md2pango.js';
import { MarginRevealer } from '../../.widgethacks/advancedrevealers.js';
+import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
Gtk.IconTheme.get_default().append_search_path(`${App.configDir}/assets/icons`);
@@ -19,7 +20,95 @@ export const chatGPTTabIcon = Icon({
icon: `openai-symbolic`,
});
-const ChatGPTInfo = () => {
+const ProviderSwitcher = () => {
+ const ProviderChoice = (id, provider) => {
+ const providerSelected = MaterialIcon('check', 'norm', {
+ setup: (self) => self.hook(GPTService, (self) => {
+ self.toggleClassName('invisible', GPTService.providerID !== id);
+ }, 'providerChanged')
+ });
+ return Button({
+ tooltipText: provider.description,
+ onClicked: () => {
+ GPTService.providerID = id;
+ providerList.revealChild = false;
+ indicatorChevron.label = 'expand_more';
+ },
+ child: Box({
+ className: 'spacing-h-10 txt',
+ children: [
+ Icon({
+ icon: provider['logo_name'],
+ className: 'txt-large'
+ }),
+ Label({
+ hexpand: true,
+ xalign: 0,
+ className: 'txt-small',
+ label: provider.name,
+ }),
+ providerSelected
+ ],
+ }),
+ setup: setupCursorHover,
+ });
+ }
+ const indicatorChevron = MaterialIcon('expand_more', 'norm');
+ const indicatorButton = Button({
+ tooltipText: 'Select ChatGPT-compatible API provider',
+ child: Box({
+ className: 'spacing-h-10 txt',
+ children: [
+ MaterialIcon('cloud', 'norm'),
+ Label({
+ hexpand: true,
+ xalign: 0,
+ className: 'txt-small',
+ label: GPTService.providerID,
+ setup: (self) => self.hook(GPTService, (self) => {
+ self.label = `${GPTService.providers[GPTService.providerID]['name']}`;
+ }, 'providerChanged')
+ }),
+ indicatorChevron,
+ ]
+ }),
+ onClicked: () => {
+ providerList.revealChild = !providerList.revealChild;
+ indicatorChevron.label = (providerList.revealChild ? 'expand_less' : 'expand_more');
+ },
+ setup: setupCursorHover,
+ });
+ const providerList = Revealer({
+ revealChild: false,
+ transition: 'slide_down',
+ transitionDuration: 180,
+ child: Box({
+ vertical: true, className: 'spacing-v-5 sidebar-chat-providerswitcher-list',
+ children: [
+ Box({ className: 'separator-line margin-top-5 margin-bottom-5' }),
+ Box({
+ className: 'spacing-v-5',
+ vertical: true,
+ setup: (self) => self.hook(GPTService, (self) => {
+ self.children = Object.entries(GPTService.providers)
+ .map(([id, provider]) => ProviderChoice(id, provider));
+ }, 'initialized'),
+ })
+ ]
+ })
+ })
+ return Box({
+ hpack: 'center',
+ vertical: true,
+ className: 'sidebar-chat-providerswitcher',
+ children: [
+ indicatorButton,
+ providerList,
+ ]
+ })
+}
+
+const GPTInfo = () => {
const openAiLogo = Icon({
hpack: 'center',
className: 'sidebar-chat-welcome-logo',
@@ -34,7 +123,7 @@ const ChatGPTInfo = () => {
className: 'txt txt-title-small sidebar-chat-welcome-txt',
wrap: true,
justify: Gtk.Justification.CENTER,
- label: 'Assistant (ChatGPT 3.5)',
+ label: 'Assistant (GPTs)',
}),
Box({
className: 'spacing-h-5',
@@ -49,7 +138,7 @@ const ChatGPTInfo = () => {
Button({
className: 'txt-subtext txt-norm icon-material',
label: 'info',
- tooltipText: 'Uses gpt-3.5-turbo.\nNot affiliated, endorsed, or sponsored by OpenAI.',
+ tooltipText: 'Uses gpt-3.5-turbo.\nNot affiliated, endorsed, or sponsored by OpenAI.\n\nPrivacy: OpenAI claims they do not use your data when you use their API.',
setup: setupCursorHoverInfo,
}),
]
@@ -58,14 +147,14 @@ const ChatGPTInfo = () => {
});
}
-export const ChatGPTSettings = () => MarginRevealer({
+const GPTSettings = () => MarginRevealer({
transition: 'slide_down',
revealChild: true,
extraSetup: (self) => self
- .hook(ChatGPT, (self) => Utils.timeout(200, () => {
+ .hook(GPTService, (self) => Utils.timeout(200, () => {
self.attribute.hide();
}), 'newMsg')
- .hook(ChatGPT, (self) => Utils.timeout(200, () => {
+ .hook(GPTService, (self) => Utils.timeout(200, () => {
self.attribute.show();
}), 'clear')
,
@@ -77,7 +166,7 @@ export const ChatGPTSettings = () => MarginRevealer({
hpack: 'center',
icon: 'casino',
name: 'Randomness',
- desc: 'ChatGPT\'s temperature value.\n Precise = 0\n Balanced = 0.5\n Creative = 1',
+ desc: 'The model\'s temperature value.\n Precise = 0\n Balanced = 0.5\n Creative = 1',
options: [
{ value: 0.00, name: 'Precise', },
{ value: 0.50, name: 'Balanced', },
@@ -85,7 +174,7 @@ export const ChatGPTSettings = () => MarginRevealer({
],
initIndex: 2,
onChange: (value, name) => {
- ChatGPT.temperature = value;
+ GPTService.temperature = value;
},
}),
ConfigGap({ vertical: true, size: 10 }), // Note: size can only be 5, 10, or 15
@@ -98,18 +187,18 @@ export const ChatGPTSettings = () => MarginRevealer({
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,
+ initValue: GPTService.cycleModels,
onChange: (self, newValue) => {
- ChatGPT.cycleModels = newValue;
+ GPTService.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,
+ desc: 'Tells the model:\n- It\'s a Linux sidebar assistant\n- Be brief and use bullet points',
+ initValue: GPTService.assistantPrompt,
onChange: (self, newValue) => {
- ChatGPT.assistantPrompt = newValue;
+ GPTService.assistantPrompt = newValue;
},
}),
]
@@ -124,8 +213,8 @@ export const OpenaiApiKeyInstructions = () => Box({
transition: 'slide_down',
transitionDuration: 150,
setup: (self) => self
- .hook(ChatGPT, (self, hasKey) => {
- self.revealChild = (ChatGPT.key.length == 0);
+ .hook(GPTService, (self, hasKey) => {
+ self.revealChild = (GPTService.key.length == 0);
}, 'hasKey')
,
child: Button({
@@ -134,17 +223,17 @@ export const OpenaiApiKeyInstructions = () => Box({
wrap: true,
className: 'txt sidebar-chat-welcome-txt',
justify: Gtk.Justification.CENTER,
- label: 'An OpenAI API key is required\nYou can grab one here, then enter it below'
+ label: 'An API key is required\nYou can grab one here, then enter it below'
}),
setup: setupCursorHover,
onClicked: () => {
- Utils.execAsync(['bash', '-c', `xdg-open https://platform.openai.com/api-keys &`]);
+ Utils.execAsync(['bash', '-c', `xdg-open ${GPTService.getKeyUrl}`]);
}
})
})]
});
-const chatGPTWelcome = Box({
+const GPTWelcome = () => Box({
vexpand: true,
homogeneous: true,
child: Box({
@@ -152,9 +241,9 @@ const chatGPTWelcome = Box({
vpack: 'center',
vertical: true,
children: [
- ChatGPTInfo(),
+ GPTInfo(),
OpenaiApiKeyInstructions(),
- ChatGPTSettings(),
+ GPTSettings(),
]
})
});
@@ -163,16 +252,16 @@ export const chatContent = Box({
className: 'spacing-v-15',
vertical: true,
setup: (self) => self
- .hook(ChatGPT, (box, id) => {
- const message = ChatGPT.messages[id];
+ .hook(GPTService, (box, id) => {
+ const message = GPTService.messages[id];
if (!message) return;
- box.add(ChatMessage(message, 'ChatGPT'))
+ box.add(ChatMessage(message, `Model (${GPTService.providers[GPTService.providerID]['name']})`))
}, 'newMsg')
,
});
const clearChat = () => {
- ChatGPT.clear();
+ GPTService.clear();
const children = chatContent.get_children();
for (let i = 0; i < children.length; i++) {
const child = children[i];
@@ -180,34 +269,6 @@ const clearChat = () => {
}
}
-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),
@@ -228,16 +289,16 @@ export const chatGPTCommands = Box({
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));
+ if (GPTService.key.length == 0) {
+ GPTService.key = text;
+ chatContent.add(SystemMessage(`Key saved to\n\`${GPTService.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('/model')) chatContent.add(SystemMessage(`Currently using \`${GPTService.modelName}\``, '/model', chatGPTView))
else if (text.startsWith('/prompt')) {
const firstSpaceIndex = text.indexOf(' ');
const prompt = text.slice(firstSpaceIndex + 1);
@@ -245,18 +306,18 @@ export const sendMessage = (text) => {
chatContent.add(SystemMessage(`Usage: \`/prompt MESSAGE\``, '/prompt', chatGPTView))
}
else {
- ChatGPT.addMessage('user', prompt)
+ GPTService.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 stored in:\n\`${GPTService.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));
+ GPTService.key = parts[1];
+ chatContent.add(SystemMessage(`Updated API Key at\n\`${GPTService.keyPath}\``, '/key', chatGPTView));
}
}
else if (text.startsWith('/test'))
@@ -265,6 +326,40 @@ export const sendMessage = (text) => {
chatContent.add(SystemMessage(`Invalid command.`, 'Error', chatGPTView))
}
else {
- ChatGPT.send(text);
+ GPTService.send(text);
}
-}
\ No newline at end of file
+}
+
+export const chatGPTView = Box({
+ vertical: true,
+ children: [
+ ProviderSwitcher(),
+ Scrollable({
+ className: 'sidebar-chat-viewport',
+ vexpand: true,
+ child: Box({
+ vertical: true,
+ children: [
+ GPTWelcome(),
+ 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());
+ })
+ }
+ })
+ ]
+});
\ No newline at end of file
diff --git a/.config/ags/modules/sideleft/apis/gemini.js b/.config/ags/modules/sideleft/apis/gemini.js
index 7f2e4e107..7da16e435 100644
--- a/.config/ags/modules/sideleft/apis/gemini.js
+++ b/.config/ags/modules/sideleft/apis/gemini.js
@@ -4,7 +4,7 @@ import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { Box, Button, Icon, Label, Revealer, Scrollable } = Widget;
-import Gemini from '../../../services/gemini.js';
+import GeminiService from '../../../services/gemini.js';
import { setupCursorHover, setupCursorHoverInfo } from '../../.widgetutils/cursorhover.js';
import { SystemMessage, ChatMessage } from "./ai_chatmessage.js";
import { ConfigToggle, ConfigSegmentedSelection, ConfigGap } from '../../.commonwidgets/configwidgets.js';
@@ -50,7 +50,7 @@ const GeminiInfo = () => {
Button({
className: 'txt-subtext txt-norm icon-material',
label: 'info',
- tooltipText: 'Uses gemini-pro.\nNot affiliated, endorsed, or sponsored by Google.',
+ tooltipText: 'Uses gemini-pro.\nNot affiliated, endorsed, or sponsored by Google.\n\nPrivacy: Google collects data for training by default.\nIf you mind, turn off Gemini Apps Activity in your account.',
setup: setupCursorHoverInfo,
}),
]
@@ -63,10 +63,10 @@ export const GeminiSettings = () => MarginRevealer({
transition: 'slide_down',
revealChild: true,
extraSetup: (self) => self
- .hook(Gemini, (self) => Utils.timeout(200, () => {
+ .hook(GeminiService, (self) => Utils.timeout(200, () => {
self.attribute.hide();
}), 'newMsg')
- .hook(Gemini, (self) => Utils.timeout(200, () => {
+ .hook(GeminiService, (self) => Utils.timeout(200, () => {
self.attribute.show();
}), 'clear')
,
@@ -86,7 +86,7 @@ export const GeminiSettings = () => MarginRevealer({
],
initIndex: 2,
onChange: (value, name) => {
- Gemini.temperature = value;
+ GeminiService.temperature = value;
},
}),
ConfigGap({ vertical: true, size: 10 }), // Note: size can only be 5, 10, or 15
@@ -99,9 +99,9 @@ export const GeminiSettings = () => MarginRevealer({
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,
+ initValue: GeminiService.assistantPrompt,
onChange: (self, newValue) => {
- Gemini.assistantPrompt = newValue;
+ GeminiService.assistantPrompt = newValue;
},
}),
]
@@ -116,8 +116,8 @@ export const GoogleAiInstructions = () => Box({
transition: 'slide_down',
transitionDuration: 150,
setup: (self) => self
- .hook(Gemini, (self, hasKey) => {
- self.revealChild = (Gemini.key.length == 0);
+ .hook(GeminiService, (self, hasKey) => {
+ self.revealChild = (GeminiService.key.length == 0);
}, 'hasKey')
,
child: Button({
@@ -155,8 +155,8 @@ export const chatContent = Box({
className: 'spacing-v-15',
vertical: true,
setup: (self) => self
- .hook(Gemini, (box, id) => {
- const message = Gemini.messages[id];
+ .hook(GeminiService, (box, id) => {
+ const message = GeminiService.messages[id];
if (!message) return;
box.add(ChatMessage(message, MODEL_NAME))
}, 'newMsg')
@@ -164,7 +164,7 @@ export const chatContent = Box({
});
const clearChat = () => {
- Gemini.clear();
+ GeminiService.clear();
const children = chatContent.get_children();
for (let i = 0; i < children.length; i++) {
const child = children[i];
@@ -172,34 +172,6 @@ const clearChat = () => {
}
}
-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),
@@ -220,16 +192,16 @@ export const geminiCommands = Box({
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));
+ if (GeminiService.key.length == 0) {
+ GeminiService.key = text;
+ chatContent.add(SystemMessage(`Key saved to\n\`${GeminiService.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('/model')) chatContent.add(SystemMessage(`Currently using \`${GeminiService.modelName}\``, '/model', geminiView))
else if (text.startsWith('/prompt')) {
const firstSpaceIndex = text.indexOf(' ');
const prompt = text.slice(firstSpaceIndex + 1);
@@ -237,18 +209,18 @@ export const sendMessage = (text) => {
chatContent.add(SystemMessage(`Usage: \`/prompt MESSAGE\``, '/prompt', geminiView))
}
else {
- Gemini.addMessage('user', prompt)
+ GeminiService.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 stored in:\n\`${GeminiService.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));
+ GeminiService.key = parts[1];
+ chatContent.add(SystemMessage(`Updated API Key at\n\`${GeminiService.keyPath}\``, '/key', geminiView));
}
}
else if (text.startsWith('/test'))
@@ -257,6 +229,37 @@ export const sendMessage = (text) => {
chatContent.add(SystemMessage(`Invalid command.`, 'Error', geminiView))
}
else {
- Gemini.send(text);
+ GeminiService.send(text);
}
-}
\ No newline at end of file
+}
+
+export const geminiView = Box({
+ homogeneous: true,
+ children: [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());
+ })
+ }
+ })]
+});
\ No newline at end of file
diff --git a/.config/ags/modules/sideleft/apiwidgets.js b/.config/ags/modules/sideleft/apiwidgets.js
index c141e32ca..ac89b6a66 100644
--- a/.config/ags/modules/sideleft/apiwidgets.js
+++ b/.config/ags/modules/sideleft/apiwidgets.js
@@ -6,7 +6,7 @@ const { execAsync, exec } = Utils;
import { setupCursorHover, setupCursorHoverInfo } from '../.widgetutils/cursorhover.js';
import { contentStack } from './sideleft.js';
// APIs
-import ChatGPT from '../../services/chatgpt.js';
+import GPTService from '../../services/gpt.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';
@@ -26,12 +26,12 @@ const APIS = [
placeholderText: 'Message Gemini...',
},
{
- name: 'Assistant (ChatGPT 3.5)',
+ name: 'Assistant (GPTs)',
sendCommand: chatGPTSendMessage,
contentWidget: chatGPTView,
commandBar: chatGPTCommands,
tabIcon: chatGPTTabIcon,
- placeholderText: 'Message ChatGPT...',
+ placeholderText: 'Message the model...',
},
{
name: 'Waifus',
@@ -65,9 +65,9 @@ export const chatEntry = TextView({
acceptsTab: false,
className: 'sidebar-chat-entry txt txt-smallie',
setup: (self) => self
- .hook(ChatGPT, (self) => {
- if (APIS[currentApiId].name != 'Assistant (ChatGPT 3.5)') return;
- self.placeholderText = (ChatGPT.key.length > 0 ? 'Message ChatGPT...' : 'Enter OpenAI API Key...');
+ .hook(GPTService, (self) => {
+ if (APIS[currentApiId].name != 'Assistant (GPTs)') return;
+ self.placeholderText = (GPTService.key.length > 0 ? 'Message the model...' : 'Enter API Key...');
}, 'hasKey')
.hook(Gemini, (self) => {
if (APIS[currentApiId].name != 'Assistant (Gemini Pro)') return;
diff --git a/.config/ags/scripts/color_generation/applycolor.sh b/.config/ags/scripts/color_generation/applycolor.sh
index d6d16b504..a8304abe8 100755
--- a/.config/ags/scripts/color_generation/applycolor.sh
+++ b/.config/ags/scripts/color_generation/applycolor.sh
@@ -66,7 +66,7 @@ apply_gtklock() {
# Copy template
mkdir -p "$HOME"/.cache/ags/user/generated/gtklock
- sassc "scripts/templates/gtklock/main.scss" "$HOME"/.cache/ags/user/generated/gtklock/style.css
+ sass "scripts/templates/gtklock/main.scss" "$HOME"/.cache/ags/user/generated/gtklock/style.css
cp "$HOME"/.cache/ags/user/generated/gtklock/style.css "$HOME"/.config/gtklock/style.css
}
@@ -195,9 +195,9 @@ apply_gtk() { # Using gradience-cli
}
apply_ags() {
- sassc "$HOME"/.config/ags/scss/main.scss "$HOME"/.config/ags/style.css
+ sass "$HOME"/.config/ags/scss/main.scss "$HOME"/.cache/ags/user/generated/style.css
ags run-js 'openColorScheme.value = true; Utils.timeout(2000, () => openColorScheme.value = false);'
- ags run-js "App.resetCss(); App.applyCss('${HOME}/.config/ags/style.css');"
+ ags run-js "App.resetCss(); App.applyCss('${HOME}/.cache/ags/user/generated/style.css');"
}
apply_ags &
diff --git a/.config/ags/scripts/color_generation/colorgen.sh b/.config/ags/scripts/color_generation/colorgen.sh
index 93a7e7504..1748ffb9d 100755
--- a/.config/ags/scripts/color_generation/colorgen.sh
+++ b/.config/ags/scripts/color_generation/colorgen.sh
@@ -42,7 +42,7 @@ elif [ "$backend" = "pywal" ]; then
cat color_generation/pywal_to_material.scss >> "$HOME"/.cache/ags/user/generated/material_colors.scss
if [ "$2" = "--apply" ]; then
- sassc "$HOME"/.cache/ags/user/generated/material_colors.scss "$HOME"/.cache/ags/user/generated/colors_classes.scss --style compact
+ sass "$HOME"/.cache/ags/user/generated/material_colors.scss "$HOME"/.cache/ags/user/generated/colors_classes.scss --style compact
sed -i "s/ { color//g" "$HOME"/.cache/ags/user/generated/colors_classes.scss
sed -i "s/\./$/g" "$HOME"/.cache/ags/user/generated/colors_classes.scss
sed -i "s/}//g" "$HOME"/.cache/ags/user/generated/colors_classes.scss
diff --git a/.config/ags/scripts/grimblast.sh b/.config/ags/scripts/grimblast.sh
new file mode 100755
index 000000000..db8bb5069
--- /dev/null
+++ b/.config/ags/scripts/grimblast.sh
@@ -0,0 +1,278 @@
+#!/usr/bin/env bash
+## Grimblast: a helper for screenshots within hyprland
+## Requirements:
+## - `grim`: screenshot utility for wayland
+## - `slurp`: to select an area
+## - `hyprctl`: to read properties of current window (provided by Hyprland)
+## - `hyprpicker`: to freeze the screen when selecting area
+## - `wl-copy`: clipboard utility (provided by wl-clipboard)
+## - `jq`: json utility to parse hyprctl output
+## - `notify-send`: to show notifications (provided by libnotify)
+## Those are needed to be installed, if unsure, run `grimblast check`
+##
+## See `man 1 grimblast` or `grimblast usage` for further details.
+
+## Author: Misterio (https://github.com/misterio77)
+
+## This tool is based on grimshot, with swaymsg commands replaced by their
+## hyprctl equivalents.
+## https://github.com/swaywm/sway/blob/master/contrib/grimshot
+jq=gojq
+getTargetDirectory() {
+ test -f "${XDG_CONFIG_HOME:-$HOME/.config}/user-dirs.dirs" &&
+ . "${XDG_CONFIG_HOME:-$HOME/.config}/user-dirs.dirs"
+
+ echo "${XDG_SCREENSHOTS_DIR:-${XDG_PICTURES_DIR:-$HOME}}"
+}
+
+tmp_editor_directory() {
+ echo "/tmp"
+}
+
+#Detect if $GRIMBLAST_EDITOR env exist
+env_editor_confirm() {
+ if [ -n "$GRIMBLAST_EDITOR" ]; then
+ echo "GRIMBLAST_EDITOR is set. Continuing..."
+ else
+ echo "GRIMBLAST_EDITOR is not set. Defaulting to gimp"
+ GRIMBLAST_EDITOR=gimp
+ fi
+}
+
+NOTIFY=no
+CURSOR=
+FREEZE=
+WAIT=no
+SCALE=
+HYPRPICKER_PID=-1
+
+while [ $# -gt 0 ]; do
+ key="$1"
+
+ case $key in
+ -n | --notify)
+ NOTIFY=yes
+ shift # past argument
+ ;;
+ -c | --cursor)
+ CURSOR=yes
+ shift # past argument
+ ;;
+ -f | --freeze)
+ FREEZE=yes
+ shift # past argument
+ ;;
+ -w | --wait)
+ shift
+ WAIT=$1
+ if echo "$WAIT" | grep "[^0-9]" -q; then
+ echo "Invalid value for wait '$WAIT'" >&2
+ exit 3
+ fi
+ shift
+ ;;
+ -s | --scale)
+ shift # past argument
+ if [ $# -gt 0 ]; then
+ SCALE="$1" # assign the next argument to SCALE
+ shift # past argument
+ else
+ echo "Error: Missing argument for --scale option."
+ exit 1
+ fi
+ ;;
+ *) # unknown option
+ break # done with parsing --flags
+ ;;
+ esac
+done
+
+ACTION=${1:-usage}
+SUBJECT=${2:-screen}
+FILE=${3:-$(getTargetDirectory)/$(date -Ins).png}
+FILE_EDITOR=${3:-$(tmp_editor_directory)/$(date -Ins).png}
+
+if [ "$ACTION" != "save" ] && [ "$ACTION" != "copy" ] && [ "$ACTION" != "edit" ] && [ "$ACTION" != "copysave" ] && [ "$ACTION" != "check" ]; then
+ echo "Usage:"
+ echo " grimblast [--notify] [--cursor] [--freeze] [--wait N] [--scale ] (copy|save|copysave|edit) [active|screen|output|area] [FILE|-]"
+ echo " grimblast check"
+ echo " grimblast usage"
+ echo ""
+ echo "Commands:"
+ echo " copy: Copy the screenshot data into the clipboard."
+ echo " save: Save the screenshot to a regular file or '-' to pipe to STDOUT."
+ echo " copysave: Combine the previous 2 options."
+ echo " edit: Open screenshot in the image editor of your choice (default is gimp). See man page for info."
+ echo " check: Verify if required tools are installed and exit."
+ echo " usage: Show this message and exit."
+ echo ""
+ echo "Targets:"
+ echo " active: Currently active window."
+ echo " screen: All visible outputs."
+ echo " output: Currently active output."
+ echo " area: Manually select a region or window."
+ exit
+fi
+
+notify() {
+ notify-send -t 3000 -a grimblast "$@"
+}
+
+notifyOk() {
+ [ "$NOTIFY" = "no" ] && return
+
+ notify "$@"
+}
+
+notifyError() {
+ if [ $NOTIFY = "yes" ]; then
+ TITLE=${2:-"Screenshot"}
+ MESSAGE=${1:-"Error taking screenshot with grim"}
+ notify -u critical "$TITLE" "$MESSAGE"
+ else
+ echo "$1"
+ fi
+}
+
+resetFade() {
+ if [[ -n $FADE && -n $FADEOUT ]]; then
+ hyprctl keyword animation "$FADE" >/dev/null
+ hyprctl keyword animation "$FADEOUT" >/dev/null
+ fi
+}
+
+killHyprpicker() {
+ if [ ! $HYPRPICKER_PID -eq -1 ]; then
+ kill $HYPRPICKER_PID
+ fi
+}
+
+die() {
+ killHyprpicker
+ MSG=${1:-Bye}
+ notifyError "Error: $MSG"
+ exit 2
+}
+
+check() {
+ COMMAND=$1
+ if command -v "$COMMAND" >/dev/null 2>&1; then
+ RESULT="OK"
+ else
+ RESULT="NOT FOUND"
+ fi
+ echo " $COMMAND: $RESULT"
+}
+
+takeScreenshot() {
+ FILE=$1
+ GEOM=$2
+ OUTPUT=$3
+ if [ -n "$OUTPUT" ]; then
+ grim ${CURSOR:+-c} ${SCALE:+-s "$SCALE"} -o "$OUTPUT" "$FILE" || die "Unable to invoke grim"
+ elif [ -z "$GEOM" ]; then
+ grim ${CURSOR:+-c} ${SCALE:+-s "$SCALE"} "$FILE" || die "Unable to invoke grim"
+ else
+ grim ${CURSOR:+-c} ${SCALE:+-s "$SCALE"} -g "$GEOM" "$FILE" || die "Unable to invoke grim"
+ resetFade
+ fi
+}
+
+wait() {
+ if [ "$WAIT" != "no" ]; then
+ sleep "$WAIT"
+ fi
+}
+
+if [ "$ACTION" = "check" ]; then
+ echo "Checking if required tools are installed. If something is missing, install it to your system and make it available in PATH..."
+ check grim
+ check slurp
+ check hyprctl
+ check hyprpicker
+ check wl-copy
+ check $jq
+ check notify-send
+ exit
+elif [ "$SUBJECT" = "active" ]; then
+ wait
+ FOCUSED=$(hyprctl activewindow -j)
+ GEOM=$(echo "$FOCUSED" | $jq -r '"\(.at[0]),\(.at[1]) \(.size[0])x\(.size[1])"')
+ APP_ID=$(echo "$FOCUSED" | $jq -r '.class')
+ WHAT="$APP_ID window"
+elif [ "$SUBJECT" = "screen" ]; then
+ wait
+ GEOM=""
+ WHAT="Screen"
+elif [ "$SUBJECT" = "output" ]; then
+ wait
+ GEOM=""
+ OUTPUT=$(hyprctl monitors -j | $jq -r '.[] | select(.focused == true)' | $jq -r '.name')
+ WHAT="$OUTPUT"
+elif [ "$SUBJECT" = "area" ]; then
+ if [ "$FREEZE" = "yes" ] && [ "$(command -v "hyprpicker")" ] >/dev/null 2>&1; then
+ hyprpicker -r -z &
+ sleep 0.2
+ HYPRPICKER_PID=$!
+ fi
+
+ # get fade & fadeOut animation and unset it
+ # this removes the black border seen around screenshots
+ FADE="$(hyprctl -j animations | $jq -jr '.[0][] | select(.name == "fade") | .name, ",", (if .enabled == true then "1" else "0" end), ",", (.speed|floor), ",", .bezier')"
+ FADEOUT="$(hyprctl -j animations | $jq -jr '.[0][] | select(.name == "fadeOut") | .name, ",", (if .enabled == true then "1" else "0" end), ",", (.speed|floor), ",", .bezier')"
+ hyprctl keyword animation 'fade,0,1,default' >/dev/null
+ hyprctl keyword animation 'fadeOut,0,1,default' >/dev/null
+
+ WORKSPACES="$(hyprctl monitors -j | $jq -r 'map(.activeWorkspace.id)')"
+ WINDOWS="$(hyprctl clients -j | $jq -r --argjson workspaces "$WORKSPACES" 'map(select([.workspace.id] | inside($workspaces)))')"
+ # shellcheck disable=2086 # if we don't split, spaces mess up slurp
+ GEOM=$(echo "$WINDOWS" | $jq -r '.[] | "\(.at[0]),\(.at[1]) \(.size[0])x\(.size[1])"' | slurp $SLURP_ARGS)
+
+ # Check if user exited slurp without selecting the area
+ if [ -z "$GEOM" ]; then
+ killHyprpicker
+ resetFade
+ exit 1
+ fi
+ WHAT="Area"
+ wait
+elif [ "$SUBJECT" = "window" ]; then
+ die "Subject 'window' is now included in 'area'"
+else
+ die "Unknown subject to take a screen shot from" "$SUBJECT"
+fi
+
+if [ "$ACTION" = "copy" ]; then
+ takeScreenshot - "$GEOM" "$OUTPUT" | wl-copy --type image/png || die "Clipboard error"
+ notifyOk "$WHAT copied to buffer"
+elif [ "$ACTION" = "save" ]; then
+ if takeScreenshot "$FILE" "$GEOM" "$OUTPUT"; then
+ TITLE="Screenshot of $SUBJECT"
+ MESSAGE=$(basename "$FILE")
+ notifyOk "$TITLE" "$MESSAGE" -i "$FILE"
+ echo "$FILE"
+ else
+ notifyError "Error taking screenshot with grim"
+ fi
+elif [ "$ACTION" = "edit" ]; then
+ env_editor_confirm
+ if takeScreenshot "$FILE_EDITOR" "$GEOM" "$OUTPUT"; then
+ TITLE="Screenshot of $SUBJECT"
+ MESSAGE="Open screenshot in image editor"
+ notifyOk "$TITLE" "$MESSAGE" -i "$FILE_EDITOR"
+ $GRIMBLAST_EDITOR "$FILE_EDITOR"
+ echo "$FILE_EDITOR"
+ else
+ notifyError "Error taking screenshot"
+ fi
+else
+ if [ "$ACTION" = "copysave" ]; then
+ takeScreenshot - "$GEOM" "$OUTPUT" | tee "$FILE" | wl-copy --type image/png || die "Clipboard error"
+ notifyOk "$WHAT copied to buffer and saved to $FILE" -i "$FILE"
+ echo "$FILE"
+ else
+ notifyError "Error taking screenshot with grim"
+ fi
+fi
+
+killHyprpicker
diff --git a/.config/ags/scripts/templates/hypr/hyprland/colors.conf b/.config/ags/scripts/templates/hypr/hyprland/colors.conf
index 3a0bf4a34..8c0689f31 100644
--- a/.config/ags/scripts/templates/hypr/hyprland/colors.conf
+++ b/.config/ags/scripts/templates/hypr/hyprland/colors.conf
@@ -1,4 +1,4 @@
-$SLURP_COMMAND="$(slurp -d -c {{ $onSecondaryContainer }}BB -b {{ $secondaryContainer }}44 -s 00000000)"
+# exec = export SLURP_ARGS='-d -c {{ $onSecondaryContainer }}BB -b {{ $secondaryContainer }}44 -s 00000000'
general {
col.active_border = rgba({{ $onSurface }}39)
diff --git a/.config/ags/scripts/templates/hypr/hyprlock.conf b/.config/ags/scripts/templates/hypr/hyprlock.conf
index 3ad0f0d6f..5a42af563 100644
--- a/.config/ags/scripts/templates/hypr/hyprlock.conf
+++ b/.config/ags/scripts/templates/hypr/hyprlock.conf
@@ -40,7 +40,7 @@ label { # Clock
halign = center
valign = center
}
-label {
+label { # Greeting
monitor =
text = hi $USER !!!
color = $text_color
@@ -51,7 +51,7 @@ label {
halign = center
valign = center
}
-label {
+label { # lock icon
monitor =
text = lock
color = $text_color
@@ -62,7 +62,7 @@ label {
halign = center
valign = bottom
}
-label {
+label { # "locked" text
monitor =
text = locked
color = $text_color
@@ -72,4 +72,16 @@ label {
position = 0, 50
halign = center
valign = bottom
+}
+
+label { # Status
+ monitor =
+ text = cmd[update:5000] ~/.config/hypr/hyprlock/status.sh
+ color = $text_color
+ font_size = 14
+ font_family = $font_family
+
+ position = 30, -30
+ halign = left
+ valign = top
}
\ No newline at end of file
diff --git a/.config/ags/scss/_lib_classes.scss b/.config/ags/scss/_lib_classes.scss
index 6cd3f893b..988dee7a2 100644
--- a/.config/ags/scss/_lib_classes.scss
+++ b/.config/ags/scss/_lib_classes.scss
@@ -167,7 +167,7 @@
}
.separator-line {
- background-color: $outline;
+ background-color: mix($subtext, $surface, 50%);
min-width: 0.068rem;
min-height: 0.068rem;
}
diff --git a/.config/ags/scss/_osk.scss b/.config/ags/scss/_osk.scss
index eca128c76..d5e62fffd 100644
--- a/.config/ags/scss/_osk.scss
+++ b/.config/ags/scss/_osk.scss
@@ -69,7 +69,8 @@ $osk_key_fontsize: 1.091rem;
.osk-key-fn {
min-width: $osk_key_width * 1.005;
- min-height: $osk_key_height / 2;
+ min-height: calc($osk_key_height / 2); // dart-sass
+ // min-height: $osk_key_height / 2; // sassc
}
.osk-key-tab {
diff --git a/.config/ags/scss/_sidebars.scss b/.config/ags/scss/_sidebars.scss
index 51a3b5dde..1989c9cb8 100644
--- a/.config/ags/scss/_sidebars.scss
+++ b/.config/ags/scss/_sidebars.scss
@@ -557,6 +557,17 @@ $colorpicker_rounding: 0.341rem;
color: $onSecondaryContainer;
}
+.sidebar-chat-providerswitcher {
+ @include small-rounding;
+ padding: 0.477rem 0.682rem;
+ background-color: $textboxColor;
+ color: $onSurfaceVariant;
+}
+
+// .sidebar-chat-providerswitcher-list {
+// margin: 0.341rem 0rem;
+// }
+
.sidebar-chat-viewport {
@include element_decel;
// margin: 0.682rem 0rem;
@@ -655,6 +666,17 @@ $colorpicker_rounding: 0.341rem;
@include readingfont;
}
+.sidebar-chat-latex {
+ @include small-rounding;
+ margin: 0rem 0.682rem;
+ padding: 0.682rem;
+ @if $darkmode ==true {
+ background-color: white;
+ }
+ color: $onBackground;
+ // background-color: $termbg;
+}
+
.sidebar-chat-codeblock {
@include normal-rounding;
// @include elevation2;
diff --git a/.config/ags/services/gemini.js b/.config/ags/services/gemini.js
index bcc8cbcb7..1caee94ce 100644
--- a/.config/ags/services/gemini.js
+++ b/.config/ags/services/gemini.js
@@ -8,7 +8,7 @@ import { fileExists } from './messages.js';
const initMessages =
[
- { role: "user", parts: [{ text: "You are an assistant on a sidebar of a Wayland Linux desktop. Please always use a casual tone when answering your questions, unless requested otherwise or making writing suggestions. These are the steps you should take to respond to the user's queries:\n1. If it's a writing- or grammar-related question or a sentence in quotation marks, Please point out errors and correct when necessary using underlines, and make the writing more natural where appropriate without making too major changes. If you're given a sentence in quotes but is grammatically correct, explain briefly concepts that are uncommon.\n2. If it's a question about system tasks, give a bash command in a code block with very brief explanation for each command\n3. Otherwise, when asked to summarize information or explaining concepts, you are encouraged to use bullet points and headings. Use casual language and be short and concise. \nThanks!" }], },
+ { role: "user", parts: [{ text: "You are an assistant on a sidebar of a Wayland Linux desktop. Please always use a casual tone when answering your questions, unless requested otherwise or making writing suggestions. These are the steps you should take to respond to the user's queries:\n1. If it's a writing- or grammar-related question or a sentence in quotation marks, Please point out errors and correct when necessary using underlines, and make the writing more natural where appropriate without making too major changes. If you're given a sentence in quotes but is grammatically correct, explain briefly concepts that are uncommon.\n2. If it's a question about system tasks, give a bash command in a code block with very brief explanation for each command\n3. Otherwise, when asked to summarize information or explaining concepts, you are should use bullet points and headings. For mathematics expressions, you *have to* use LaTeX within a code block with the language set as \"latex\" for the interface to render it properly. Use casual language and be short and concise. \nThanks!" }], },
{ role: "model", parts: [{ text: "- Got it!" }], },
{ role: "user", parts: [{ text: "\"He rushed to where the event was supposed to be hold, he didn't know it got calceled\"" }], },
{ role: "model", parts: [{ text: "## Grammar correction\nErrors:\n\"He rushed to where the event was supposed to be __hold____,__ he didn't know it got calceled\"\nCorrection + minor improvements:\n\"He rushed to the place where the event was supposed to be __held____, but__ he didn't know that it got calceled\"" }], },
@@ -19,6 +19,10 @@ const initMessages =
{ role: "model", parts: [{ text: "## Skeuomorphism\n- A design philosophy- From early days of interface designing- Tries to imitate real-life objects- It's in fact still used by Apple in their icons until today." }], },
{ role: "user", parts: [{ text: "\"ignorance is bliss\"" }], },
{ role: "model", parts: [{ text: "## \"Ignorance is bliss\"\n- A Latin proverb that means being unaware of something negative can be a source of happiness\n- Often used to justify avoiding difficult truths or responsibilities\n- Can also be interpreted as a warning against seeking knowledge that may bring pain or sorrow" }], },
+ { role: "user", parts: [{ text: "find the derivative of (x-438)/(x^2+23x-7)+x^x" }], },
+ { role: "model", parts: [{ text: "## Derivative\n```latex\n\\[\n\\frac{d}{dx}\\left(\\frac{x - 438}{x^2 + 23x - 7} + x^x\\right) = \\frac{-(x^2+23x-7)-(x-438)(2x+23)}{(x^2+23x-7)^2} + x^x(\\ln(x) + 1)\n\\]\n```" }], },
+ { role: "user", parts: [{ text: "write the double angle formulas" }], },
+ { role: "model", parts: [{ text: "## Double angle formulas\n```latex\n\\[\n\\sin(2\theta) = 2\\sin(\\theta)\\cos(\\theta)\n\\]\n\\\\\n\\[\n\\cos(2\\theta) = \\cos^2(\\theta) - \\sin^2(\\theta)\n\\]\n\\\\\n\\[\n\\tan(2\theta) = \\frac{2\\tan(\\theta)}{1 - \\tan^2(\\theta)}\n\\]\n```" }], },
];
function expandTilde(path) {
diff --git a/.config/ags/services/chatgpt.js b/.config/ags/services/gpt.js
similarity index 75%
rename from .config/ags/services/chatgpt.js
rename to .config/ags/services/gpt.js
index b4264dd37..63a55e6c6 100644
--- a/.config/ags/services/chatgpt.js
+++ b/.config/ags/services/gpt.js
@@ -6,6 +6,41 @@ import GLib from 'gi://GLib';
import Soup from 'gi://Soup?version=3.0';
import { fileExists } from './messages.js';
+const PROVIDERS = { // There's this list hmm https://github.com/zukixa/cool-ai-stuff/
+ 'openai': {
+ 'name': 'OpenAI',
+ 'logo_name': 'openai-symbolic',
+ 'description': 'Official OpenAI API.\nPricing: Free for the first $5 or 3 months, whichever is less.',
+ 'base_url': 'https://api.openai.com/v1/chat/completions',
+ 'key_get_url': 'https://platform.openai.com/api-keys',
+ 'key_file': 'openai_key.txt',
+ },
+ 'oxygen': {
+ 'name': 'Oxygen',
+ 'logo_name': 'ai-oxygen-symbolic',
+ 'description': 'An API from Tornado Softwares\nPricing: Free: 100/day\nRequires you to join their Discord for a key',
+ 'base_url': 'https://app.oxyapi.uk/v1/chat/completions',
+ 'key_get_url': 'https://discord.com/invite/kM6MaCqGKA',
+ 'key_file': 'oxygen_key.txt',
+ },
+ 'zukijourney': {
+ 'name': 'zukijourney',
+ 'logo_name': 'ai-zukijourney',
+ 'description': 'An API from @zukixa on GitHub.\nNote: Keys are IP-locked so it\'s buggy sometimes\nPricing: Free: 10/min, 800/day.\nRequires you to join their Discord for a key',
+ 'base_url': 'https://zukijourney.xyzbot.net/v1/chat/completions',
+ 'key_get_url': 'https://discord.com/invite/Y4J6XXnmQ6',
+ 'key_file': 'zuki_key.txt',
+ },
+ 'zukijourney_roleplay': {
+ 'name': 'zukijourney (roleplay)',
+ 'logo_name': 'ai-zukijourney',
+ 'description': 'An API from @zukixa on GitHub.\nNote: Keys are IP-locked so it\'s buggy sometimes\nPricing: Free: 10/min, 800/day.\nRequires you to join their Discord for a key',
+ 'base_url': 'https://zukijourney.xyzbot.net/unf/chat/completions',
+ 'key_get_url': 'https://discord.com/invite/Y4J6XXnmQ6',
+ 'key_file': 'zuki_key.txt',
+ },
+}
+
// Custom prompt
const initMessages =
[
@@ -21,28 +56,9 @@ const initMessages =
{ role: "assistant", content: "## Skeuomorphism\n- A design philosophy- From early days of interface designing- Tries to imitate real-life objects- It's in fact still used by Apple in their icons until today.", },
];
-function expandTilde(path) {
- if (path.startsWith('~')) {
- return GLib.get_home_dir() + path.slice(1);
- } else {
- return path;
- }
-}
-
// We're using many models to not be restricted to 3 messages per minute.
// The whole chat will be sent every request anyway.
Utils.exec(`mkdir -p ${GLib.get_user_cache_dir()}/ags/user/ai`);
-const KEY_FILE_LOCATION = `${GLib.get_user_cache_dir()}/ags/user/ai/openai_key.txt`;
-const APIDOM_FILE_LOCATION = `${GLib.get_user_cache_dir()}/ags/user/openai_api_dom.txt`;
-function replaceapidom(URL) {
- //Utils.writeFile(URL, "/tmp/openai-url-old.log"); // For debugging
- if (fileExists(expandTilde(APIDOM_FILE_LOCATION))) {
- var contents = Utils.readFile(expandTilde(APIDOM_FILE_LOCATION)).trim();
- var URL = URL.toString().replace("api.openai.com", contents);
- }
- //Utils.writeFile(URL, "/tmp/openai-url.log"); // For debugging
- return URL;
-}
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;
@@ -113,25 +129,33 @@ class ChatGPTService extends Service {
'clear': [],
'newMsg': ['int'],
'hasKey': ['boolean'],
+ 'providerChanged': [],
});
}
_assistantPrompt = true;
- _messages = [];
- _cycleModels = true;
+ _currentProvider = 'openai';
+ _cycleModels = false;
_requestCount = 0;
_temperature = 0.9;
+ _messages = [];
_modelIndex = 0;
_key = '';
+ _key_file_location = `${GLib.get_user_cache_dir()}/ags/user/ai/${PROVIDERS[this._currentProvider]['key_file']}`;
+ _url = GLib.Uri.parse(PROVIDERS[this._currentProvider]['base_url'], GLib.UriFlags.NONE);
+
_decoder = new TextDecoder();
- url = GLib.Uri.parse(replaceapidom('https://api.openai.com/v1/chat/completions'), GLib.UriFlags.NONE);
+ _initChecks() {
+ this._key_file_location = `${GLib.get_user_cache_dir()}/ags/user/ai/${PROVIDERS[this._currentProvider]['key_file']}`;
+ if (fileExists(this._key_file_location)) this._key = Utils.readFile(this._key_file_location).trim();
+ else this.emit('hasKey', false);
+ this._url = GLib.Uri.parse(PROVIDERS[this._currentProvider]['base_url'], GLib.UriFlags.NONE);
+ }
constructor() {
super();
-
- if (fileExists(expandTilde(KEY_FILE_LOCATION))) this._key = Utils.readFile(expandTilde(KEY_FILE_LOCATION)).trim();
- else this.emit('hasKey', false);
+ this._initChecks();
if (this._assistantPrompt) this._messages = [...initMessages];
else this._messages = [];
@@ -140,12 +164,20 @@ class ChatGPTService extends Service {
}
get modelName() { return CHAT_MODELS[this._modelIndex] }
+ get getKeyUrl() { return PROVIDERS[this._currentProvider]['key_get_url'] }
+ get providerID() { return this._currentProvider }
+ set providerID(value) {
+ this._currentProvider = value;
+ this.emit('providerChanged');
+ this._initChecks();
+ }
+ get providers() { return PROVIDERS }
- get keyPath() { return KEY_FILE_LOCATION }
+ get keyPath() { return this._key_file_location }
get key() { return this._key }
set key(keyValue) {
this._key = keyValue;
- Utils.writeFile(this._key, expandTilde(KEY_FILE_LOCATION))
+ Utils.writeFile(this._key, this._key_file_location)
.then(this.emit('hasKey', true))
.catch(err => print(err));
}
@@ -197,6 +229,7 @@ class ChatGPTService extends Service {
return;
}
aiResponse.addDelta(result.choices[0].delta.content);
+ // print(result.choices[0])
}
catch {
aiResponse.addDelta(line + '\n');
@@ -229,7 +262,7 @@ class ChatGPTService extends Service {
const session = new Soup.Session();
const message = new Soup.Message({
method: 'POST',
- uri: this.url,
+ uri: this._url,
});
message.request_headers.append('Authorization', `Bearer ${this._key}`);
message.set_request_body_from_bytes('application/json', new GLib.Bytes(JSON.stringify(body)));
diff --git a/.config/hypr/hypridle.conf b/.config/hypr/hypridle.conf
index 6b2852514..3efbd108a 100644
--- a/.config/hypr/hypridle.conf
+++ b/.config/hypr/hypridle.conf
@@ -3,19 +3,21 @@ $suspend_cmd = systemctl suspend
general {
lock_cmd = $lock_cmd
- # unlock_cmd
before_sleep_cmd = $lock_cmd
- # after_sleep_cmd
}
listener {
- timeout = 300
+ timeout = 180 # 3mins
on-timeout = $lock_cmd
- # on-resume
}
listener {
- timeout = 450
- on-timeout = $suspend_cmd
- # on-resume
+ timeout = 240 # 4mins
+ on-timeout = hyprctl dispatch dpms off
+ on-resume = hyprctl dispatch dpms on
+}
+
+listener {
+ timeout = 540 # 9mins
+ on-timeout = $suspend_cmd
}
diff --git a/.config/hypr/hyprland/keybinds.conf b/.config/hypr/hyprland/keybinds.conf
index 275d0dba4..d892c0224 100644
--- a/.config/hypr/hyprland/keybinds.conf
+++ b/.config/hypr/hyprland/keybinds.conf
@@ -6,10 +6,9 @@ bindle=, XF86AudioRaiseVolume, exec, wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@
bindle=, XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-
# Brightness
+# Uncomment these if you can't get AGS to work
#bindle=, XF86MonBrightnessUp, exec, brightnessctl set '12.75+'
#bindle=, XF86MonBrightnessDown, exec, brightnessctl set '12.75-'
-bindle=, XF86MonBrightnessUp, exec, ags run-js 'brightness.screen_value += 0.05;'
-bindle=, XF86MonBrightnessDown, exec, ags run-js 'brightness.screen_value -= 0.05;'
#################################### Applications ###################################
# Apps: just normal apps
@@ -38,9 +37,9 @@ bind = Control+Shift+Alt, Delete, exec, pkill wlogout || wlogout -p layer-shell
bind = Control+Shift+Alt+Super, Delete, exec, systemctl poweroff
# Screenshot, Record, OCR, Color picker, Clipboard history
-bind = Super+Shift+Alt, S, exec, grim -g $SLURP_COMMAND - | swappy -f -
+bind = Super+Shift+Alt, S, exec, grim -g $(slurp $SLURP_ARGS) - | swappy -f -
bindl=,Print,exec,grim - | wl-copy
-bind = Super+Shift, S, exec, grim -g $SLURP_COMMAND - | wl-copy
+bind = Super+Shift, S, exec, ~/.config/ags/scripts/grimblast.sh --freeze copy area
bind = Super+Alt, R, exec, ~/.config/ags/scripts/record-script.sh
bind = Control+Alt, R, exec, ~/.config/ags/scripts/record-script.sh --fullscreen
bind = Super+Shift+Alt, R, exec, ~/.config/ags/scripts/record-script.sh --fullscreen-sound
@@ -49,11 +48,11 @@ bind = Super, V, exec, pkill fuzzel || cliphist list | fuzzel --no-fuzzy --dmenu
# Text-to-image
# Normal
-bind = Control+Super+Shift,S,exec,grim -g $SLURP_COMMAND "tmp.png" && tesseract "tmp.png" - | wl-copy && rm "tmp.png"
+bind = Control+Super+Shift,S,exec,grim -g $(slurp $SLURP_ARGS) "tmp.png" && tesseract "tmp.png" - | wl-copy && rm "tmp.png"
# English
-bind = Super+Shift,T,exec,grim -g $SLURP_COMMAND "tmp.png" && tesseract -l eng "tmp.png" - | wl-copy && rm "tmp.png"
+bind = Super+Shift,T,exec,grim -g $(slurp $SLURP_ARGS) "tmp.png" && tesseract -l eng "tmp.png" - | wl-copy && rm "tmp.png"
# Japanese
-bind = Super+Shift,J,exec,grim -g $SLURP_COMMAND "tmp.png" && tesseract -l jpn "tmp.png" - | wl-copy && rm "tmp.png"
+bind = Super+Shift,J,exec,grim -g $(slurp $SLURP_ARGS) "tmp.png" && tesseract -l jpn "tmp.png" - | wl-copy && rm "tmp.png"
# Media
bindl= Super+Shift, N, exec, playerctl next || playerctl position `bc <<< "100 * $(playerctl metadata mpris:length) / 1000000 / 100"`
@@ -87,10 +86,10 @@ bind = Super, K, exec, ags -t 'osk'
bind = Control+Alt, Delete, exec, ags -t 'session'
bindle = , XF86AudioRaiseVolume, exec, ags run-js 'indicator.popup(1);'
bindle = , XF86AudioLowerVolume, exec, ags run-js 'indicator.popup(1);'
+bindle=, XF86MonBrightnessUp, exec, ags run-js 'brightness.screen_value += 0.05; indicator.popup(1);'
+bindle=, XF86MonBrightnessDown, exec, ags run-js 'brightness.screen_value -= 0.05; indicator.popup(1);'
bindl = , XF86AudioMute, exec, ags run-js 'indicator.popup(1);'
bindl = Super+Shift,M, exec, ags run-js 'indicator.popup(1);'
-bindle = , XF86MonBrightnessUp, exec, ags run-js 'indicator.popup(1);'
-bindle = , XF86MonBrightnessDown, exec, ags run-js 'indicator.popup(1);'
###################################### Plugins #########################################
bind = Control+Super, P, exec, hyprctl plugin load "~/.config/hypr/plugins/droidbars.so"
@@ -100,7 +99,8 @@ bind = Control+Super, O, exec, hyprctl plugin unload "~/.config/hypr/plugins/dro
# bind = SuperAlt, f12, exec, notify-send "Hyprland version: $(hyprctl version | head -2 | tail -1 | cut -f2 -d ' ')" "owo" -a 'Hyprland keybind'
# bind = Super+Alt, f12, exec, notify-send "Millis since epoch" "$(date +%s%N | cut -b1-13)" -a 'Hyprland keybind'
bind = Super+Alt, f12, exec, notify-send 'Test notification' "Here's a really long message to test truncation and wrapping\nYou can middle click or flick this notification to dismiss it!" -a 'Shell' -A "Test1=I got it!" -A "Test2=Another action"
-bind = Super+Alt, Equal, exec, notify-send "Urgent notification" "Ah hell no" -u critical -a 'Hyprland keybind'
+# bind = Super+Alt, Equal, exec, notify-send "Urgent notification" "Ah hell no" -u critical -a 'Hyprland keybind'
+bind = Super+Alt, Equal, exec, notify-send 'hmm' ${SLURP_ARGS}
############################ Keybinds for Hyprland ############################
# Swap windows
diff --git a/.config/hypr/hyprland/rules.conf b/.config/hypr/hyprland/rules.conf
index cad82a903..996fa2fd1 100644
--- a/.config/hypr/hyprland/rules.conf
+++ b/.config/hypr/hyprland/rules.conf
@@ -4,7 +4,8 @@ windowrule = noblur,.* # Disables blur for windows. Substantially improves perf
# windowrule = opacity 0.89 override 0.89 override, .* # Applies transparency to EVERY WINDOW
windowrule = float, ^(steam)$
windowrule = float, ^(guifetch)$ # FlafyDev/guifetch
-windowrulev2 = tile,class:(wpsoffice)
+windowrulev2 = tile,class:(wps)
+windowrulev2 = tile,class:(dev.warp.Warp)
# Dialogs
@@ -21,6 +22,9 @@ layerrule = xray 1, .*
layerrule = noanim, selection
layerrule = noanim, overview
layerrule = noanim, anyrun
+layerrule = noanim, sideleft
+layerrule = noanim, sideright
+layerrule = noanim, osk
layerrule = blur, eww
layerrule = ignorealpha 0.8, eww
diff --git a/.config/hypr/hyprlock.conf b/.config/hypr/hyprlock.conf
index f2299434d..71d9034be 100644
--- a/.config/hypr/hyprlock.conf
+++ b/.config/hypr/hyprlock.conf
@@ -1,13 +1,13 @@
-$text_color = rgba(eae0e4FF)
-$entry_background_color = rgba(120F1111)
-$entry_border_color = rgba(9a8d9555)
-$entry_color = rgba(d1c2cbFF)
+$text_color = rgba(ede0deFF)
+$entry_background_color = rgba(130F0F11)
+$entry_border_color = rgba(a08c8955)
+$entry_color = rgba(d8c2bfFF)
$font_family = Gabarito
$font_family_clock = Gabarito
$font_material_symbols = Material Symbols Rounded
background {
- color = rgba(120F1177)
+ color = rgba(130F0F77)
# path = {{ SWWW_WALL }}
path = screenshot
blur_size = 5
@@ -40,7 +40,7 @@ label { # Clock
halign = center
valign = center
}
-label {
+label { # Greeting
monitor =
text = hi $USER !!!
color = $text_color
@@ -51,7 +51,7 @@ label {
halign = center
valign = center
}
-label {
+label { # lock icon
monitor =
text = lock
color = $text_color
@@ -62,7 +62,7 @@ label {
halign = center
valign = bottom
}
-label {
+label { # "locked" text
monitor =
text = locked
color = $text_color
@@ -72,4 +72,16 @@ label {
position = 0, 50
halign = center
valign = bottom
+}
+
+label { # Status
+ monitor =
+ text = cmd[update:5000] ~/.config/hypr/hyprlock/status.sh
+ color = $text_color
+ font_size = 14
+ font_family = $font_family
+
+ position = 30, -30
+ halign = left
+ valign = top
}
\ No newline at end of file
diff --git a/.config/hypr/hyprlock/status.sh b/.config/hypr/hyprlock/status.sh
new file mode 100755
index 000000000..794c468f4
--- /dev/null
+++ b/.config/hypr/hyprlock/status.sh
@@ -0,0 +1,22 @@
+#!/usr/bin/env bash
+
+############ Variables ############
+enable_battery=false
+
+####### Check availability ########
+for battery in /sys/class/power_supply/*BAT*; do
+ if [[ -f "$battery/uevent" ]]; then
+ enable_battery=true
+ break
+ fi
+done
+
+############# Output #############
+if [[ $enable_battery == true ]]; then
+ if [[ $(cat /sys/class/power_supply/*/status | head -1) == "Charging" ]]; then
+ echo -n "(+) "
+ fi
+ echo -n "$(cat /sys/class/power_supply/*/capacity | head -1)"% remaining
+fi
+
+echo ''
\ No newline at end of file
diff --git a/.config/swaylock/config b/Import Manually/swaylock/config
similarity index 100%
rename from .config/swaylock/config
rename to Import Manually/swaylock/config
diff --git a/README.md b/README.md
index 4bee3a78e..16c9af056 100644
--- a/README.md
+++ b/README.md
@@ -29,12 +29,19 @@
bash <(curl -s "https://end-4.github.io/dots-hyprland-wiki/setup.sh")
```
- Manual installation, other distros and more:
- - See the [Wiki](https://end-4.github.io/dots-hyprland-wiki/en/i-i/01setup/).
- - If you'd like to suggest fixes or maybe a new widget, feel free to [open an issue](https://github.com/end-4/dots-hyprland/issues/new/choose)!
+ - See the [Wiki](https://end-4.github.io/dots-hyprland-wiki/en/i-i/01setup/)
+ - (_Available in: English, Vietnamese, and Simplified Chinese. Translations are welcome._)
+
+
+
+ Help improve these dotfiles!
+
+ - Join the [discussions](https://github.com/end-4/dots-hyprland/discussions)
+ - If you'd like to suggest fixes or a new widget, feel free to [open an issue](https://github.com/end-4/dots-hyprland/issues/new/choose)
### [illogical_impulse](https://github.com/end-4/dots-hyprland/tree/illogical-impulse)
-
+



diff --git a/install.sh b/install.sh
index 21649bdf1..836c14f9d 100755
--- a/install.sh
+++ b/install.sh
@@ -119,11 +119,28 @@ if $(test -d /usr/local/share/icons/OneUI); then
else ask_OneUI=true
fi
if $ask_OneUI;then showfun install-OneUI;v install-OneUI;fi
+
+if $(test -d /usr/local/share/icons/Bibata-Modern-Classic); then
+ echo -e "\e[33m[$0]: Cursor theme \"Bibata-Modern-Classic\" already exists, no need to install.\e[0m"
+ echo -e "\e[34mYou can reinstall it in order to update to the latest version anyway.\e[0m"
+ ask_bibata=$ask
+else ask_bibata=true
+fi
+if $ask_bibata;then showfun install-bibata;v install-bibata;fi
+
+if command -v LaTeX >/dev/null 2>&1;then
+ echo -e "\e[33m[$0]: Program \"MicroTeX\" already exists, no need to install.\e[0m"
+ echo -e "\e[34mYou can reinstall it in order to update to the latest version anyway.\e[0m"
+ ask_MicroTeX=$ask
+else ask_MicroTeX=true
+fi
+if $ask_MicroTeX;then showfun install-MicroTeX;v install-MicroTeX;fi
+
#####################################################################################
printf "\e[36m[$0]: 3. Copying\e[97m\n"
-# In case ~/.local/bin does not exists
-v mkdir -p "$HOME/.local/bin" "$HOME/.local/share"
+# In case some folders does not exists
+v mkdir -p "$HOME"/.{config,cache,local/{bin,share}}
# `--delete' for rsync to make sure that
# original dotfiles and new ones in the SAME DIRECTORY
diff --git a/scriptdata/dependencies.conf b/scriptdata/dependencies.conf
index 0dc219432..7ffeed1cf 100644
--- a/scriptdata/dependencies.conf
+++ b/scriptdata/dependencies.conf
@@ -2,7 +2,10 @@
### PKGs on same line will be send to `yay` together, unless `-f` is specified.
### Basic
-coreutils cliphist curl fuzzel rsync wget ripgrep gojq npm meson typescript gjs sassc
+coreutils cliphist cmake curl fuzzel rsync wget ripgrep gojq npm meson typescript gjs dart-sass axel
+
+# Make deps of MicroTeX
+tinyxml2 gtkmm3 gtksourceviewmm cairomm
### Python
# Add `python-setuptools-scm` and `python-wheel` explicitly to fix #197
diff --git a/scriptdata/installers b/scriptdata/installers
index 8e707b29f..8d2fe7bab 100644
--- a/scriptdata/installers
+++ b/scriptdata/installers
@@ -73,3 +73,31 @@ install-OneUI (){
x sudo cp -r OneUI-light /usr/local/share/icons
x cd $base
}
+
+install-bibata (){
+ x mkdir -p $base/cache/bibata-cursor
+ x cd $base/cache/bibata-cursor
+ name="Bibata-Modern-Classic"
+ file="$name.tar.xz"
+ # Use axel because `curl -O` always downloads a file with 0 byte size, idk why
+ x axel https://github.com/ful1e5/Bibata_Cursor/releases/latest/download/$file
+ tar -xf $file
+ x sudo mkdir -p /usr/local/share/icons
+ x sudo cp -r $name /usr/local/share/icons
+ x cd $base
+}
+
+install-MicroTeX (){
+ x mkdir -p $base/cache/MicroTeX
+ x cd $base/cache/MicroTeX
+ try git init -b master
+ try git remote add origin https://github.com/NanoMichael/MicroTeX.git
+ x git pull origin master && git submodule update --init --recursive
+ x mkdir -p build
+ x cd build
+ x cmake ..
+ x make -j32
+ x sudo mkdir -p /usr/local/bin
+ x sudo cp ./LaTeX /usr/local/bin/
+ x cd $base
+}