forked from Shinonome/dots-hyprland
266 lines
8.8 KiB
JavaScript
266 lines
8.8 KiB
JavaScript
import Service from 'resource:///com/github/Aylur/ags/service.js';
|
|
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
|
|
|
|
import Gio from 'gi://Gio';
|
|
import GLib from 'gi://GLib';
|
|
import Soup from 'gi://Soup?version=3.0';
|
|
import { fileExists } from '../modules/.miscutils/files.js';
|
|
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);
|
|
}
|
|
// read gemini_history.json
|
|
const readfile = Utils.readFile(`${GLib.get_user_config_dir()}/gemini_history.json`)
|
|
const history_chat = JSON.parse(readfile)
|
|
const initMessages = history_chat
|
|
// api key
|
|
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`;
|
|
function replaceapidom(URL) {
|
|
if (fileExists(APIDOM_FILE_LOCATION)) {
|
|
var contents = Utils.readFile(APIDOM_FILE_LOCATION).trim();
|
|
var URL = URL.toString().replace("generativelanguage.googleapis.com", contents);
|
|
}
|
|
return URL;
|
|
}
|
|
const CHAT_MODELS = ["gemini-pro"]
|
|
const ONE_CYCLE_COUNT = 3;
|
|
|
|
class GeminiMessage extends Service {
|
|
static {
|
|
Service.register(this,
|
|
{
|
|
'delta': ['string'],
|
|
},
|
|
{
|
|
'content': ['string'],
|
|
'thinking': ['boolean'],
|
|
'done': ['boolean'],
|
|
});
|
|
}
|
|
|
|
_role = '';
|
|
_parts = [{ text: '' }];
|
|
_thinking = false;
|
|
_done = false;
|
|
_rawData = '';
|
|
|
|
constructor(role, content, thinking = false, done = false) {
|
|
super();
|
|
this._role = role;
|
|
this._parts = [{ text: content }];
|
|
this._thinking = thinking;
|
|
this._done = done;
|
|
}
|
|
|
|
get rawData() { return this._rawData }
|
|
set rawData(value) { this._rawData = value }
|
|
|
|
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._parts.map(part => part.text).join();
|
|
}
|
|
set content(content) {
|
|
this._parts = [{ text: content }];
|
|
this.notify('content')
|
|
this.emit('changed')
|
|
}
|
|
|
|
get parts() { return this._parts }
|
|
|
|
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);
|
|
}
|
|
|
|
parseSection() {
|
|
if(this._thinking) {
|
|
this._thinking = false;
|
|
this._parts[0].text= '';
|
|
}
|
|
const parsedData = JSON.parse(this._rawData);
|
|
const delta = parsedData.candidates[0].content.parts[0].text;
|
|
this._parts[0].text += delta;
|
|
// this.emit('delta', delta);
|
|
this.notify('content');
|
|
this._rawData = '';
|
|
}
|
|
}
|
|
|
|
class GeminiService extends Service {
|
|
static {
|
|
Service.register(this, {
|
|
'initialized': [],
|
|
'clear': [],
|
|
'newMsg': ['int'],
|
|
'hasKey': ['boolean'],
|
|
});
|
|
}
|
|
|
|
_assistantPrompt = true;
|
|
_messages = [];
|
|
_cycleModels = true;
|
|
_requestCount = 0;
|
|
_temperature = userOptions.ai.defaultTemperature;
|
|
_modelIndex = 0;
|
|
_key = '';
|
|
_decoder = new TextDecoder();
|
|
|
|
constructor() {
|
|
super();
|
|
|
|
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 = [];
|
|
|
|
this.emit('initialized');
|
|
}
|
|
|
|
get modelName() { return CHAT_MODELS[this._modelIndex] }
|
|
|
|
get keyPath() { return KEY_FILE_LOCATION }
|
|
get key() { return this._key }
|
|
set key(keyValue) {
|
|
this._key = keyValue;
|
|
Utils.writeFile(this._key, KEY_FILE_LOCATION)
|
|
.then(this.emit('hasKey', true))
|
|
.catch(err => print(err));
|
|
}
|
|
|
|
get cycleModels() { return this._cycleModels }
|
|
set cycleModels(value) {
|
|
this._cycleModels = value;
|
|
if (!value) this._modelIndex = 0;
|
|
else {
|
|
this._modelIndex = (this._requestCount - (this._requestCount % ONE_CYCLE_COUNT)) % CHAT_MODELS.length;
|
|
}
|
|
}
|
|
|
|
get temperature() { return this._temperature }
|
|
set temperature(value) { this._temperature = value; }
|
|
|
|
get messages() { return this._messages }
|
|
get lastMessage() { return this._messages[this._messages.length - 1] }
|
|
|
|
clear() {
|
|
if (this._assistantPrompt)
|
|
this._messages = [...initMessages];
|
|
else
|
|
this._messages = [];
|
|
this.emit('clear');
|
|
}
|
|
|
|
get assistantPrompt() { return this._assistantPrompt; }
|
|
set assistantPrompt(value) {
|
|
this._assistantPrompt = value;
|
|
if (value) this._messages = [...initMessages];
|
|
else this._messages = [];
|
|
}
|
|
|
|
readResponse(stream, aiResponse) {
|
|
stream.read_line_async(
|
|
0, null,
|
|
(stream, res) => {
|
|
try {
|
|
const [bytes] = stream.read_line_finish(res);
|
|
const line = this._decoder.decode(bytes);
|
|
if (line == '[{') { // beginning of response
|
|
aiResponse._rawData += '{';
|
|
this.thinking = false;
|
|
}
|
|
else if (line == ',\u000d' || line == ']') { // end of stream pulse
|
|
aiResponse.parseSection();
|
|
}
|
|
else // Normal content
|
|
aiResponse._rawData += line;
|
|
|
|
this.readResponse(stream, aiResponse);
|
|
} catch {
|
|
aiResponse.done = true;
|
|
// the way to save all the history messages for gemini
|
|
Utils.writeFile(JSON.stringify(this._messages.map(msg => { let m = { role: msg.role, parts: msg.parts }; return m; })),`${GLib.get_user_config_dir()}/gemini_history.json`);
|
|
return;
|
|
}
|
|
});
|
|
}
|
|
|
|
addMessage(role, message) {
|
|
this._messages.push(new GeminiMessage(role, message));
|
|
this.emit('newMsg', this._messages.length - 1);
|
|
}
|
|
|
|
send(msg) {
|
|
this._messages.push(new GeminiMessage('user', msg));
|
|
this.emit('newMsg', this._messages.length - 1);
|
|
const aiResponse = new GeminiMessage('model', 'thinking...', true, false)
|
|
|
|
const body =
|
|
{
|
|
"contents": this._messages.map(msg => { let m = { role: msg.role, parts: msg.parts }; return m; }),
|
|
// "safetySettings": [
|
|
// { category: "HARM_CATEGORY_DEROGATORY", threshold: "BLOCK_NONE", },
|
|
// { category: "HARM_CATEGORY_HARASSMENT", threshold: "BLOCK_NONE", },
|
|
// { category: "HARM_CATEGORY_HATE_SPEECH", threshold: "BLOCK_NONE", },
|
|
// { category: "HARM_CATEGORY_SEXUALLY_EXPLICIT", threshold: "BLOCK_NONE", },
|
|
// { category: "HARM_CATEGORY_UNSPECIFIED", threshold: "BLOCK_NONE", },
|
|
// ],
|
|
"generationConfig": {
|
|
"temperature": this._temperature,
|
|
},
|
|
// "key": this._key,
|
|
// "apiKey": this._key,
|
|
};
|
|
|
|
const session = new Soup.Session();
|
|
const message = new Soup.Message({
|
|
method: 'POST',
|
|
uri: GLib.Uri.parse(replaceapidom(`https://generativelanguage.googleapis.com/v1/models/${this.modelName}:streamGenerateContent?key=${this._key}`), GLib.UriFlags.NONE),
|
|
});
|
|
message.request_headers.append('Content-Type', `application/json`);
|
|
message.set_request_body_from_bytes('application/json', new GLib.Bytes(JSON.stringify(body)));
|
|
|
|
session.send_async(message, GLib.DEFAULT_PRIORITY, null, (_, result) => {
|
|
const stream = session.send_finish(result);
|
|
this.readResponse(new Gio.DataInputStream({
|
|
close_base_stream: true,
|
|
base_stream: stream
|
|
}), aiResponse);
|
|
});
|
|
this._messages.push(aiResponse);
|
|
this.emit('newMsg', this._messages.length - 1);
|
|
|
|
if (this._cycleModels) {
|
|
this._requestCount++;
|
|
if (this._cycleModels)
|
|
this._modelIndex = (this._requestCount - (this._requestCount % ONE_CYCLE_COUNT)) % CHAT_MODELS.length;
|
|
}
|
|
}
|
|
}
|
|
|
|
export default new GeminiService();
|
|
|