diff --git a/_locales/en/messages.json b/_locales/en/messages.json
index a41f2c60..629031f8 100644
--- a/_locales/en/messages.json
+++ b/_locales/en/messages.json
@@ -894,5 +894,25 @@
"prompt_add_tags_force_lang": {
"message": "The tags must be written in",
"description": ""
+ },
+ "prefs_Connection_type_Google_Gemini_API": {
+ "message": "Google Gemini API",
+ "description": ""
+ },
+ "prefs_GoogleGemini_API_Key": {
+ "message": "API Key",
+ "description": ""
+ },
+ "GoogleGemini_Models": {
+ "message": "Google Gemini API Models",
+ "description": ""
+ },
+ "GoogleGemini_Models_Fetch": {
+ "message": "Update Google Gemini Models list",
+ "description": ""
+ },
+ "GoogleGemini_Models_Error_fetching": {
+ "message": "Error trying to fetch Google Gemini models",
+ "description": ""
}
}
\ No newline at end of file
diff --git a/js/api/google_gemini.js b/js/api/google_gemini.js
new file mode 100644
index 00000000..ba8e7185
--- /dev/null
+++ b/js/api/google_gemini.js
@@ -0,0 +1,75 @@
+/*
+ * ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
+ * Copyright (C) 2024 Mic (m@micz.it)
+
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+
+
+export class GoogleGemini {
+
+ apiKey = '';
+ model = '';
+ stream = false;
+
+ constructor(apiKey, model, stream) {
+ this.apiKey = apiKey;
+ this.model = model;
+ this.stream = stream;
+ }
+
+
+ fetchModels = async () => {
+ try{
+ const response = await fetch("https://generativelanguage.googleapis.com/v1beta/models?key=" + this.apiKey, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json"
+ },
+ });
+
+ if (!response.ok) {
+ const errorDetail = await response.text();
+ let err_msg = "[ThunderAI] Google Gemini API request failed: " + response.status + " " + response.statusText + ", Detail: " + errorDetail;
+ console.error(err_msg);
+ let output = {};
+ output.ok = false;
+ output.error = errorDetail;
+ return output;
+ }
+
+ let output = {};
+ output.ok = true;
+ let output_response = await response.json();
+ console.log("[ThunderAI] Google Gemini API response: " + JSON.stringify(output_response));
+ output.response = output_response.models;
+
+ return output;
+ }catch (error) {
+ console.error("[ThunderAI] Google Gemini API request failed: " + error);
+ let output = {};
+ output.is_exception = true;
+ output.ok = false;
+ output.error = "Google Gemini API request failed: " + error;
+ return output;
+ }
+ }
+
+//TODO
+ fetchResponse = async (messages, maxTokens = 0) => {
+
+ }
+
+}
\ No newline at end of file
diff --git a/js/workers/model-worker-google_gemini.js b/js/workers/model-worker-google_gemini.js
new file mode 100644
index 00000000..3f364682
--- /dev/null
+++ b/js/workers/model-worker-google_gemini.js
@@ -0,0 +1,124 @@
+/*
+ * ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
+ * Copyright (C) 2024 Mic (m@micz.it)
+
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * This file contains a modified version of the code from the project at https://github.com/boxabirds/chatgpt-frontend-nobuild
+ * The original code has been released under the Apache License, Version 2.0.
+ */
+
+import { OpenAI } from '../api/openai.js';
+import { taLogger } from '../mzta-logger.js';
+
+let chatgpt_api_key = null;
+let chatgpt_model = '';
+let openai = null;
+let stopStreaming = false;
+let i18nStrings = null;
+let do_debug = false;
+let taLog = null;
+
+let conversationHistory = [];
+let assistantResponseAccumulator = '';
+
+self.onmessage = async function(event) {
+ if (event.data.type === 'init') {
+ chatgpt_api_key = event.data.chatgpt_api_key;
+ chatgpt_model = event.data.chatgpt_model;
+ openai = new OpenAI(chatgpt_api_key, chatgpt_model, true);
+ do_debug = event.data.do_debug;
+ i18nStrings = event.data.i18nStrings;
+ taLog = new taLogger('model-worker-openai', do_debug);
+ } else if (event.data.type === 'chatMessage') {
+ conversationHistory.push({ role: 'user', content: event.data.message });
+
+ const response = await openai.fetchResponse(conversationHistory); //4096);
+ postMessage({ type: 'messageSent' });
+
+ if (!response.ok) {
+ let error_message = '';
+ let errorDetail = '';
+ if(response.is_exception === true){
+ error_message = response.error;
+ }else{
+ try{
+ const errorJSON = await response.json();
+ errorDetail = JSON.stringify(errorJSON);
+ error_message = errorJSON.error.message;
+ }catch(e){
+ error_message = response.statusText;
+ }
+ taLog.log("error_message: " + JSON.stringify(error_message));
+ }
+ postMessage({ type: 'error', payload: i18nStrings["chatgpt_api_request_failed"] + ": " + response.status + " " + response.statusText + ", Detail: " + error_message + " " + errorDetail });
+ throw new Error("[ThunderAI] OpenAI ChatGPT API request failed: " + response.status + " " + response.statusText + ", Detail: " + error_message + " " + errorDetail);
+ }
+
+ const reader = response.body.getReader();
+ const decoder = new TextDecoder("utf-8");
+ let buffer = '';
+
+ while (true) {
+ if (stopStreaming) {
+ stopStreaming = false;
+ reader.cancel();
+ conversationHistory.push({ role: 'assistant', content: assistantResponseAccumulator });
+ assistantResponseAccumulator = '';
+ postMessage({ type: 'tokensDone' });
+ break;
+ }
+ const { done, value } = await reader.read();
+ if (done) {
+ conversationHistory.push({ role: 'assistant', content: assistantResponseAccumulator });
+ assistantResponseAccumulator = '';
+ postMessage({ type: 'tokensDone' });
+ break;
+ }
+ // lots of low-level OpenAI response parsing stuff
+ const chunk = decoder.decode(value);
+ buffer += chunk;
+ taLog.log("buffer " + buffer);
+ const lines = buffer.split("\n");
+ buffer = lines.pop();
+ let parsedLines = [];
+ try{
+ parsedLines = lines
+ .map((line) => line.replace(/^data: /, "").trim()) // Remove the "data: " prefix
+ .filter((line) => line !== "" && line !== "[DONE]") // Remove empty lines and "[DONE]"
+ // .map((line) => JSON.parse(line)); // Parse the JSON string
+ .map((line) => {
+ taLog.log("line: " + JSON.stringify(line));
+ return JSON.parse(line);
+ });
+ }catch(e){
+ taLog.error("Error parsing lines: " + e);
+ }
+
+ for (const parsedLine of parsedLines) {
+ const { choices } = parsedLine;
+ const { delta } = choices[0];
+ const { content } = delta;
+ // Update the UI with the new content
+ if (content) {
+ assistantResponseAccumulator += content;
+ postMessage({ type: 'newToken', payload: { token: content } });
+ }
+ }
+ }
+ } else if (event.data.type === 'stop') {
+ stopStreaming = true;
+ }
+};
diff --git a/options/mzta-options.css b/options/mzta-options.css
index 77a05fef..3467488b 100644
--- a/options/mzta-options.css
+++ b/options/mzta-options.css
@@ -104,11 +104,20 @@ tr.conntype_openai_comp_api, tr.conntype_openai_comp_api2{
background-color: rgb(213, 169, 238);
}
+tr.conntype_google_gemini_api, tr.conntype_google_gemini_api2{
+ background-color: rgb(233, 238, 169);
+}
+
#chatgpt_model_fetch_loading{
display: none;
font-style: italic;
}
+#google_gemini_model_fetch_loading{
+ display: none;
+ font-style: italic;
+}
+
.api_key-container {
position: relative;
width: 100%;
@@ -218,6 +227,10 @@ input.option-input[type="text"]{
background-color: rgb(89, 20, 129);
}
+ tr.conntype_google_gemini_api, tr.conntype_google_gemini_api2{
+ background-color: rgb(72, 77, 3);
+ }
+
.api_key-container .toggle-icon img {
filter: invert(1);
}
diff --git a/options/mzta-options.html b/options/mzta-options.html
index dc54e2eb..5790b670 100644
--- a/options/mzta-options.html
+++ b/options/mzta-options.html
@@ -109,6 +109,7 @@
@@ -174,6 +175,33 @@
+
+ |
+
+
+
+ 
+
+ |
+
+
+ |
+
+ |
+
+ __MSG_Loading__
+
+ |
+
|