diff --git a/.config/ags/modules/.configuration/user_options.js b/.config/ags/modules/.configuration/user_options.js index 94327c69..0f3e1b5c 100644 --- a/.config/ags/modules/.configuration/user_options.js +++ b/.config/ags/modules/.configuration/user_options.js @@ -7,6 +7,8 @@ let configOptions = { 'ai': { 'defaultGPTProvider': "openai", 'defaultTemperature': 0.9, + 'enhancements': true, + 'useHistory': true, 'writingCursor': " ...", // Warning: Using weird characters can mess up Markdown rendering }, 'animations': { diff --git a/.config/ags/modules/sideleft/apis/gemini.js b/.config/ags/modules/sideleft/apis/gemini.js index 57b966dd..6c65af64 100644 --- a/.config/ags/modules/sideleft/apis/gemini.js +++ b/.config/ags/modules/sideleft/apis/gemini.js @@ -112,6 +112,15 @@ export const GeminiSettings = () => MarginRevealer({ GeminiService.safe = newValue; }, }), + ConfigToggle({ + icon: 'history', + name: 'History', + desc: 'Saves chat history', + initValue: GeminiService.useHistory, + onChange: (self, newValue) => { + GeminiService.useHistory = newValue; + }, + }), ] }) ] @@ -209,6 +218,10 @@ export const sendMessage = (text) => { // Commands if (text.startsWith('/')) { if (text.startsWith('/clear')) clearChat(); + else if (text.startsWith('/load')) { + clearChat(); + GeminiService.loadHistory(); + } else if (text.startsWith('/model')) chatContent.add(SystemMessage(`Currently using \`${GeminiService.modelName}\``, '/model', geminiView)) else if (text.startsWith('/prompt')) { const firstSpaceIndex = text.indexOf(' '); @@ -265,10 +278,10 @@ export const geminiView = Box({ }) // Always scroll to bottom with new content const adjustment = scrolledWindow.get_vadjustment(); - adjustment.connect("changed", () => { + adjustment.connect("changed", () => Utils.timeout(1, () => { if(!chatEntry.hasFocus) return; adjustment.set_value(adjustment.get_upper() - adjustment.get_page_size()); - }) + })) } })] }); \ No newline at end of file diff --git a/.config/ags/services/gemini.js b/.config/ags/services/gemini.js index ae7fe249..b0bcd526 100644 --- a/.config/ags/services/gemini.js +++ b/.config/ags/services/gemini.js @@ -6,10 +6,13 @@ import GLib from 'gi://GLib'; import Soup from 'gi://Soup?version=3.0'; import { fileExists } from '../modules/.miscutils/files.js'; +const HISTORY_DIR = `${GLib.get_user_cache_dir()}/ags/user/ai/chats/`; +const HISTORY_FILENAME = `gemini.txt`; +const HISTORY_PATH = HISTORY_DIR + HISTORY_FILENAME; 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 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: "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\"" }], }, { role: "user", parts: [{ text: "raise volume by 5%" }], }, @@ -25,6 +28,12 @@ const initMessages = { 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```" }], }, ]; + +if (!fileExists(`${GLib.get_user_config_dir()}/gemini_history.json`)) { + Utils.execAsync([`bash`, `-c`, `touch ${GLib.get_user_config_dir()}/gemini_history.json`]).catch(print); + Utils.writeFile('[ ]', `${GLib.get_user_config_dir()}/gemini_history.json`).catch(print); +} + Utils.exec(`mkdir -p ${GLib.get_user_cache_dir()}/ags/user/ai`); const KEY_FILE_LOCATION = `${GLib.get_user_cache_dir()}/ags/user/ai/google_key.txt`; const APIDOM_FILE_LOCATION = `${GLib.get_user_cache_dir()}/ags/user/ai/google_api_dom.txt`; @@ -133,14 +142,15 @@ class GeminiService extends Service { }); } - _assistantPrompt = true; - _messages = []; + _assistantPrompt = userOptions.ai.enhancements; _cycleModels = true; + _usingHistory = userOptions.ai.useHistory; + _key = ''; _requestCount = 0; _safe = true; _temperature = userOptions.ai.defaultTemperature; + _messages = []; _modelIndex = 0; - _key = ''; _decoder = new TextDecoder(); constructor() { @@ -149,8 +159,9 @@ class GeminiService extends Service { if (fileExists(KEY_FILE_LOCATION)) this._key = Utils.readFile(KEY_FILE_LOCATION).trim(); else this.emit('hasKey', false); - if (this._assistantPrompt) this._messages = [...initMessages]; - else this._messages = []; + // if (this._usingHistory) Utils.timeout(1000, () => this.loadHistory()); + if (this._usingHistory) this.loadHistory(); + else this._messages = this._assistantPrompt ? [...initMessages] : []; this.emit('initialized'); } @@ -175,6 +186,12 @@ class GeminiService extends Service { } } + get useHistory() { return this._usingHistory; } + set useHistory(value) { + if (value && !this._usingHistory) this.loadHistory(); + this._usingHistory = value; + } + get safe() { return this._safe } set safe(value) { this._safe = value; } @@ -184,11 +201,40 @@ class GeminiService extends Service { get messages() { return this._messages } get lastMessage() { return this._messages[this._messages.length - 1] } + saveHistory() { + Utils.writeFile(JSON.stringify(this._messages.map(msg => { + let m = { role: msg.role, parts: msg.parts }; return m; + })), HISTORY_PATH); + } + + loadHistory() { + this._messages = []; + this.appendHistory(); + this._usingHistory = true; + } + + appendHistory() { + if (fileExists(HISTORY_PATH)) { + const readfile = Utils.readFile(HISTORY_PATH); + JSON.parse(readfile).forEach(element => { + // this._messages.push(element); + this.addMessage(element.role, element.parts[0].text); + }); + // console.log(this._messages) + // this._messages = this._messages.concat(JSON.parse(readfile)); + // for (let index = 0; index < this._messages.length; index++) { + // this.emit('newMsg', index); + // } + } + else { + this._messages = this._assistantPrompt ? [...initMessages] : [] + Utils.exec(`bash -c 'mkdir -p ${HISTORY_DIR} && touch ${HISTORY_PATH}'`) + } + } + clear() { - if (this._assistantPrompt) - this._messages = [...initMessages]; - else - this._messages = []; + this._messages = this._assistantPrompt ? [...initMessages] : []; + if (this._usingHistory) this.saveHistory(); this.emit('clear'); } @@ -220,6 +266,7 @@ class GeminiService extends Service { this.readResponse(stream, aiResponse); } catch { aiResponse.done = true; + if (this._usingHistory) this.saveHistory(); return; } });