diff --git a/CHANGELOG.md b/CHANGELOG.md index 44ea76ba..3dcee592 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@

Version 3.5.0 - ??/??/2025

Version 3.4.1 - 12/05/2025

diff --git a/README.md b/README.md index 7aca3c51..989e331d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # ![ThunderAI icon](images/icon-32px.png "ThunderAI") ThunderAI -ThunderAI is a Thunderbird Addon that uses the capabilities of ChatGPT, Google Gemini or Ollama to enhance email management. +ThunderAI is a Thunderbird Addon that uses the capabilities of ChatGPT, Google Gemini, Anthropic or Ollama to enhance email management. It enables users to analyse, write, correct, assign tags, create calendar events or tasks and optimize their emails, facilitating more effective and professional communication. diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 61c51288..d5e2bf56 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1192,7 +1192,7 @@ "description" : "" }, "prompt_spamfilter_full_text" : { - "message" : "Analyze the following email and determine if it is spam or not. Consider factors such as suspicious keywords, excessive promotional language, misleading subject lines, requests for personal information, and unusual sender addresses.\nProvide a value from 0 (not spam) to 100 (spam) and an explaination of no more than 10 words.\nGenerate a response in JSON format only. Do not include any additional text or explanation; provide only the JSON. Here is the format to be used:\n{\n\"spamValue\": ,\n\"explanation\": \"Brief explanation of your reasoning\",\n}\nHere there are the mail information:\nSender: \"{%author%}\"\nSubject: \"{%mail_subject%}\"\nHtml body: \"{%mail_html_body%}\"", + "message" : "Analyze the following email and determine if it is spam or not. Consider factors such as suspicious keywords, excessive promotional language, misleading subject lines, requests for personal information, and unusual sender addresses.\nProvide a value from 0 (not spam) to 100 (spam) and an explanation of no more than 10 words.\nIn case of missing message data, set the value to 0 (not spam), and give the reason.\nGenerate a response in JSON format only. Do not include any additional text or explanation; provide only the JSON. Here is the format to be used:\n{\n\"spamValue\": ,\n\"explanation\": \"Brief explanation of your reasoning\"\n}\nHere there are the mail information:\nSender: \"{%author%}\"\nSubject: \"{%mail_subject%}\"\nHtml body: \"{%mail_html_body%}\"", "description" : "" }, "SpamFilter_prompt_prefs_title": { @@ -1438,5 +1438,61 @@ "prefs_OptionText_Project_No_temporary_chat_warn": { "message": "If a Project is specified in the options or in a prompt, the temporary chat will not be used.", "description": "" + }, + "prefs_Anthropic_API_Key": { + "message": "Anthropic API Key", + "description": "" + }, + "prefs_Connection_type_Anthropic_API": { + "message": "Anthropic API", + "description": "" + }, + "Anthropic_Models": { + "message": "Anthropic Models", + "description": "" + }, + "Anthropic_Models_Fetch": { + "message": "Update Anthropic Models list", + "description": "" + }, + "Anthropic_Models_Error_fetching": { + "message": "Error trying to fetch Anthropic models", + "description": "" + }, + "Anthropic_Version": { + "message": "Anthropic API Version", + "description": "" + }, + "Anthropic_Version_Info": { + "message": "REQUIRED. Do not change this value unless you know what you're doing. More info at: ", + "description": "" + }, + "prefs_OptionText_anthropic_max_tokens": { + "message": "Anthropic Max Tokens", + "description": "" + }, + "prefs_OptionText_anthropic_max_tokens_Info": { + "message": "The maximum number of tokens to generate in the completion. The token count of your prompt plus max_tokens cannot exceed the model's context length.", + "description": "" + }, + "anthropic_empty_apikey": { + "message": "You've not added an API Key for the Anthropic API. Please insert one in the options page.", + "description": "" + }, + "anthropic_empty_model": { + "message": "You've not choosen a model for the Anthropic API. Please choose one in the options page.", + "description": "" + }, + "anthropic_empty_version": { + "message": "You've not added a version string for the Anthropic API. Please insert one in the options page.", + "description": "" + }, + "anthropic_api_request_failed": { + "message": "Anthropic API request failed", + "description": "" + }, + "anthropic_api_connecting": { + "message": "Attempting to connect to Anthropic API using the API key provided", + "description": "" } } \ No newline at end of file diff --git a/api_webchat/controller.js b/api_webchat/controller.js index 0666ae91..04b0478b 100644 --- a/api_webchat/controller.js +++ b/api_webchat/controller.js @@ -56,6 +56,12 @@ switch (llm) { case "openai_comp_api": worker = new Worker('../js/workers/model-worker-openai_comp.js', { type: 'module' }); break; + case "anthropic_api": + worker = new Worker('../js/workers/model-worker-anthropic.js', { type: 'module' }); + break; + default: + console.error('[ThunderAI] API WebChat Unknown LLM type:', llm); + break; } messagesArea.init(worker); @@ -113,6 +119,18 @@ switch (llm) { browser.runtime.sendMessage({command: "openai_comp_api_ready_" + call_id, window_id: (await browser.windows.getCurrent()).id}); break; } + case "anthropic_api": { + let prefs_api = await browser.storage.sync.get({anthropic_api_key: '', anthropic_model: '', anthropic_version: '2023-06-01', anthropic_max_tokens: 4096, do_debug: false}); + let i18nStrings = {}; + i18nStrings["anthropic_api_request_failed"] = browser.i18n.getMessage('anthropic_api_request_failed'); + i18nStrings["error_connection_interrupted"] = browser.i18n.getMessage('error_connection_interrupted'); + messageInput.setModel(prefs_api.anthropic_model); + messagesArea.setLLMName("Anthropic"); + worker.postMessage({ type: 'init', anthropic_api_key: prefs_api.anthropic_api_key, anthropic_model: prefs_api.anthropic_model, anthropic_version: prefs_api.anthropic_version, anthropic_max_tokens: prefs_api.anthropic_max_tokens, do_debug: prefs_api.do_debug, i18nStrings: i18nStrings}); + messagesArea.appendUserMessage(browser.i18n.getMessage("anthropic_api_connecting") + " " +browser.i18n.getMessage("AndModel") + " \"" + prefs_api.anthropic_model + "\"...", "info"); + browser.runtime.sendMessage({command: "anthropic_api_ready_" + call_id, window_id: (await browser.windows.getCurrent()).id}); + break; + } } //let prefs_ph = await browser.storage.sync.get({placeholders_use_default_value: false}); diff --git a/js/api/anthropic.js b/js/api/anthropic.js new file mode 100644 index 00000000..662b45df --- /dev/null +++ b/js/api/anthropic.js @@ -0,0 +1,107 @@ +/* + * ThunderAI [https://micz.it/thunderbird-addon-thunderai/] + * Copyright (C) 2024 - 2025 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 . + */ + +// Some original methods derived from https://github.com/ali-raheem/Aify/blob/4ece286095ea7a6cf89d696902e6b81b5d1c3a4b/plugin/html/API.js + + +export class Anthropic { + + apiKey = ''; + version = ''; + model = ''; + max_tokens = 4096; + stream = false; + + constructor(apiKey, version, model, max_tokens = 4096, stream = false) { + this.apiKey = apiKey; + this.version = version; + this.model = model; + this.max_tokens = max_tokens > 0 ? max_tokens : 4096; + this.stream = stream; + } + + + fetchModels = async () => { + try{ + const response = await fetch("https://api.anthropic.com/v1/models", { + method: "GET", + headers: { + "Content-Type": "application/json", + "x-api-key": this.apiKey, + "anthropic-version": this.version, + }, + }); + + if (!response.ok) { + const errorDetail = await response.text(); + let err_msg = "[ThunderAI] Anthropic 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(); + output.response = output_response.data; + + return output; + }catch (error) { + console.error("[ThunderAI] Anthropic API request failed: " + error); + let output = {}; + output.is_exception = true; + output.ok = false; + output.error = "Anthropic API request failed: " + error; + return output; + } + } + + fetchResponse = async (messages) => { + + // console.log(">>>>>>>>>>> Anthropic API request: " + JSON.stringify(messages)); + + try { + const response = await fetch("https://api.anthropic.com/v1/messages", { + method: "POST", + headers: { + "Content-Type": "application/json", + "x-api-key": this.apiKey, + "anthropic-version": this.version, + "anthropic-dangerous-direct-browser-access": "true", + }, + body: JSON.stringify({ + model: this.model, + max_tokens: this.max_tokens, + messages: messages, + stream: this.stream, + }), + }); + return response; + }catch (error) { + console.error("[ThunderAI] Anthropic API request failed: " + error); + let output = {}; + output.is_exception = true; + output.ok = false; + output.error = "Anthropic API request failed: " + error; + return output; + } + } + +} \ No newline at end of file diff --git a/js/mzta-special-commands.js b/js/mzta-special-commands.js index 0a86c411..2ac7ac38 100644 --- a/js/mzta-special-commands.js +++ b/js/mzta-special-commands.js @@ -48,6 +48,12 @@ case "openai_comp_api": this.worker = new Worker(new URL('./workers/model-worker-openai_comp.js', import.meta.url), { type: 'module' }); break; + case "anthropic_api": + this.worker = new Worker(new URL('./workers/model-worker-anthropic.js', import.meta.url), { type: 'module' }); + break; + default: + this.logger.log("Invalid LLM type: " + this.llm); + throw new Error("Invalid LLM type: " + this.llm); } } @@ -73,6 +79,11 @@ this.worker.postMessage({ type: 'init', openai_comp_host: prefs_api.openai_comp_host, openai_comp_model: prefs_api.openai_comp_model, openai_comp_api_key: prefs_api.openai_comp_api_key, openai_comp_use_v1: prefs_api.openai_comp_use_v1, do_debug: this.do_debug, i18nStrings: ''}); break; } + case "anthropic_api": { + let prefs_api = await browser.storage.sync.get({anthropic_api_key: '', anthropic_model: '', anthropic_version: '2023-06-01', anthropic_max_tokens: 4096}); + this.worker.postMessage({ type: 'init', anthropic_api_key: prefs_api.anthropic_api_key, anthropic_model: prefs_api.anthropic_model, anthropic_version: prefs_api.anthropic_version, anthropic_max_tokens: prefs_api.anthropic_max_tokens, do_debug: this.do_debug, i18nStrings: ''}); + break; + } } } diff --git a/js/workers/model-worker-anthropic.js b/js/workers/model-worker-anthropic.js new file mode 100644 index 00000000..a16578c9 --- /dev/null +++ b/js/workers/model-worker-anthropic.js @@ -0,0 +1,148 @@ +/* + * ThunderAI [https://micz.it/thunderbird-addon-thunderai/] + * Copyright (C) 2024 - 2025 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 { Anthropic } from '../api/anthropic.js'; +import { taLogger } from '../mzta-logger.js'; + +let anthropic_api_key = null; +let anthropic_model = ''; +let anthropic = 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') { + anthropic_api_key = event.data.anthropic_api_key; + anthropic_model = event.data.anthropic_model; + anthropic = new Anthropic(anthropic_api_key, event.data.anthropic_version, anthropic_model, event.data.anthropic_max_tokens, true); + do_debug = event.data.do_debug; + i18nStrings = event.data.i18nStrings; + taLog = new taLogger('model-worker-anthropic', do_debug); + } else if (event.data.type === 'chatMessage') { + conversationHistory.push({ role: 'user', content: event.data.message }); + + const response = await anthropic.fetchResponse(conversationHistory); + 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["anthropic_api_request_failed"] + ": " + response.status + " " + response.statusText + ", Detail: " + error_message + " " + errorDetail }); + throw new Error("[ThunderAI] Anthropic 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 Anthropic response parsing stuff + const chunk = decoder.decode(value); + buffer += chunk; + taLog.log("buffer " + buffer); + const lines = buffer.split("\n"); + buffer = lines.pop(); + + for (const line of lines) { + const cleanLine = line.trim(); + + // Ignore ping events + if (cleanLine === '' || cleanLine.startsWith('event: ping')) { + continue; + } + + // Remove "data: " and parse the JSON + if (cleanLine.startsWith('data: ')) { + const jsonPart = cleanLine.replace(/^data: /, ''); + let parsedData = null; + + try { + parsedData = JSON.parse(jsonPart); + } catch (e) { + taLog.error("JSON parse error: " + e); + continue; + } + + // Events handling + switch (parsedData.type) { + case 'content_block_delta': + if (parsedData.delta && parsedData.delta.text) { + const token = parsedData.delta.text; + assistantResponseAccumulator += token; + postMessage({ type: 'newToken', payload: { token } }); + } + break; + + case 'content_block_start': + // optional + break; + + case 'message_start': + // optional + break; + + case 'message_stop': + conversationHistory.push({ role: 'assistant', content: assistantResponseAccumulator }); + assistantResponseAccumulator = ''; + postMessage({ type: 'tokensDone' }); + return; // end the loop + } + } + } + + } + } else if (event.data.type === 'stop') { + stopStreaming = true; + } +}; diff --git a/mzta-background.js b/mzta-background.js index 0005a26a..4be7c798 100644 --- a/mzta-background.js +++ b/mzta-background.js @@ -337,207 +337,212 @@ async function openChatGPT(promptText, action, curr_tabId, prompt_name = '', do_ switch(prefs.connection_type){ case 'chatgpt_web': - // We are using the ChatGPT web interface + { + // We are using the ChatGPT web interface - let rand_call_id = '_chatgptweb_' + generateCallID(); - let call_opt = ''; + let rand_call_id = '_chatgptweb_' + generateCallID(); + let call_opt = ''; - let _wait_time = 1000; - let _base_url = "https://chatgpt.com"; - let _webproject_set = false; - let _custom_gpt_set = false; - let _use_prompt_info_custom_gpt = false; - let _custom_model = sanitizeChatGPTModelData(prompt_info.chatgpt_web_model != '' ? prompt_info.chatgpt_web_model : prefs.chatgpt_web_model); - let _web_project = sanitizeChatGPTWebCustomData(prompt_info.chatgpt_web_project != '' ? prompt_info.chatgpt_web_project : prefs.chatgpt_web_project) - let _custom_gpt = sanitizeChatGPTWebCustomData(prompt_info.chatgpt_web_custom_gpt != '' ? prompt_info.chatgpt_web_custom_gpt : prefs.chatgpt_web_custom_gpt) + let _wait_time = 1000; + let _base_url = "https://chatgpt.com"; + let _webproject_set = false; + let _custom_gpt_set = false; + let _use_prompt_info_custom_gpt = false; + let _custom_model = sanitizeChatGPTModelData(prompt_info.chatgpt_web_model != '' ? prompt_info.chatgpt_web_model : prefs.chatgpt_web_model); + let _web_project = sanitizeChatGPTWebCustomData(prompt_info.chatgpt_web_project != '' ? prompt_info.chatgpt_web_project : prefs.chatgpt_web_project) + let _custom_gpt = sanitizeChatGPTWebCustomData(prompt_info.chatgpt_web_custom_gpt != '' ? prompt_info.chatgpt_web_custom_gpt : prefs.chatgpt_web_custom_gpt) - if(prefs.chatgpt_web_tempchat){ - call_opt += '&temporary-chat=true'; - } + if(prefs.chatgpt_web_tempchat){ + call_opt += '&temporary-chat=true'; + } - if((prompt_info.chatgpt_web_model != '') || (prefs.chatgpt_web_model != '')){ - call_opt += '&model=' + _custom_model; - } + if((prompt_info.chatgpt_web_model != '') || (prefs.chatgpt_web_model != '')){ + call_opt += '&model=' + _custom_model; + } - taLog.log("[chatgpt_web] call_opt: " + call_opt); + taLog.log("[chatgpt_web] call_opt: " + call_opt); - // If there is a custom gpt on the prompt, but also a web_project on the prefs, we need to use the custom gpt - _use_prompt_info_custom_gpt = (prompt_info.chatgpt_web_custom_gpt != '' && prompt_info.chatgpt_web_project == ''); + // If there is a custom gpt on the prompt, but also a web_project on the prefs, we need to use the custom gpt + _use_prompt_info_custom_gpt = (prompt_info.chatgpt_web_custom_gpt != '' && prompt_info.chatgpt_web_project == ''); - if(!_use_prompt_info_custom_gpt && ((prompt_info.chatgpt_web_project != '') || (prefs.chatgpt_web_project != ''))){ - _base_url += _web_project; - _webproject_set = true; - _wait_time = 2000; - } - if(!_webproject_set && ((prompt_info.chatgpt_web_custom_gpt != '') || (prefs.chatgpt_web_custom_gpt != ''))){ - _base_url += _custom_gpt; - _custom_gpt_set = true; - } + if(!_use_prompt_info_custom_gpt && ((prompt_info.chatgpt_web_project != '') || (prefs.chatgpt_web_project != ''))){ + _base_url += _web_project; + _webproject_set = true; + _wait_time = 2000; + } + if(!_webproject_set && ((prompt_info.chatgpt_web_custom_gpt != '') || (prefs.chatgpt_web_custom_gpt != ''))){ + _base_url += _custom_gpt; + _custom_gpt_set = true; + } - let win_options = { - url: _base_url + "?call_id=" + rand_call_id + call_opt, - type: "popup", - } - - taLog.log("[chatgpt_web] prefs.chatgpt_win_width: " + prefs.chatgpt_win_width + ", prefs.chatgpt_win_height: " + prefs.chatgpt_win_height); + let win_options = { + url: _base_url + "?call_id=" + rand_call_id + call_opt, + type: "popup", + } + + taLog.log("[chatgpt_web] prefs.chatgpt_win_width: " + prefs.chatgpt_win_width + ", prefs.chatgpt_win_height: " + prefs.chatgpt_win_height); - if((prefs.chatgpt_win_width != '') && (prefs.chatgpt_win_height != '') && (prefs.chatgpt_win_width != 0) && (prefs.chatgpt_win_height != 0)){ - win_options.width = prefs.chatgpt_win_width, - win_options.height = prefs.chatgpt_win_height - } + if((prefs.chatgpt_win_width != '') && (prefs.chatgpt_win_height != '') && (prefs.chatgpt_win_width != 0) && (prefs.chatgpt_win_height != 0)){ + win_options.width = prefs.chatgpt_win_width, + win_options.height = prefs.chatgpt_win_height + } - const listener = (message, sender, sendResponse) => { - async function handleChatGptWeb(createdTab) { - taLog.log("ChatGPT web interface script started..."); + const listener = (message, sender, sendResponse) => { + async function handleChatGptWeb(createdTab) { + taLog.log("ChatGPT web interface script started..."); - let _gpt_model = getGPTWebModelString(_custom_model); + let _gpt_model = getGPTWebModelString(_custom_model); - taLog.log("_custom_model: " + _custom_model); - taLog.log("_gpt_model: " + _gpt_model); + taLog.log("_custom_model: " + _custom_model); + taLog.log("_gpt_model: " + _gpt_model); - let originalText = prompt_info.selection_text; - if((originalText == null) || (originalText == "")) { - originalText = prompt_info.body_text; + let originalText = prompt_info.selection_text; + if((originalText == null) || (originalText == "")) { + originalText = prompt_info.body_text; + } + //console.log(">>>>>>>>>> prompt_info: " + JSON.stringify(prompt_info)); + let pre_script = `let mztaWinId = `+ createdTab.windowId +`; + let mztaStatusPageDesc="`+ browser.i18n.getMessage("prefs_status_page") +`"; + let mztaForceCompletionDesc="`+ browser.i18n.getMessage("chatgpt_force_completion") +`"; + let mztaForceCompletionTitle="`+ browser.i18n.getMessage("chatgpt_force_completion_title") +`"; + let mztaDoCustomText=`+ do_custom_text +`; + let mztaPromptName="[`+ i18nConditionalGet(prompt_name) +`]"; + let mztaPhDefVal="`+(prefs.placeholders_use_default_value?'1':'0')+`"; + let mztaGPTModel="`+ (_custom_gpt_set ? '' : _gpt_model) +`"; + let mztaDoDebug="`+(prefs.do_debug?'1':'0')+`"; + let mztaUseDiffViewer="`+(prompt_info.use_diff_viewer=='1'?'1':'0')+`"; + let mztaOriginalText="`+ JSON.stringify(originalText).slice(1, -1) +`"; + `; + + taLog.log("Waiting " + _wait_time + " millisec"); + await new Promise(resolve => setTimeout(resolve, _wait_time)); + taLog.log("Waiting " + _wait_time + " millisec done"); + + await browser.tabs.executeScript(createdTab.id, { code: pre_script + mzta_script, matchAboutBlank: false }); + // let mailMessage = await browser.messageDisplay.getDisplayedMessage(curr_tabId); + let mailMessageId = -1; + if(mailMessage) mailMessageId = mailMessage.id; + browser.tabs.sendMessage(createdTab.id, { command: "chatgpt_send", prompt: promptText, action: action, tabId: curr_tabId, mailMessageId: mailMessageId}); + taLog.log('[ChatGPT Web] Connection succeded!'); + taLog.log("[ThunderAI] ChatGPT Web script injected successfully"); + browser.runtime.onMessage.removeListener(listener); } - //console.log(">>>>>>>>>> prompt_info: " + JSON.stringify(prompt_info)); - let pre_script = `let mztaWinId = `+ createdTab.windowId +`; - let mztaStatusPageDesc="`+ browser.i18n.getMessage("prefs_status_page") +`"; - let mztaForceCompletionDesc="`+ browser.i18n.getMessage("chatgpt_force_completion") +`"; - let mztaForceCompletionTitle="`+ browser.i18n.getMessage("chatgpt_force_completion_title") +`"; - let mztaDoCustomText=`+ do_custom_text +`; - let mztaPromptName="[`+ i18nConditionalGet(prompt_name) +`]"; - let mztaPhDefVal="`+(prefs.placeholders_use_default_value?'1':'0')+`"; - let mztaGPTModel="`+ (_custom_gpt_set ? '' : _gpt_model) +`"; - let mztaDoDebug="`+(prefs.do_debug?'1':'0')+`"; - let mztaUseDiffViewer="`+(prompt_info.use_diff_viewer=='1'?'1':'0')+`"; - let mztaOriginalText="`+ JSON.stringify(originalText).slice(1, -1) +`"; - `; + + if (message.command === "chatgpt_web_ready_" + rand_call_id) { + return handleChatGptWeb(sender.tab) + } + return false; + } - taLog.log("Waiting " + _wait_time + " millisec"); - await new Promise(resolve => setTimeout(resolve, _wait_time)); - taLog.log("Waiting " + _wait_time + " millisec done"); - - await browser.tabs.executeScript(createdTab.id, { code: pre_script + mzta_script, matchAboutBlank: false }); - // let mailMessage = await browser.messageDisplay.getDisplayedMessage(curr_tabId); - let mailMessageId = -1; - if(mailMessage) mailMessageId = mailMessage.id; - browser.tabs.sendMessage(createdTab.id, { command: "chatgpt_send", prompt: promptText, action: action, tabId: curr_tabId, mailMessageId: mailMessageId}); - taLog.log('[ChatGPT Web] Connection succeded!'); - taLog.log("[ThunderAI] ChatGPT Web script injected successfully"); - browser.runtime.onMessage.removeListener(listener); - } - - if (message.command === "chatgpt_web_ready_" + rand_call_id) { - return handleChatGptWeb(sender.tab) - } - return false; + browser.runtime.onMessage.addListener(listener); + await browser.windows.create(win_options); } - - browser.runtime.onMessage.addListener(listener); - await browser.windows.create(win_options); break; // chatgpt_web - END - case 'chatgpt_api': + case 'chatgpt_api': + { // We are using the ChatGPT API - let rand_call_id2 = '_openai_' + generateCallID(); + let rand_call_id2 = '_openai_' + generateCallID(); - const listener2 = (message, sender, sendResponse) => { + const listener2 = (message, sender, sendResponse) => { - function handleChatGptApi(createdTab) { - let mailMessageId2 = -1; - if(mailMessage) mailMessageId2 = mailMessage.id; + function handleChatGptApi(createdTab) { + let mailMessageId2 = -1; + if(mailMessage) mailMessageId2 = mailMessage.id; - // check if the config is present, or give a message error - if (prefs.chatgpt_api_key == '') { - browser.tabs.sendMessage(createdTab.id, { command: "api_error", error: browser.i18n.getMessage('chatgpt_empty_apikey')}); - return; + // check if the config is present, or give a message error + if (prefs.chatgpt_api_key == '') { + browser.tabs.sendMessage(createdTab.id, { command: "api_error", error: browser.i18n.getMessage('chatgpt_empty_apikey')}); + return; + } + if (prefs.chatgpt_model == '') { + browser.tabs.sendMessage(createdTab.id, { command: "api_error", error: browser.i18n.getMessage('chatgpt_empty_model')}); + return; + } + //console.log(">>>>>>>>>> sender: " + JSON.stringify(sender)); + browser.tabs.sendMessage(createdTab.id, { command: "api_send", prompt: promptText, action: action, tabId: curr_tabId, mailMessageId: mailMessageId2, do_custom_text: do_custom_text, prompt_info: prompt_info}); + taLog.log('[OpenAI ChatGPT] Connection succeded!'); + browser.runtime.onMessage.removeListener(listener2); } - if (prefs.chatgpt_model == '') { - browser.tabs.sendMessage(createdTab.id, { command: "api_error", error: browser.i18n.getMessage('chatgpt_empty_model')}); - return; + + if (message.command === "openai_api_ready_"+rand_call_id2) { + return handleChatGptApi(sender.tab); } - //console.log(">>>>>>>>>> sender: " + JSON.stringify(sender)); - browser.tabs.sendMessage(createdTab.id, { command: "api_send", prompt: promptText, action: action, tabId: curr_tabId, mailMessageId: mailMessageId2, do_custom_text: do_custom_text, prompt_info: prompt_info}); - taLog.log('[OpenAI ChatGPT] Connection succeded!'); - browser.runtime.onMessage.removeListener(listener2); + return false; } - if (message.command === "openai_api_ready_"+rand_call_id2) { - return handleChatGptApi(sender.tab); + browser.runtime.onMessage.addListener(listener2); + + let win_options2 = { + url: browser.runtime.getURL('api_webchat/index.html?llm='+prefs.connection_type+'&call_id='+rand_call_id2+'&ph_def_val='+(prefs.placeholders_use_default_value?'1':'0')), + type: "popup", } - return false; + + taLog.log("[chatgpt_api] prefs.chatgpt_win_width: " + prefs.chatgpt_win_width + ", prefs.chatgpt_win_height: " + prefs.chatgpt_win_height); + + if((prefs.chatgpt_win_width != '') && (prefs.chatgpt_win_height != '') && (prefs.chatgpt_win_width != 0) && (prefs.chatgpt_win_height != 0)){ + win_options2.width = prefs.chatgpt_win_width, + win_options2.height = prefs.chatgpt_win_height + } + + await browser.windows.create(win_options2); } - - browser.runtime.onMessage.addListener(listener2); - - let win_options2 = { - url: browser.runtime.getURL('api_webchat/index.html?llm='+prefs.connection_type+'&call_id='+rand_call_id2+'&ph_def_val='+(prefs.placeholders_use_default_value?'1':'0')), - type: "popup", - } - - taLog.log("[chatgpt_api] prefs.chatgpt_win_width: " + prefs.chatgpt_win_width + ", prefs.chatgpt_win_height: " + prefs.chatgpt_win_height); - - if((prefs.chatgpt_win_width != '') && (prefs.chatgpt_win_height != '') && (prefs.chatgpt_win_width != 0) && (prefs.chatgpt_win_height != 0)){ - win_options2.width = prefs.chatgpt_win_width, - win_options2.height = prefs.chatgpt_win_height - } - - await browser.windows.create(win_options2); - break; // chatgpt_api - END case 'google_gemini_api': - // We are using the Google Gemini API + { + // We are using the Google Gemini API - let rand_call_id5 = '_google_gemini_' + generateCallID(); + let rand_call_id5 = '_google_gemini_' + generateCallID(); - const listener5 = (message, sender, sendResponse) => { + const listener5 = (message, sender, sendResponse) => { - function handleChatGptApi(createdTab) { - let mailMessageId5 = -1; - if(mailMessage) mailMessageId5 = mailMessage.id; + function handleChatGptApi(createdTab) { + let mailMessageId5 = -1; + if(mailMessage) mailMessageId5 = mailMessage.id; - // check if the config is present, or give a message error - if (prefs.google_gemini_api_key == '') { - browser.tabs.sendMessage(createdTab.id, { command: "api_error", error: browser.i18n.getMessage('google_gemini_empty_apikey')}); - return; + // check if the config is present, or give a message error + if (prefs.google_gemini_api_key == '') { + browser.tabs.sendMessage(createdTab.id, { command: "api_error", error: browser.i18n.getMessage('google_gemini_empty_apikey')}); + return; + } + if (prefs.google_gemini_model == '') { + browser.tabs.sendMessage(createdTab.id, { command: "api_error", error: browser.i18n.getMessage('google_gemini_empty_model')}); + return; + } + //console.log(">>>>>>>>>> sender: " + JSON.stringify(sender)); + browser.tabs.sendMessage(createdTab.id, { command: "api_send", prompt: promptText, action: action, tabId: curr_tabId, mailMessageId: mailMessageId5, do_custom_text: do_custom_text, prompt_info: prompt_info}); + taLog.log('[Google Gemini] Connection succeded!'); + browser.runtime.onMessage.removeListener(listener5); } - if (prefs.google_gemini_model == '') { - browser.tabs.sendMessage(createdTab.id, { command: "api_error", error: browser.i18n.getMessage('google_gemini_empty_model')}); - return; + + if (message.command === "google_gemini_api_ready_"+rand_call_id5) { + return handleChatGptApi(sender.tab); } - //console.log(">>>>>>>>>> sender: " + JSON.stringify(sender)); - browser.tabs.sendMessage(createdTab.id, { command: "api_send", prompt: promptText, action: action, tabId: curr_tabId, mailMessageId: mailMessageId5, do_custom_text: do_custom_text, prompt_info: prompt_info}); - taLog.log('[Google Gemini] Connection succeded!'); - browser.runtime.onMessage.removeListener(listener5); + return false; } - if (message.command === "google_gemini_api_ready_"+rand_call_id5) { - return handleChatGptApi(sender.tab); + browser.runtime.onMessage.addListener(listener5); + + let win_options5 = { + url: browser.runtime.getURL('api_webchat/index.html?llm='+prefs.connection_type+'&call_id='+rand_call_id5+'&ph_def_val='+(prefs.placeholders_use_default_value?'1':'0')), + type: "popup", } - return false; + + taLog.log("[google_gemini_api] prefs.chatgpt_win_width: " + prefs.chatgpt_win_width + ", prefs.chatgpt_win_height: " + prefs.chatgpt_win_height); + + if((prefs.chatgpt_win_width != '') && (prefs.chatgpt_win_height != '') && (prefs.chatgpt_win_width != 0) && (prefs.chatgpt_win_height != 0)){ + win_options5.width = prefs.chatgpt_win_width, + win_options5.height = prefs.chatgpt_win_height + } + + await browser.windows.create(win_options5); } - - browser.runtime.onMessage.addListener(listener5); - - let win_options5 = { - url: browser.runtime.getURL('api_webchat/index.html?llm='+prefs.connection_type+'&call_id='+rand_call_id5+'&ph_def_val='+(prefs.placeholders_use_default_value?'1':'0')), - type: "popup", - } - - taLog.log("[google_gemini_api] prefs.chatgpt_win_width: " + prefs.chatgpt_win_width + ", prefs.chatgpt_win_height: " + prefs.chatgpt_win_height); - - if((prefs.chatgpt_win_width != '') && (prefs.chatgpt_win_height != '') && (prefs.chatgpt_win_width != 0) && (prefs.chatgpt_win_height != 0)){ - win_options5.width = prefs.chatgpt_win_width, - win_options5.height = prefs.chatgpt_win_height - } - - await browser.windows.create(win_options5); - break; // google_gemini_api - END case 'ollama_api': + { // We are using the Ollama API taLog.log("Ollama API window opening..."); @@ -592,61 +597,119 @@ async function openChatGPT(promptText, action, curr_tabId, prompt_name = '', do_ await browser.windows.create(win_options3); - break; // ollama_api - END + } + break; // ollama_api - END - case 'openai_comp_api': - // We are using the OpenAI Comp API - - let rand_call_id4 = '_openai_comp_api_' + generateCallID(); + case 'openai_comp_api': + { + // We are using the OpenAI Comp API + + let rand_call_id4 = '_openai_comp_api_' + generateCallID(); - - const listener4 = (message, sender, sendResponse) => { + + const listener4 = (message, sender, sendResponse) => { - function handleOpenAICompApi(createdTab) { - let mailMessageId4 = -1; - if(mailMessage) mailMessageId4 = mailMessage.id; - - // check if the config is present, or give a message error - if (prefs.openai_comp_host == '') { - browser.tabs.sendMessage(createdTab.id, { command: "api_error", error: browser.i18n.getMessage('OpenAIComp_empty_host')}); - return; - } - if (prefs.openai_comp_model == '') { - browser.tabs.sendMessage(createdTab.id, { command: "api_error", error: browser.i18n.getMessage('OpenAIComp_empty_model')}); - return; - } - - browser.tabs.sendMessage(createdTab.id, { command: "api_send", prompt: promptText, action: action, tabId: curr_tabId, mailMessageId: mailMessageId4, do_custom_text: do_custom_text, prompt_info: prompt_info}); - taLog.log('[OpenAI Comp API] Connection succeded!'); - browser.runtime.onMessage.removeListener(listener4); + function handleOpenAICompApi(createdTab) { + let mailMessageId4 = -1; + if(mailMessage) mailMessageId4 = mailMessage.id; + + // check if the config is present, or give a message error + if (prefs.openai_comp_host == '') { + browser.tabs.sendMessage(createdTab.id, { command: "api_error", error: browser.i18n.getMessage('OpenAIComp_empty_host')}); + return; } - - if (message.command === "openai_comp_api_ready_"+rand_call_id4) { - return handleOpenAICompApi(sender.tab); + if (prefs.openai_comp_model == '') { + browser.tabs.sendMessage(createdTab.id, { command: "api_error", error: browser.i18n.getMessage('OpenAIComp_empty_model')}); + return; } - return false; - } - - browser.runtime.onMessage.addListener(listener4); - - let win_options4 = { - url: browser.runtime.getURL('api_webchat/index.html?llm='+prefs.connection_type+'&call_id='+rand_call_id4+'&ph_def_val='+(prefs.placeholders_use_default_value?'1':'0')), - type: "popup", + + browser.tabs.sendMessage(createdTab.id, { command: "api_send", prompt: promptText, action: action, tabId: curr_tabId, mailMessageId: mailMessageId4, do_custom_text: do_custom_text, prompt_info: prompt_info}); + taLog.log('[OpenAI Comp API] Connection succeded!'); + browser.runtime.onMessage.removeListener(listener4); } - taLog.log("[openai_comp_api] prefs.chatgpt_win_width: " + prefs.chatgpt_win_width + ", prefs.chatgpt_win_height: " + prefs.chatgpt_win_height); - - if((prefs.chatgpt_win_width != '') && (prefs.chatgpt_win_height != '') && (prefs.chatgpt_win_width != 0) && (prefs.chatgpt_win_height != 0)){ - win_options4.width = prefs.chatgpt_win_width, - win_options4.height = prefs.chatgpt_win_height + if (message.command === "openai_comp_api_ready_"+rand_call_id4) { + return handleOpenAICompApi(sender.tab); } - - await browser.windows.create(win_options4); - - break; // openai_comp_api - END - default: - taLog.error("Unknown API connection type: " + prefs.connection_type); - break; + return false; + } + + browser.runtime.onMessage.addListener(listener4); + + let win_options4 = { + url: browser.runtime.getURL('api_webchat/index.html?llm='+prefs.connection_type+'&call_id='+rand_call_id4+'&ph_def_val='+(prefs.placeholders_use_default_value?'1':'0')), + type: "popup", + } + + taLog.log("[openai_comp_api] prefs.chatgpt_win_width: " + prefs.chatgpt_win_width + ", prefs.chatgpt_win_height: " + prefs.chatgpt_win_height); + + if((prefs.chatgpt_win_width != '') && (prefs.chatgpt_win_height != '') && (prefs.chatgpt_win_width != 0) && (prefs.chatgpt_win_height != 0)){ + win_options4.width = prefs.chatgpt_win_width, + win_options4.height = prefs.chatgpt_win_height + } + + await browser.windows.create(win_options4); + } + break; // openai_comp_api - END + + case 'anthropic_api': + { + // We are using the Anthropic API + + let rand_call_id5 = '_anthropic_' + generateCallID(); + + const listener5 = (message, sender, sendResponse) => { + + function handleAnthropicApi(createdTab) { + let mailMessageId5 = -1; + if(mailMessage) mailMessageId5 = mailMessage.id; + + // check if the config is present, or give a message error + if (prefs.anthropic_api_key == '') { + browser.tabs.sendMessage(createdTab.id, { command: "api_error", error: browser.i18n.getMessage('anthropic_empty_apikey')}); + return; + } + if (prefs.anthropic_model == '') { + browser.tabs.sendMessage(createdTab.id, { command: "api_error", error: browser.i18n.getMessage('anthropic_empty_model')}); + return; + } + if (prefs.anthropic_version == '') { + browser.tabs.sendMessage(createdTab.id, { command: "api_error", error: browser.i18n.getMessage('anthropic_empty_version')}); + return; + } + //console.log(">>>>>>>>>> sender: " + JSON.stringify(sender)); + browser.tabs.sendMessage(createdTab.id, { command: "api_send", prompt: promptText, action: action, tabId: curr_tabId, mailMessageId: mailMessageId5, do_custom_text: do_custom_text, prompt_info: prompt_info}); + taLog.log('[OpenAI ChatGPT] Connection succeded!'); + browser.runtime.onMessage.removeListener(listener5); + } + + if (message.command === "anthropic_api_ready_"+rand_call_id5) { + return handleAnthropicApi(sender.tab); + } + return false; + } + + browser.runtime.onMessage.addListener(listener5); + + let win_options5 = { + url: browser.runtime.getURL('api_webchat/index.html?llm='+prefs.connection_type+'&call_id='+rand_call_id5+'&ph_def_val='+(prefs.placeholders_use_default_value?'1':'0')), + type: "popup", + } + + taLog.log("[chatgpt_api] prefs.chatgpt_win_width: " + prefs.chatgpt_win_width + ", prefs.chatgpt_win_height: " + prefs.chatgpt_win_height); + + if((prefs.chatgpt_win_width != '') && (prefs.chatgpt_win_height != '') && (prefs.chatgpt_win_width != 0) && (prefs.chatgpt_win_height != 0)){ + win_options5.width = prefs.chatgpt_win_width, + win_options5.height = prefs.chatgpt_win_height + } + + await browser.windows.create(win_options5); + } + break; // anthropic_api - END + + default: + taLog.error("Unknown API connection type: " + prefs.connection_type); + break; } } @@ -863,7 +926,9 @@ const newEmailListener = (folder, messagesList) => { taSpamReport.logger = taLog; - await processEmails(messages, prefs_init.add_tags_auto, prefs_init.spamfilter); + let add_tags_auto_enabled = prefs_init.add_tags && prefs_init.add_tags_auto; + + await processEmails(messages, add_tags_auto_enabled, prefs_init.spamfilter); if(prefs_init.spamfilter){ taSpamReport.truncReportData(); @@ -900,6 +965,7 @@ async function processEmails(messages, addTagsAuto, spamFilter) { let specialFullPrompt_add_tags = ''; let curr_prompt_add_tags = menus.allPrompts.find(p => p.id === 'prompt_add_tags'); let tags_full_list = await getTagsList(); + // console.log(">>>>>>>>>>>>> curr_prompt_add_tags: " + curr_prompt_add_tags); let chatgpt_lang = await taPromptUtils.getDefaultLang(curr_prompt_add_tags); specialFullPrompt_add_tags = await taPromptUtils.preparePrompt(curr_prompt_add_tags, message, chatgpt_lang, '', body_text, curr_fullMessage.headers.subject, msg_text, '', tags_full_list); specialFullPrompt_add_tags = taPromptUtils.finalizePrompt_add_tags(specialFullPrompt_add_tags, prefs_aats.add_tags_maxnum, prefs_aats.add_tags_force_lang, prefs_aats.default_chatgpt_lang); @@ -926,6 +992,7 @@ async function processEmails(messages, addTagsAuto, spamFilter) { } } let curr_prompt_spamfilter = await getSpamFilterPrompt(); + // console.log(">>>>>>>>>>>>> curr_prompt_spamfilter: " + curr_prompt_spamfilter); let chatgpt_lang = await taPromptUtils.getDefaultLang(curr_prompt_spamfilter); let specialFullPrompt_spamfilter = await taPromptUtils.preparePrompt(curr_prompt_spamfilter, message, chatgpt_lang, '', body_text, curr_fullMessage.headers.subject, msg_text, '', ''); taLog.log("Special prompt: " + specialFullPrompt_spamfilter); diff --git a/options/mzta-options-default.js b/options/mzta-options-default.js index a6d1ca66..12ca7726 100644 --- a/options/mzta-options-default.js +++ b/options/mzta-options-default.js @@ -42,6 +42,10 @@ export const prefs_default = { google_gemini_api_key: '', google_gemini_model: '', google_gemini_system_instruction: '', + anthropic_api_key: '', + anthropic_model: '', + anthropic_version: '2023-06-01', + anthropic_max_tokens: 4096, dynamic_menu_force_enter: false, dynamic_menu_order_alphabet: true, placeholders_use_default_value: false, diff --git a/options/mzta-options.css b/options/mzta-options.css index 47554130..0029bc67 100644 --- a/options/mzta-options.css +++ b/options/mzta-options.css @@ -120,6 +120,10 @@ tr.conntype_google_gemini_api, tr.conntype_google_gemini_api2{ background-color: rgb(233, 238, 169); } +tr.conntype_anthropic_api, tr.conntype_anthropic_api2{ + background-color: rgb(255, 168, 204); +} + #chatgpt_model_fetch_loading{ display: none; font-style: italic; @@ -178,6 +182,11 @@ input.option-input[type="text"]{ font-style: italic; } +#anthropic_model_fetch_loading{ + display: none; + font-style: italic; +} + #owl_warning{ display: none; } @@ -265,6 +274,10 @@ input.option-input[type="text"]{ background-color: rgb(72, 77, 3); } + tr.conntype_anthropic_api, tr.conntype_anthropic_api2{ + background-color: rgb(83, 0, 35); + } + .api_key-container .toggle-icon img { filter: invert(1); } diff --git a/options/mzta-options.html b/options/mzta-options.html index a3dffc18..7c3e85f9 100644 --- a/options/mzta-options.html +++ b/options/mzta-options.html @@ -124,6 +124,7 @@ + @@ -384,6 +385,55 @@ + + + +
+ + +
+ + + + + + + + __MSG_Loading__
+ + + + + + + + + + + + + __MSG_prefs_OptionText_anthropic_max_tokens__ + + + + __MSG_prefs_OptionText_max_prompt_length__ diff --git a/options/mzta-options.js b/options/mzta-options.js index 67d73003..871f08cf 100644 --- a/options/mzta-options.js +++ b/options/mzta-options.js @@ -22,6 +22,7 @@ import { OpenAI } from '../js/api/openai.js'; import { Ollama } from '../js/api/ollama.js'; import { OpenAIComp } from '../js/api/openai_comp.js' import { GoogleGemini } from '../js/api/google_gemini.js'; +import { Anthropic } from '../js/api/anthropic.js'; import { ChatGPTWeb_models, checkSparksPresence, isThunderbird128OrGreater, openTab, sanitizeChatGPTModelData, sanitizeChatGPTWebCustomData, validateCustomData_ChatGPTWeb, getChatGPTWebModelsList_HTML } from '../js/mzta-utils.js'; let taLog = new taLogger("mzta-options",true); @@ -60,7 +61,7 @@ function saveOptions(e) { async function restoreOptions() { function setCurrentChoice(result) { document.querySelectorAll(".option-input").forEach(element => { - taLog.log("Options restoring " + element.id + " = " + (element.id=="chatgpt_api_key" || element.id=="openai_comp_api_key" || element.id=="google_gemini_api_key" ? "****************" : result[element.id])); + taLog.log("Options restoring " + element.id + " = " + (element.id=="chatgpt_api_key" || element.id=="openai_comp_api_key" || element.id=="google_gemini_api_key" || element.id=="anthropic_api_key" ? "****************" : result[element.id])); switch (element.type) { case 'checkbox': element.checked = result[element.id] || false; @@ -109,6 +110,7 @@ function showConnectionOptions() { let ollama_api_display = 'none'; let openai_comp_api_display = 'none'; let google_gemini_api_display = 'none'; + let anthropic_api_display = 'none'; let conntype_select = document.getElementById("connection_type"); let parent = conntype_select.parentElement.parentElement.parentElement; parent.classList.toggle("conntype_chatgpt_web", (conntype_select.value === "chatgpt_web")); @@ -116,6 +118,7 @@ function showConnectionOptions() { parent.classList.toggle("conntype_ollama_api", (conntype_select.value === "ollama_api")); parent.classList.toggle("conntype_openai_comp_api", (conntype_select.value === "openai_comp_api")); parent.classList.toggle("conntype_google_gemini_api", (conntype_select.value === "google_gemini_api")); + parent.classList.toggle("conntype_anthropic_api", (conntype_select.value === "anthropic_api")); if (conntype_select.value === "chatgpt_web") { chatgpt_web_display = 'table-row'; }else{ @@ -141,6 +144,11 @@ function showConnectionOptions() { }else{ google_gemini_api_display = 'none'; } + if (conntype_select.value === "anthropic_api") { + anthropic_api_display = 'table-row'; + }else{ + anthropic_api_display = 'none'; + } document.querySelectorAll(".conntype_chatgpt_web").forEach(element => { element.style.display = chatgpt_web_display; }); @@ -156,6 +164,9 @@ function showConnectionOptions() { document.querySelectorAll(".conntype_google_gemini_api").forEach(element => { element.style.display = google_gemini_api_display; }); + document.querySelectorAll(".conntype_anthropic_api").forEach(element => { + element.style.display = anthropic_api_display; + }); if (permission_all_urls) { document.getElementById('openai_comp_api_cors_warning').style.display = 'none'; document.getElementById('ollama_api_cors_warning').style.display = 'none'; @@ -250,6 +261,50 @@ function warn_OpenAIComp_HostEmpty() { } } +function warn_Anthropic_APIKeyEmpty() { + let apiKeyInput = document.getElementById('anthropic_api_key'); + let btnFetchAnthropicModels = document.getElementById('btnUpdateAnthropicModels'); + let modelAnthropic = document.getElementById('anthropic_model'); + if(apiKeyInput.value === ''){ + apiKeyInput.style.border = '2px solid red'; + btnFetchAnthropicModels.disabled = true; + modelAnthropic.disabled = true; + modelAnthropic.selectedIndex = -1; + modelAnthropic.style.border = ''; + }else{ + apiKeyInput.style.border = ''; + btnFetchAnthropicModels.disabled = false; + modelAnthropic.disabled = false; + if((modelAnthropic.selectedIndex === -1)||(modelAnthropic.value === '')){ + modelAnthropic.style.border = '2px solid red'; + }else{ + modelAnthropic.style.border = ''; + } + } +} + +function warn_Anthropic_VersionEmpty() { + let versionInput = document.getElementById('anthropic_version'); + let btnFetchAnthropicModels = document.getElementById('btnUpdateAnthropicModels'); + let modelAnthropic = document.getElementById('anthropic_model'); + if(versionInput.value === ''){ + versionInput.style.border = '2px solid red'; + btnFetchAnthropicModels.disabled = true; + modelAnthropic.disabled = true; + modelAnthropic.selectedIndex = -1; + modelAnthropic.style.border = ''; + }else{ + versionInput.style.border = ''; + btnFetchAnthropicModels.disabled = false; + modelAnthropic.disabled = false; + if((modelAnthropic.selectedIndex === -1)||(modelAnthropic.value === '')){ + modelAnthropic.style.border = '2px solid red'; + }else{ + modelAnthropic.style.border = ''; + } + } +} + function disable_MaxPromptLength(){ let maxPromptLength = document.getElementById('max_prompt_length'); let conntype_select = document.getElementById("connection_type"); @@ -450,6 +505,8 @@ document.addEventListener('DOMContentLoaded', async () => { conntype_select.addEventListener("change", warn_Ollama_HostEmpty); conntype_select.addEventListener("change", warn_OpenAIComp_HostEmpty); conntype_select.addEventListener("change", warn_GoogleGemini_APIKeyEmpty); + conntype_select.addEventListener("change", warn_Anthropic_APIKeyEmpty); + conntype_select.addEventListener("change", warn_Anthropic_VersionEmpty); conntype_select.addEventListener("change", disable_AddTags); conntype_select.addEventListener("change", disable_SpamFilter); conntype_select.addEventListener("change", disable_GetCalendarEvent); @@ -459,8 +516,10 @@ document.addEventListener('DOMContentLoaded', async () => { document.getElementById("google_gemini_api_key").addEventListener("change", warn_GoogleGemini_APIKeyEmpty); document.getElementById("chatgpt_web_project").addEventListener("input", validateCustomData_ChatGPTWeb); document.getElementById("chatgpt_web_custom_gpt").addEventListener("input", validateCustomData_ChatGPTWeb); + document.getElementById("anthropic_api_key").addEventListener("change", warn_Anthropic_APIKeyEmpty); + document.getElementById("anthropic_version").addEventListener("change", warn_Anthropic_VersionEmpty); - let prefs = await browser.storage.sync.get({chatgpt_web_model: '', chatgpt_model: '', ollama_model: '', openai_comp_model: '', google_gemini_model: '', chatgpt_win_height: 0, chatgpt_win_width: 0 }); + let prefs = await browser.storage.sync.get({chatgpt_web_model: '', chatgpt_model: '', ollama_model: '', openai_comp_model: '', google_gemini_model: '', anthropic_model: '', anthropic_version: '', chatgpt_win_height: 0, chatgpt_win_width: 0 }); // OpenAI API ChatGPT model fetching let select_chatgpt_model = document.getElementById('chatgpt_model'); @@ -502,7 +561,7 @@ document.addEventListener('DOMContentLoaded', async () => { warn_ChatGPT_APIKeyEmpty(); }); - // Google Gemini API ChatGPT model fetching + // Google Gemini API model fetching let select_google_gemini_model = document.getElementById('google_gemini_model'); const google_gemini_option = document.createElement('option'); google_gemini_option.value = prefs.google_gemini_model; @@ -640,11 +699,57 @@ document.addEventListener('DOMContentLoaded', async () => { warn_OpenAIComp_HostEmpty(); }); + // Anthropic API model fetching + let select_anthropic_model = document.getElementById('anthropic_model'); + const anthropic_option = document.createElement('option'); + anthropic_option.value = prefs.anthropic_model; + anthropic_option.text = prefs.anthropic_model; + select_anthropic_model.appendChild(anthropic_option); + select_anthropic_model.addEventListener("change", warn_Anthropic_APIKeyEmpty); + select_anthropic_model.addEventListener("change", warn_Anthropic_VersionEmpty); + + document.getElementById('btnUpdateAnthropicModels').addEventListener('click', async () => { + document.getElementById('anthropic_model_fetch_loading').style.display = 'inline'; + let anthropic = new Anthropic(document.getElementById("anthropic_api_key").value, document.getElementById("anthropic_version").value, ''); + anthropic.fetchModels().then((data) => { + if(!data.ok){ + let errorDetail; + try { + errorDetail = JSON.parse(data.error); + errorDetail = errorDetail.error.message; + } catch (e) { + errorDetail = data.error; + } + document.getElementById('anthropic_model_fetch_loading').style.display = 'none'; + console.error("[ThunderAI] " + browser.i18n.getMessage("Anthropic_Models_Error_fetching")); + alert(browser.i18n.getMessage("Anthropic_Models_Error_fetching")+": " + errorDetail); + return; + } + taLog.log("Anthropic models: " + JSON.stringify(data)); + data.response.forEach(model => { + const existingOption = Array.from(select_anthropic_model.options).find(option => option.value === model.id); + if (existingOption) { + existingOption.text = model.display_name + " (" + model.id + ")"; + } else { + const option = document.createElement('option'); + option.value = model.id; + option.text = model.display_name + " (" + model.id + ")"; + select_anthropic_model.appendChild(option); + } + }); + document.getElementById('anthropic_model_fetch_loading').style.display = 'none'; + }); + + warn_Anthropic_APIKeyEmpty(); + }); + showConnectionOptions(); warn_ChatGPT_APIKeyEmpty(); warn_Ollama_HostEmpty(); warn_OpenAIComp_HostEmpty(); warn_GoogleGemini_APIKeyEmpty(); + warn_Anthropic_APIKeyEmpty(); + warn_Anthropic_VersionEmpty(); disable_MaxPromptLength(); disable_AddTags(); disable_SpamFilter(); @@ -683,6 +788,17 @@ document.addEventListener('DOMContentLoaded', async () => { icon_img_openai_comp_api_key.src = type === 'password' ? "../images/pwd-show.png" : "../images/pwd-hide.png"; }); + const passwordField_anthropic_api_key = document.getElementById('anthropic_api_key'); + const toggleIcon_anthropic_api_key = document.getElementById('toggle_anthropic_api_key'); + const icon_img_anthropic_api_key = document.getElementById('pwd-icon_anthropic_api_key'); + + toggleIcon_anthropic_api_key.addEventListener('click', () => { + const type = passwordField_anthropic_api_key.getAttribute('type') === 'password' ? 'text' : 'password'; + passwordField_anthropic_api_key.setAttribute('type', type); + + icon_img_anthropic_api_key.src = type === 'password' ? "../images/pwd-show.png" : "../images/pwd-hide.png"; + }); + const btnChatGPTWeb_Tab = document.getElementById('btnChatGPTWeb_Tab'); btnChatGPTWeb_Tab.addEventListener('click', async () => { let prefs_mod = await browser.storage.sync.get({chatgpt_web_model: prefs_default.chatgpt_web_model, chatgpt_web_project: prefs_default.chatgpt_web_project, chatgpt_web_custom_gpt: prefs_default.chatgpt_web_custom_gpt}); diff --git a/options/mzta-release-notes.html b/options/mzta-release-notes.html index 6ae39eb0..e79f7678 100644 --- a/options/mzta-release-notes.html +++ b/options/mzta-release-notes.html @@ -9,6 +9,9 @@

ThunderAI Release Notes

Version 3.5.0 - ??/??/2025

    +
  • Added Anthropic API support [#349].
  • +
  • Fix: in the Spamfilter page the unsaved changes warning is now correctly shown.
  • +
  • Fixed incoming emails elaboration that was failing when the auto tagging was enabled and then the complete tagging feature was disabled.
  • ...

Version 3.4.1 - 12/05/2025

diff --git a/pages/spamfilter/mzta-spamfilter.html b/pages/spamfilter/mzta-spamfilter.html index 2496b976..cffc7831 100644 --- a/pages/spamfilter/mzta-spamfilter.html +++ b/pages/spamfilter/mzta-spamfilter.html @@ -33,7 +33,7 @@
- __MSG_GetCalendarEvent_prompt_text_title__ + __MSG_GetCalendarEvent_prompt_text_title__
__MSG_prefs_OptionText_btnManagePrompts_infoline__ __MSG_customPrompts_managePrompts_help__
__MSG_prefs_OptionText_GetCalendarEvent_infoline2__