From 2740fe3ee9a961e29727c71dfc68fe7b6037ad67 Mon Sep 17 00:00:00 2001 From: mic Date: Tue, 10 Feb 2026 21:36:59 +0100 Subject: [PATCH 01/34] additional_text is now dynamic. see #525 #554 --- js/mzta-placeholders.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/mzta-placeholders.js b/js/mzta-placeholders.js index d8b63008..171267fb 100644 --- a/js/mzta-placeholders.js +++ b/js/mzta-placeholders.js @@ -148,7 +148,7 @@ const defaultPlaceholders = [ default_value: "", type: 0, is_default: "1", - is_dynamic: "0", + is_dynamic: "1", enabled: 1, }, { @@ -427,7 +427,7 @@ export const placeholdersUtils = { // console.log(">>>>>>>>>> replacePlaceholders match: " + JSON.stringify(match)); // console.log(">>>>>>>>>> replacePlaceholders p1: " + JSON.stringify(p1)); // p1 contains the key inside {% %} - if (skip_additional_text && (p1 === 'additional_text')) { + if (skip_additional_text && ((p1 === 'additional_text') || (p1.startsWith('additional_text:')))) { return match; } const currPlaceholder = defaultPlaceholders.find(ph => (ph.id === p1) || (ph.is_dynamic == 1 && p1.startsWith(ph.id + ':'))); From 34e8e242f6f066f813493a2f6765909d41eab5e2 Mon Sep 17 00:00:00 2001 From: mic Date: Tue, 10 Feb 2026 21:42:26 +0100 Subject: [PATCH 02/34] hasPlaceholder and replacePlaceholder can now work with dynamic placeholders. see #525 #554 --- js/mzta-placeholders.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/mzta-placeholders.js b/js/mzta-placeholders.js index 171267fb..2b05e7d1 100644 --- a/js/mzta-placeholders.js +++ b/js/mzta-placeholders.js @@ -436,7 +436,7 @@ export const placeholdersUtils = { return match; } // Replace if found, otherwise keep the original or substitute with default value - return replacements[p1] || (use_default_value ? currPlaceholder.default_value : match); + return replacements[p1] || replacements[currPlaceholder.id] || (use_default_value ? currPlaceholder.default_value : match); }); }, @@ -459,7 +459,7 @@ export const placeholdersUtils = { // If a specific placeholder is provided, we search for it if (placeholder !== "") { // Dynamically build the regex for the specific placeholder - regex = new RegExp(`{%\s*${placeholder}\s*%}`); + regex = new RegExp(`{%\s*${placeholder}(:.*?)?\s*%}`); } else { // Otherwise, we search for any placeholder in the format {% ... %} regex = /{%\s*(.*?)\s*%}/; From b1282c956216715d182addd6b146072a9c1e30e8 Mon Sep 17 00:00:00 2001 From: mic Date: Wed, 11 Feb 2026 21:30:56 +0100 Subject: [PATCH 03/34] getPlaceholdersAdditionalTextArray method added. see #525 #554 --- js/mzta-placeholders.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/js/mzta-placeholders.js b/js/mzta-placeholders.js index 2b05e7d1..b06f82d3 100644 --- a/js/mzta-placeholders.js +++ b/js/mzta-placeholders.js @@ -485,6 +485,19 @@ export const placeholdersUtils = { return regex.test(text); }, + getPlaceholdersAdditionalTextArray(prompt_text){ + const regex = /{%\s*additional_text(?::(.*?))?\s*%}/g; + let matches = []; + let match; + while ((match = regex.exec(prompt_text)) !== null) { + matches.push({ + placeholder: match[0], + info: match[1] ? match[1].trim() : "" + }); + } + return matches; + }, + async getPlaceholdersValues(args) { const { prompt_text = "", From c29b4b335295da9929b88a835a43534fa074bb77 Mon Sep 17 00:00:00 2001 From: mic Date: Wed, 11 Feb 2026 21:45:48 +0100 Subject: [PATCH 04/34] userInput is now an array. see #525 #554 --- api_webchat/controller.js | 11 +++++++++-- js/mzta-menus.js | 7 +++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/api_webchat/controller.js b/api_webchat/controller.js index 8b722a25..2525069e 100644 --- a/api_webchat/controller.js +++ b/api_webchat/controller.js @@ -262,7 +262,7 @@ browser.runtime.onMessage.addListener((message, sender, sendResponse) => { } break; case 'api_send_custom_text': - let userInput = message.custom_text; + let userInput = message.custom_text; // From version 4.0.0 this is an array if(userInput !== null) { if(!placeholdersUtils.hasPlaceholder(promptData.prompt, 'additional_text')){ // no additional_text placeholder, do as usual @@ -270,7 +270,14 @@ browser.runtime.onMessage.addListener((message, sender, sendResponse) => { }else{ // we have the additional_text placeholder, do the magic! let finalSubs = {}; - finalSubs["additional_text"] = userInput; + + if (Array.isArray(userInput)) { + userInput.forEach(obj => { + finalSubs[obj.placeholder.replace(/^{%|%}$/g, '').trim()] = obj.custom_text; + }); + } else { + finalSubs["additional_text"] = userInput; + } promptData.prompt = placeholdersUtils.replacePlaceholders({ text: promptData.prompt, replacements: finalSubs, diff --git a/js/mzta-menus.js b/js/mzta-menus.js index 4d04dcaf..b29ad6cc 100644 --- a/js/mzta-menus.js +++ b/js/mzta-menus.js @@ -192,6 +192,13 @@ export class mzta_Menus { only_quoted_text: only_quoted_text, tags_full_list: tags_full_list }); + + curr_prompt.custom_text_array = {}; + + if(placeholdersUtils.hasPlaceholder(curr_prompt.text, 'additional_text')){ + curr_prompt.custom_text_array = placeholdersUtils.getPlaceholdersAdditionalTextArray(curr_prompt.text); + } + console.log(">>>>>>>>>>>>>>>>>>> curr_prompt.custom_text_array: " + JSON.stringify(curr_prompt.custom_text_array)); switch(curr_prompt.id){ case 'prompt_translate_this': From 3213a71eba24b13e3335936136d51a567bea022c Mon Sep 17 00:00:00 2001 From: mic Date: Wed, 11 Feb 2026 21:55:04 +0100 Subject: [PATCH 05/34] the additional text is now an array. see #525 #554 --- api_webchat/controller.js | 2 +- api_webchat/messageInput.js | 54 ++++++++++++++++++++++++++++++------- 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/api_webchat/controller.js b/api_webchat/controller.js index 2525069e..c6f349c9 100644 --- a/api_webchat/controller.js +++ b/api_webchat/controller.js @@ -256,7 +256,7 @@ browser.runtime.onMessage.addListener((message, sender, sendResponse) => { promptData = message; //send the received prompt to the llm api if(message.do_custom_text=="1") { - messageInput._showCustomTextField(); + messageInput._showCustomTextField(message.prompt_info?.custom_text_array); }else{ sendPrompt(message); } diff --git a/api_webchat/messageInput.js b/api_webchat/messageInput.js index b5b2a6b0..9007691d 100644 --- a/api_webchat/messageInput.js +++ b/api_webchat/messageInput.js @@ -193,6 +193,8 @@ messageInputTemplate.content.appendChild(customDiv); class MessageInput extends HTMLElement { model = ''; + _customTextArray = []; + _currentCustomTextIndex = 0; constructor() { super(); @@ -306,21 +308,55 @@ class MessageInput extends HTMLElement { this._messageInputField.value = msg; } - _showCustomTextField(){ + _showCustomTextField(custom_text_array){ + this._customTextArray = custom_text_array || []; + if (this._customTextArray.length === 0) { + this._customTextArray.push({ placeholder: "{%additional_text%}", info: "" }); + } + this._currentCustomTextIndex = 0; + this._renderCustomTextStep(); this._customText.style.display = 'block'; + } + + _renderCustomTextStep() { + const currentItem = this._customTextArray[this._currentCustomTextIndex]; + const infoDiv = this.shadowRoot.querySelector('#mzta-custom_info'); + + this._customTextArea.value = ""; + + if (currentItem.info && currentItem.info.trim() !== "") { + infoDiv.textContent = currentItem.info; + } else { + infoDiv.textContent = browser.i18n.getMessage("chatgpt_win_custom_text"); + } + this._customTextArea.focus(); } async _customTextBtnClick(args) { const customText = this._customTextArea.value; - // console.log(">>>>>>>>>>>>>>>> customText: " + customText); - args.customBtn.disabled = true; - args.customBtn.classList.add('disabled'); - args.customLoading.style.display = 'inline-block'; - args.customLoading.style.display = 'none'; - let tab = await browser.tabs.query({ active: true, currentWindow: true }); - browser.runtime.sendMessage({ command: "api_send_custom_text", custom_text: customText, tabId: tab[0].id }); - args.customDiv.style.display = 'none'; + + if (this._customTextArray[this._currentCustomTextIndex]) { + this._customTextArray[this._currentCustomTextIndex].custom_text = customText; + } + + this._currentCustomTextIndex++; + + if (this._currentCustomTextIndex < this._customTextArray.length) { + this._renderCustomTextStep(); + } else { + args.customBtn.disabled = true; + args.customBtn.classList.add('disabled'); + args.customLoading.style.display = 'inline-block'; + + let tab = await browser.tabs.query({ active: true, currentWindow: true }); + browser.runtime.sendMessage({ command: "api_send_custom_text", custom_text: this._customTextArray, tabId: tab[0].id }); + args.customDiv.style.display = 'none'; + + args.customBtn.disabled = false; + args.customBtn.classList.remove('disabled'); + args.customLoading.style.display = 'none'; + } } } From ded59f14f757ff8c8817a61d9297d80cc9d99d18 Mon Sep 17 00:00:00 2001 From: mic Date: Wed, 11 Feb 2026 22:47:41 +0100 Subject: [PATCH 06/34] checkPromptsConfigForPlaceholders updated. see #525 #554 --- pages/customprompts/mzta-custom-prompts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/customprompts/mzta-custom-prompts.js b/pages/customprompts/mzta-custom-prompts.js index 55824f0d..8e45ac8c 100644 --- a/pages/customprompts/mzta-custom-prompts.js +++ b/pages/customprompts/mzta-custom-prompts.js @@ -1417,7 +1417,7 @@ async function checkPromptsConfigForPlaceholders(textarea){ // check additional_text and selected_text placeholders presence and the corrispondent checkboxes let tr_ancestor = textarea.closest('tr'); let need_custom_text_element = tr_ancestor.querySelector('.need_custom_text') || tr_ancestor.querySelector('.need_custom_text_new'); - if(String(curr_text).indexOf('{%additional_text%}') != -1){ + if(/{%\s*additional_text(?::.*?)?\s*%}/.test(String(curr_text))){ if(!need_custom_text_element.checked){ need_custom_text_element.closest('.need_custom_text_span').style.border = '2px solid red'; }else{ From e30d073bca8b1f012c53d103e6de06bac2c8c580 Mon Sep 17 00:00:00 2001 From: mic Date: Wed, 11 Feb 2026 23:10:33 +0100 Subject: [PATCH 07/34] handling multiple old {%additional_text%} placeholders. see #525 #554 #652 --- js/mzta-utils-prompt.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/js/mzta-utils-prompt.js b/js/mzta-utils-prompt.js index c19499f8..b8a58ac8 100644 --- a/js/mzta-utils-prompt.js +++ b/js/mzta-utils-prompt.js @@ -59,6 +59,13 @@ export const taPromptUtils = { if(placeholdersUtils.hasCustomPlaceholder(curr_prompt.text)){ curr_prompt.text = await placeholdersUtils.replaceCustomPlaceholders(curr_prompt.text); } + + // Replace all {%additional_text%} with {%additional_text:N%} + let additionalTextCounter = 1; + curr_prompt.text = curr_prompt.text.replace(/{%\s*additional_text\s*%}/g, () => { + return `{%additional_text:${additionalTextCounter++}%}`; + }); + let finalSubs = await placeholdersUtils.getPlaceholdersValues({ prompt_text: curr_prompt.text, curr_message: curr_message, From 41dbb5d009ae2b927107379ca0208c2ea8fb46ec Mon Sep 17 00:00:00 2001 From: mic Date: Thu, 12 Feb 2026 21:57:52 +0100 Subject: [PATCH 08/34] # added to additional_text without id. see #525 #554 --- js/mzta-utils-prompt.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/mzta-utils-prompt.js b/js/mzta-utils-prompt.js index b8a58ac8..28978662 100644 --- a/js/mzta-utils-prompt.js +++ b/js/mzta-utils-prompt.js @@ -63,7 +63,7 @@ export const taPromptUtils = { // Replace all {%additional_text%} with {%additional_text:N%} let additionalTextCounter = 1; curr_prompt.text = curr_prompt.text.replace(/{%\s*additional_text\s*%}/g, () => { - return `{%additional_text:${additionalTextCounter++}%}`; + return `{%additional_text:#${additionalTextCounter++}%}`; }); let finalSubs = await placeholdersUtils.getPlaceholdersValues({ From 774ffc2287f9e84243f9970803788f712547caa3 Mon Sep 17 00:00:00 2001 From: mic Date: Thu, 12 Feb 2026 22:10:12 +0100 Subject: [PATCH 09/34] additional_text now printed on the form. see #525 #554 --- api_webchat/messageInput.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/api_webchat/messageInput.js b/api_webchat/messageInput.js index 9007691d..bb0bbdf3 100644 --- a/api_webchat/messageInput.js +++ b/api_webchat/messageInput.js @@ -100,6 +100,9 @@ messagesInputStyle.textContent = ` padding-bottom:10px; font-size:15px; } + #mzta-custom_info span{ + font-size:0.8em; + } @media (prefers-color-scheme: dark) { #messageInputField { background-color: #303030; @@ -323,11 +326,13 @@ class MessageInput extends HTMLElement { const infoDiv = this.shadowRoot.querySelector('#mzta-custom_info'); this._customTextArea.value = ""; + infoDiv.textContent = browser.i18n.getMessage("chatgpt_win_custom_text"); if (currentItem.info && currentItem.info.trim() !== "") { - infoDiv.textContent = currentItem.info; - } else { - infoDiv.textContent = browser.i18n.getMessage("chatgpt_win_custom_text"); + infoDiv.appendChild(document.createElement("br")); + const infoSpan = document.createElement("span"); + infoSpan.textContent = "[" + browser.i18n.getMessage("customPrompts_form_label_ID") + ": " + currentItem.info + "]"; + infoDiv.appendChild(infoSpan); } this._customTextArea.focus(); From 28afa0fc456cba137f8de0be9486c8c5d43d9b5c Mon Sep 17 00:00:00 2001 From: mic Date: Thu, 12 Feb 2026 22:29:24 +0100 Subject: [PATCH 10/34] bigger form. see #525 #554 --- api_webchat/messageInput.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/api_webchat/messageInput.js b/api_webchat/messageInput.js index bb0bbdf3..c7e704d8 100644 --- a/api_webchat/messageInput.js +++ b/api_webchat/messageInput.js @@ -69,12 +69,14 @@ messagesInputStyle.textContent = ` } #mzta-custom_text{ padding:10px; - width:auto; + width:50%; + min-width:300px; max-width:80%; height:auto; max-height:80%; border-radius:5px; - overflow:auto; + overflow-y:auto; + overflow-x:hidden; position:fixed; top:50%; left:50%; @@ -84,15 +86,18 @@ messagesInputStyle.textContent = ` background:#333; color:white; border:3px solid white; + box-sizing: border-box; } #mzta-custom_loading{ height:50px;display:none; } #mzta-custom_textarea{ color:black; - padding:1px; + padding:5px; font-size:15px; width:100%; + box-sizing: border-box; + resize: vertical; } #mzta-custom_info{ text-align:center; @@ -181,6 +186,7 @@ customInfo.textContent = browser.i18n.getMessage("chatgpt_win_custom_text"); customDiv.appendChild(customInfo); const customTextArea = document.createElement('textarea'); customTextArea.id = 'mzta-custom_textarea'; +customTextArea.rows = 5; customDiv.appendChild(customTextArea); const customLoading = document.createElement('img'); customLoading.src = browser.runtime.getURL("/images/loading.gif"); From 28e3b6502851bcbd7fdfd6f7456824f897354f8d Mon Sep 17 00:00:00 2001 From: mic Date: Thu, 12 Feb 2026 22:38:02 +0100 Subject: [PATCH 11/34] progressive numbers added. see #525 #554 --- api_webchat/messageInput.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/api_webchat/messageInput.js b/api_webchat/messageInput.js index c7e704d8..1e226c15 100644 --- a/api_webchat/messageInput.js +++ b/api_webchat/messageInput.js @@ -108,6 +108,13 @@ messagesInputStyle.textContent = ` #mzta-custom_info span{ font-size:0.8em; } + #mzta-custom_step{ + position: absolute; + bottom: 5px; + right: 10px; + font-size: 12px; + color: #ccc; + } @media (prefers-color-scheme: dark) { #messageInputField { background-color: #303030; @@ -197,6 +204,9 @@ customBtn.id = 'mzta-custom_btn'; customBtn.textContent = browser.i18n.getMessage("chatgpt_win_send"); customBtn.classList.add('mzta-btn'); customDiv.appendChild(customBtn); +const customStep = document.createElement('div'); +customStep.id = 'mzta-custom_step'; +customDiv.appendChild(customStep); messageInputTemplate.content.appendChild(customDiv); class MessageInput extends HTMLElement { @@ -223,6 +233,7 @@ class MessageInput extends HTMLElement { this._customTextArea = shadowRoot.querySelector('#mzta-custom_textarea'); this._customLoading = shadowRoot.querySelector('#mzta-custom_loading'); this._customBtn = shadowRoot.querySelector('#mzta-custom_btn'); + this._customStep = shadowRoot.querySelector('#mzta-custom_step'); this._customBtn.addEventListener("click", () => { this._customTextBtnClick({customBtn:this._customBtn,customLoading:this._customLoading,customDiv:this._customText}) }); this._customTextArea.addEventListener("keydown", (event) => { if(event.code == "Enter" && event.ctrlKey) this._customTextBtnClick({customBtn:this._customBtn,customLoading:this._customLoading,customDiv:this._customText}) }); } @@ -340,6 +351,13 @@ class MessageInput extends HTMLElement { infoSpan.textContent = "[" + browser.i18n.getMessage("customPrompts_form_label_ID") + ": " + currentItem.info + "]"; infoDiv.appendChild(infoSpan); } + + if(this._customTextArray.length > 1) { + this._customStep.textContent = (this._currentCustomTextIndex + 1) + "/" + this._customTextArray.length; + this._customStep.style.display = 'block'; + } else { + this._customStep.style.display = 'none'; + } this._customTextArea.focus(); } From d00e2425096cf1cd248756540a517ca0f933fcb6 Mon Sep 17 00:00:00 2001 From: mic Date: Thu, 12 Feb 2026 22:41:16 +0100 Subject: [PATCH 12/34] button margin added. see #525 #554 --- api_webchat/messageInput.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/api_webchat/messageInput.js b/api_webchat/messageInput.js index 1e226c15..3ebdd8c7 100644 --- a/api_webchat/messageInput.js +++ b/api_webchat/messageInput.js @@ -115,6 +115,9 @@ messagesInputStyle.textContent = ` font-size: 12px; color: #ccc; } + #mzta-custom_btn{ + margin-top:7px; + } @media (prefers-color-scheme: dark) { #messageInputField { background-color: #303030; From 12206f05573bc76dccb2868064959cb4bc2c3556 Mon Sep 17 00:00:00 2001 From: mic Date: Thu, 12 Feb 2026 22:46:50 +0100 Subject: [PATCH 13/34] submit additional_text when pressing enter. see #525 fixes #654 --- api_webchat/messageInput.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/api_webchat/messageInput.js b/api_webchat/messageInput.js index 3ebdd8c7..aebc2584 100644 --- a/api_webchat/messageInput.js +++ b/api_webchat/messageInput.js @@ -238,7 +238,12 @@ class MessageInput extends HTMLElement { this._customBtn = shadowRoot.querySelector('#mzta-custom_btn'); this._customStep = shadowRoot.querySelector('#mzta-custom_step'); this._customBtn.addEventListener("click", () => { this._customTextBtnClick({customBtn:this._customBtn,customLoading:this._customLoading,customDiv:this._customText}) }); - this._customTextArea.addEventListener("keydown", (event) => { if(event.code == "Enter" && event.ctrlKey) this._customTextBtnClick({customBtn:this._customBtn,customLoading:this._customLoading,customDiv:this._customText}) }); + this._customTextArea.addEventListener("keydown", (event) => { + if (event.key === "Enter" && !event.shiftKey) { + event.preventDefault(); + this._customTextBtnClick({customBtn:this._customBtn,customLoading:this._customLoading,customDiv:this._customText}); + } + }); } connectedCallback() { From 0740d94c796e789b5c68fbbdf8005d29fd134074 Mon Sep 17 00:00:00 2001 From: mic Date: Thu, 12 Feb 2026 22:49:30 +0100 Subject: [PATCH 14/34] focus on the additional_text textarea. see #525 --- api_webchat/messageInput.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api_webchat/messageInput.js b/api_webchat/messageInput.js index aebc2584..2907174f 100644 --- a/api_webchat/messageInput.js +++ b/api_webchat/messageInput.js @@ -342,8 +342,8 @@ class MessageInput extends HTMLElement { this._customTextArray.push({ placeholder: "{%additional_text%}", info: "" }); } this._currentCustomTextIndex = 0; - this._renderCustomTextStep(); this._customText.style.display = 'block'; + this._renderCustomTextStep(); } _renderCustomTextStep() { From 0ac6ae6650e2965ca0587c0db8f98bcc9609b631 Mon Sep 17 00:00:00 2001 From: mic Date: Sat, 14 Feb 2026 23:01:20 +0100 Subject: [PATCH 15/34] custom_text_array initialize fixed. see #525 --- js/mzta-menus.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/mzta-menus.js b/js/mzta-menus.js index b29ad6cc..cecfa413 100644 --- a/js/mzta-menus.js +++ b/js/mzta-menus.js @@ -193,7 +193,7 @@ export class mzta_Menus { tags_full_list: tags_full_list }); - curr_prompt.custom_text_array = {}; + curr_prompt.custom_text_array = []; if(placeholdersUtils.hasPlaceholder(curr_prompt.text, 'additional_text')){ curr_prompt.custom_text_array = placeholdersUtils.getPlaceholdersAdditionalTextArray(curr_prompt.text); From d5d11e3c0c16024279250b8a7ecedda7a4e43e97 Mon Sep 17 00:00:00 2001 From: mic Date: Sat, 14 Feb 2026 23:04:08 +0100 Subject: [PATCH 16/34] customDiv with multiple additional_text also when using the chatgpt web interface integration. see #525 #650 --- js/mzta-chatgpt.js | 93 +++++++++++++++++++++++++++++++++++++--------- mzta-background.js | 2 +- 2 files changed, 77 insertions(+), 18 deletions(-) diff --git a/js/mzta-chatgpt.js b/js/mzta-chatgpt.js index eeda649e..3ff3a440 100644 --- a/js/mzta-chatgpt.js +++ b/js/mzta-chatgpt.js @@ -30,6 +30,8 @@ let current_mailMessageId = null; let selectionChangeTimeout = null; let isDragging = false; let delay_wait_completion = 7000; // milliseconds +let _customTextArray = []; +let _currentCustomTextIndex = 0; async function chatgpt_sendMsg(msg, method ='') { // return -1 send button not found, -2 textarea not found let textArea = document.getElementById('prompt-textarea') @@ -125,6 +127,8 @@ function addCustomDiv(prompt_action,tabId,mailMessageId) { style.textContent += "#mzta-custom_loading{height:50px;display:none;}"; style.textContent += "#mzta-custom_textarea{color:black;padding:1px;font-size:15px;width:100%;}"; style.textContent += "#mzta-custom_info{text-align:center;width:100%;padding-bottom:10px;font-size:15px;}"; + style.textContent += "#mzta-custom_info span{font-size:0.8em;}"; + style.textContent += "#mzta-custom_step{position: absolute;bottom: 5px;right: 10px;font-size: 12px;color: #ccc;}"; style.textContent += "#mzta-prompt-name{font-size:13px;font-style:italic;color:#919191;position:fixed;bottom:75px;;left:0;padding-left:5px;}"; style.textContent += "#mzta-diff-overlay{position: fixed;top:0;left:0;width:100vw;height:100vh;background: rgba(0, 0, 0, 0.5);display:flex;justify-content:center;align-items:center;z-index:999;}"; style.textContent += "#mzta-diff{padding:10px;border:2px solid white;border-radius:1em;position:fixed;top:50%;left:50%;width:80%;height:30em;transform:translate(-50%,-50%);z-index:9999;background-color: #333;color: white;}"; @@ -365,6 +369,7 @@ function addCustomDiv(prompt_action,tabId,mailMessageId) { customDiv.appendChild(customInfo); let customTextArea = document.createElement('textarea'); customTextArea.id = 'mzta-custom_textarea'; + customTextArea.rows = 5; customDiv.appendChild(customTextArea); let customLoading = document.createElement('img'); customLoading.src = browser.runtime.getURL("/images/loading.gif"); @@ -377,6 +382,9 @@ function addCustomDiv(prompt_action,tabId,mailMessageId) { customBtn.addEventListener("click", () => { customTextBtnClick({customBtn:customBtn,customLoading:customLoading,customDiv:customDiv}) }); customTextArea.addEventListener("keydown", (event) => { if(event.code == "Enter" && event.ctrlKey) customTextBtnClick({customBtn:customBtn,customLoading:customLoading,customDiv:customDiv}) }); customDiv.appendChild(customBtn); + let customStep = document.createElement('div'); + customStep.id = 'mzta-custom_step'; + customDiv.appendChild(customStep); fixedDiv.appendChild(customDiv); // light background hint with diagonal thick arrow @@ -453,14 +461,54 @@ function createReplyToAllIcon() { return svg; } +function renderCustomTextStep() { + const currentItem = _customTextArray[_currentCustomTextIndex]; + const infoDiv = document.getElementById('mzta-custom_info'); + const customTextArea = document.getElementById('mzta-custom_textarea'); + const customStep = document.getElementById('mzta-custom_step'); + + customTextArea.value = ""; + infoDiv.textContent = browser.i18n.getMessage("chatgpt_win_custom_text"); + + if (currentItem.info && currentItem.info.trim() !== "") { + infoDiv.appendChild(document.createElement("br")); + const infoSpan = document.createElement("span"); + infoSpan.textContent = "[" + browser.i18n.getMessage("customPrompts_form_label_ID") + ": " + currentItem.info + "]"; + infoDiv.appendChild(infoSpan); + } + + if(_customTextArray.length > 1) { + customStep.textContent = (_currentCustomTextIndex + 1) + "/" + _customTextArray.length; + customStep.style.display = 'block'; + } else { + customStep.style.display = 'none'; + } + + customTextArea.focus(); +} + function customTextBtnClick(args) { const customText = document.getElementById('mzta-custom_textarea').value; - args.customBtn.disabled = true; - args.customBtn.classList.add('disabled'); - args.customLoading.style.display = 'inline-block'; - args.customLoading.style.display = 'none'; - doProceed(current_message,customText); - args.customDiv.style.display = 'none'; + + if (_customTextArray[_currentCustomTextIndex]) { + _customTextArray[_currentCustomTextIndex].custom_text = customText; + } + + _currentCustomTextIndex++; + + if (_currentCustomTextIndex < _customTextArray.length) { + renderCustomTextStep(); + } else { + args.customBtn.disabled = true; + args.customBtn.classList.add('disabled'); + args.customLoading.style.display = 'inline-block'; + args.customLoading.style.display = 'none'; + doProceed(current_message, _customTextArray); + args.customDiv.style.display = 'none'; + + args.customBtn.disabled = false; + args.customBtn.classList.remove('disabled'); + } } function checkGPTModel(model) { @@ -534,8 +582,14 @@ function checkLoggedIn(){ } function showCustomTextField(){ + let rawArray = current_message.prompt_info?.custom_text_array; + _customTextArray = Array.isArray(rawArray) ? rawArray : []; + if (_customTextArray.length === 0) { + _customTextArray.push({ placeholder: "{%additional_text%}", info: "" }); + } + _currentCustomTextIndex = 0; document.getElementById('mzta-custom_text').style.display = 'block'; - document.getElementById('mzta-custom_textarea').focus(); + renderCustomTextStep(); } async function doProceed(message, customText = ''){ @@ -545,16 +599,21 @@ async function doProceed(message, customText = ''){ await checkGPTModel(_gpt_model); } let final_prompt = message.prompt; -// console.log(">>>>>>>>>>>> doProceed customText: " + customText); -// console.log(">>>>>>>>>>>> doProceed final_prompt: " + final_prompt); -// console.log(">>>>>>>>>>>> doProceed mztaPhDefVal: " + JSON.stringify(mztaPhDefVal)); - //check if there is the additional_text placeholder - if(final_prompt.includes('{%additional_text%}')){ - // console.log(">>>>>>>>>>>> found ph customText: " + customText); - final_prompt = final_prompt.replace('{%additional_text%}', customText || (mztaPhDefVal == '1'?'':'{%additional_text%}')); - }else{ - if(customText != ''){ - final_prompt += ' '+customText; + + if (Array.isArray(customText)) { + customText.forEach(obj => { + let escapedPH = obj.placeholder.replace(/[.*+?^{$}()|[\\]\\\\]/g, '\\\\$&'); + let regex = new RegExp(escapedPH, 'g'); + final_prompt = final_prompt.replace(regex, obj.custom_text); + }); + } else { + //check if there is the additional_text placeholder + if(final_prompt.includes('{%additional_text%}')){ + final_prompt = final_prompt.replace('{%additional_text%}', customText || (mztaPhDefVal == '1'?'':'{%additional_text%}')); + }else{ + if(customText != ''){ + final_prompt += ' '+customText; + } } } diff --git a/mzta-background.js b/mzta-background.js index b0eb1f13..ea2473fd 100644 --- a/mzta-background.js +++ b/mzta-background.js @@ -515,7 +515,7 @@ async function openChatGPT(promptText, action, curr_tabId, prompt_name = '', do_ let mailMessageId = -1; if(mailMessage) mailMessageId = mailMessage.id; promptText = convertNewlinesToParagraphs(promptText); - browser.tabs.sendMessage(createdTab.id, { command: "chatgpt_send", prompt: promptText, action: action, tabId: curr_tabId, mailMessageId: mailMessageId}); + browser.tabs.sendMessage(createdTab.id, { command: "chatgpt_send", prompt: promptText, action: action, tabId: curr_tabId, mailMessageId: mailMessageId, prompt_info: prompt_info}); taLog.log('[ChatGPT Web] Connection succeded!'); taLog.log("[ThunderAI] ChatGPT Web script injected successfully"); browser.runtime.onMessage.removeListener(listener); From cac639e257ff9d5cdd1a2f237ca45488e583b641 Mon Sep 17 00:00:00 2001 From: mic Date: Sat, 14 Feb 2026 23:09:25 +0100 Subject: [PATCH 17/34] console.log debug statement commented out --- js/mzta-menus.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/mzta-menus.js b/js/mzta-menus.js index cecfa413..eed540cb 100644 --- a/js/mzta-menus.js +++ b/js/mzta-menus.js @@ -198,7 +198,7 @@ export class mzta_Menus { if(placeholdersUtils.hasPlaceholder(curr_prompt.text, 'additional_text')){ curr_prompt.custom_text_array = placeholdersUtils.getPlaceholdersAdditionalTextArray(curr_prompt.text); } - console.log(">>>>>>>>>>>>>>>>>>> curr_prompt.custom_text_array: " + JSON.stringify(curr_prompt.custom_text_array)); + // console.log(">>>>>>>>>>>>>>>>>>> curr_prompt.custom_text_array: " + JSON.stringify(curr_prompt.custom_text_array)); switch(curr_prompt.id){ case 'prompt_translate_this': From 5bbe379c0c8f5b74dd3f549a9d1b5ba66eeaf55a Mon Sep 17 00:00:00 2001 From: mic Date: Sat, 14 Feb 2026 23:14:13 +0100 Subject: [PATCH 18/34] release notes updated --- CHANGELOG.md | 2 ++ options/mzta-release-notes.html | 2 ++ 2 files changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc8b453f..ac68537c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,8 @@
  • [All APIs] Added a new menu item to create a calendar event from the text saved in the clipboard [#362].
  • Added a button to copy a prompt in the Custom Prompts page [#598].
  • [All APIs] Showing the spam filter info at the top of the message. The data is saved only for the session in which the message has been checked for spam [#506].
  • +
  • Fix: Now it's possibile to use multiple additional_text placeholders in a single prompt, also using custom placeholders [#554].
  • +
  • When using the additional_text placeholder is now possibile to specify an ID that will be shown in the form asking for the text [#525].
  • ...
  • Version 3.8.4 - 10/02/2026

    diff --git a/options/mzta-release-notes.html b/options/mzta-release-notes.html index 6a2bb65c..b95f3200 100644 --- a/options/mzta-release-notes.html +++ b/options/mzta-release-notes.html @@ -24,6 +24,8 @@
  • [All APIs] Added a new menu item to create a calendar event from the text saved in the clipboard [#362].
  • Added a button to copy a prompt in the Custom Prompts page [#598].
  • [All APIs] Showing the spam filter info at the top of the message. The data is saved only for the session in which the message has been checked for spam [#506].
  • +
  • Fix: Now it's possibile to use multiple additional_text placeholders in a single prompt, also using custom placeholders [#554].
  • +
  • When using the additional_text placeholder is now possibile to specify an ID that will be shown in the form asking for the text [#525].
  • ...
  • Version 3.8.4 - 10/02/2026

    From ae26e9288b4b3ae96958e39cb86fd54dfc36a3ad Mon Sep 17 00:00:00 2001 From: mic Date: Sat, 14 Feb 2026 23:23:07 +0100 Subject: [PATCH 19/34] when using the same id in additional_text, the text is asked only once and then used in every occurrence. see #525 --- js/mzta-placeholders.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/js/mzta-placeholders.js b/js/mzta-placeholders.js index b06f82d3..c666e858 100644 --- a/js/mzta-placeholders.js +++ b/js/mzta-placeholders.js @@ -489,11 +489,16 @@ export const placeholdersUtils = { const regex = /{%\s*additional_text(?::(.*?))?\s*%}/g; let matches = []; let match; + let foundIds = new Set(); while ((match = regex.exec(prompt_text)) !== null) { - matches.push({ - placeholder: match[0], - info: match[1] ? match[1].trim() : "" - }); + let info = match[1] ? match[1].trim() : ""; + if (!foundIds.has(info)) { + foundIds.add(info); + matches.push({ + placeholder: match[0], + info: info + }); + } } return matches; }, From 5c310d1cb019dfb41eacb581adf12e61cf11d9cf Mon Sep 17 00:00:00 2001 From: mic Date: Sun, 15 Feb 2026 10:30:22 +0100 Subject: [PATCH 20/34] ChatGPT Web Integration loading wait time option added. see #634 --- _locales/en/messages.json | 8 ++++++++ mzta-background.js | 4 ++-- options/mzta-options-default.js | 1 + pages/_lib/connection-ui.js | 11 +++++++++++ 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 60a2e279..97410de8 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -699,6 +699,14 @@ "message": "If checked, the temporary chat will be used in the ChatGPT Web Interface.", "description": "" }, + "prefs_OptionText_chatgpt_web_load_wait_time": { + "message": "Wait time for page load", + "description": "" + }, + "prefs_OptionText_chatgpt_web_load_wait_time_info": { + "message": "Time in milliseconds to wait for the ChatGPT page to load before loading the additional content. Default is 1000ms. If a Custom GPT or Project is defined, additional 1000ms will be added to this value.", + "description": "" + }, "chatgpt_btn_model": { "message": "Use the current model", "description": "" diff --git a/mzta-background.js b/mzta-background.js index ea2473fd..7be949a3 100644 --- a/mzta-background.js +++ b/mzta-background.js @@ -429,7 +429,7 @@ async function openChatGPT(promptText, action, curr_tabId, prompt_name = '', do_ let rand_call_id = '_chatgptweb_' + generateCallID(); let call_opt = ''; - let _wait_time = 1000; + let _wait_time = prefs.chatgpt_web_load_wait_time; let _base_url = "https://chatgpt.com"; let _webproject_set = false; let _custom_gpt_set = false; @@ -457,7 +457,7 @@ async function openChatGPT(promptText, action, curr_tabId, prompt_name = '', do_ 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; + _wait_time += 1000; } if(!_webproject_set && ((prompt_info.chatgpt_web_custom_gpt != '') || (prefs.chatgpt_web_custom_gpt != ''))){ _base_url += _custom_gpt; diff --git a/options/mzta-options-default.js b/options/mzta-options-default.js index ddeae06f..f2b5eb83 100644 --- a/options/mzta-options-default.js +++ b/options/mzta-options-default.js @@ -108,6 +108,7 @@ export const prefs_default = { chatgpt_web_tempchat: false, chatgpt_web_project: '', chatgpt_web_custom_gpt: '', + chatgpt_web_load_wait_time: 1000, dynamic_menu_force_enter: false, dynamic_menu_order_alphabet: true, placeholders_use_default_value: false, diff --git a/pages/_lib/connection-ui.js b/pages/_lib/connection-ui.js index 8683f077..64a0d0eb 100644 --- a/pages/_lib/connection-ui.js +++ b/pages/_lib/connection-ui.js @@ -153,6 +153,17 @@ export async function injectConnectionUI({ + + + + + + __MSG_OpenChatGPTTab_Info__

    From 8fcd9fab693474aedeeb824cb8a78d43bd9963bd Mon Sep 17 00:00:00 2001 From: mic Date: Sun, 15 Feb 2026 10:32:24 +0100 Subject: [PATCH 21/34] release notes updated --- CHANGELOG.md | 1 + options/mzta-release-notes.html | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac68537c..15d5888e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@
  • [All APIs] Showing the spam filter info at the top of the message. The data is saved only for the session in which the message has been checked for spam [#506].
  • Fix: Now it's possibile to use multiple additional_text placeholders in a single prompt, also using custom placeholders [#554].
  • When using the additional_text placeholder is now possibile to specify an ID that will be shown in the form asking for the text [#525].
  • +
  • [ChatGPT Web] Added an option to define a custom time to wait for the page load. Sometimes, on slow PCs, the ChatGPT page loads slowly and ThunderAI inject its content too early. With this option you can adjust the waiting time [#634].
  • ...
  • Version 3.8.4 - 10/02/2026

    diff --git a/options/mzta-release-notes.html b/options/mzta-release-notes.html index b95f3200..3c5ec795 100644 --- a/options/mzta-release-notes.html +++ b/options/mzta-release-notes.html @@ -26,6 +26,7 @@
  • [All APIs] Showing the spam filter info at the top of the message. The data is saved only for the session in which the message has been checked for spam [#506].
  • Fix: Now it's possibile to use multiple additional_text placeholders in a single prompt, also using custom placeholders [#554].
  • When using the additional_text placeholder is now possibile to specify an ID that will be shown in the form asking for the text [#525].
  • +
  • [ChatGPT Web] Added an option to define a custom time to wait for the page load. Sometimes, on slow PCs, the ChatGPT page loads slowly and ThunderAI inject its content too early. With this option you can adjust the waiting time [#634].
  • ...
  • Version 3.8.4 - 10/02/2026

    From a7a17b7cb5e0a3d340568c5200ca2391edeb6285 Mon Sep 17 00:00:00 2001 From: mic Date: Sun, 15 Feb 2026 10:58:44 +0100 Subject: [PATCH 22/34] processEmails now accepts an obj as argument. see #631 --- mzta-background.js | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/mzta-background.js b/mzta-background.js index 7be949a3..13da6dc1 100644 --- a/mzta-background.js +++ b/mzta-background.js @@ -1041,7 +1041,11 @@ browser.menus.onClicked.addListener( (info, tab) => { _summarize = true; } if(_add_tags || _spamfilter){ - processEmails(getMessages(info.selectedMessages), _add_tags, _spamfilter); + processEmails({ + messages: getMessages(info.selectedMessages), + addTagsAuto: _add_tags, + spamFilter: _spamfilter + }); } if(_summarize) { // info.selectedMessages is of type MessageList @@ -1135,7 +1139,11 @@ const newEmailListener = (folder, messagesList) => { let add_tags_auto_enabled = prefs_init.add_tags && prefs_init.add_tags_auto; - await processEmails(messages, add_tags_auto_enabled, prefs_init.spamfilter); + await processEmails({ + messages: messages, + addTagsAuto: add_tags_auto_enabled, + spamFilter: prefs_init.spamfilter + }); if(prefs_init.spamfilter){ taSpamReport.truncReportData(); @@ -1145,7 +1153,13 @@ const newEmailListener = (folder, messagesList) => { return _newEmailListener(); } -async function processEmails(messages, addTagsAuto, spamFilter) { +async function processEmails(args) { + const { + messages, + addTagsAuto = false, + spamFilter = false + } = args; + taWorkingStatus.startWorking(); let prefs_aats = await browser.storage.sync.get({ From ca2323ba8806e8e38ec8d44c4009f3150f828472 Mon Sep 17 00:00:00 2001 From: mic Date: Sun, 15 Feb 2026 11:08:38 +0100 Subject: [PATCH 23/34] processEmails now also does the summarize. see #631 --- mzta-background.js | 442 ++++++++++++++++++++++----------------------- 1 file changed, 219 insertions(+), 223 deletions(-) diff --git a/mzta-background.js b/mzta-background.js index 13da6dc1..de4c33b4 100644 --- a/mzta-background.js +++ b/mzta-background.js @@ -1040,87 +1040,16 @@ browser.menus.onClicked.addListener( (info, tab) => { if(info.menuItemId === contextMenuID_Summarize) { _summarize = true; } - if(_add_tags || _spamfilter){ + if(_add_tags || _spamfilter || _summarize){ processEmails({ messages: getMessages(info.selectedMessages), addTagsAuto: _add_tags, - spamFilter: _spamfilter + spamFilter: _spamfilter, + summarize: _summarize }); } - if(_summarize) { - // info.selectedMessages is of type MessageList - summarizeEmails(getMessages(info.selectedMessages)); - } }); -async function summarizeEmails(messages) { - taWorkingStatus.startWorking(); - - // we have three prompts, the actual assignment for the LLM, the email - // template prompt, and the email separator prompt - const specialPrompts = await getSpecialPrompts(); - const prompt = specialPrompts.find((prompt) => prompt.id === 'prompt_summarize'); - const prompt_email = specialPrompts.find((prompt) => prompt.id === 'prompt_summarize_email_template'); - const prompt_email_separator = specialPrompts.find((prompt) => prompt.id === 'prompt_summarize_email_separator'); - - const tabs = await browser.tabs.query({ active: true, currentWindow: true }); - const chatgpt_lang = await taPromptUtils.getDefaultLang(prompt); - - // replace placeholders in the prompts the assignment prompt and email - // separator prompt do not have a message as context, so there is only - // limited things to replace - const prompt_string = await taPromptUtils.preparePrompt({ - curr_prompt: prompt, - chatgpt_lang: chatgpt_lang, - }); - const prompt_email_separator_string = await taPromptUtils.preparePrompt({ - curr_prompt: prompt_email_separator, - chatgpt_lang: chatgpt_lang, - }); - - - // assemble all email messages into one string and add the assignment prompt - const messages_list = []; - for await (let curr_message of messages) { - - // extract body of current message as text - const curr_message_full = await browser.messages.getFull(curr_message.id); - const curr_body_full_html = getMailBody(curr_message_full); - const curr_body_full_text = htmlBodyToPlainText(curr_body_full_html.html); - if( curr_body_full_text.length === 0) { - taLog.log("No HTML found in the message body, using plain text..."); - curr_body_full_text = curr_message_full.text; - } - - messages_list.push(await taPromptUtils.preparePrompt({ - curr_prompt: prompt_email, - curr_message: curr_message, - chatgpt_lang: chatgpt_lang, - body_text: curr_body_full_text, - subject_text: curr_message_full.headers.subject, - msg_text: curr_body_full_html, - })); - }; - const messages_string = messages_list.join(prompt_email_separator_string); - - const full_prompt = prompt_string + prompt_email_separator_string + messages_string; - - // console.log(full_prompt); - - // send the prompt to the chat interface - openChatGPT( - full_prompt, - prompt.action, - tabs[0].id, - prompt.name, - prompt.need_custom_text, - prompt - ) - - taWorkingStatus.stopWorking(); - return {ok : '1'}; -} - // Listening for new received emails const newEmailListener = (folder, messagesList) => { @@ -1157,168 +1086,235 @@ async function processEmails(args) { const { messages, addTagsAuto = false, - spamFilter = false + spamFilter = false, + summarize = false } = args; taWorkingStatus.startWorking(); + + if (addTagsAuto || spamFilter) { + let prefs_aats = await browser.storage.sync.get({ + add_tags_maxnum: prefs_default.add_tags_maxnum, + connection_type: prefs_default.connection_type, + add_tags_force_lang: prefs_default.add_tags_force_lang, + default_chatgpt_lang: prefs_default.default_chatgpt_lang, + add_tags_auto_force_existing: prefs_default.add_tags_auto_force_existing, + add_tags_enabled_accounts: prefs_default.add_tags_enabled_accounts, + add_tags_exclusions_exact_match: prefs_default.add_tags_exclusions_exact_match, + add_tags_auto_uselist: prefs_default.add_tags_auto_uselist, + add_tags_auto_uselist_list: prefs_default.add_tags_auto_uselist_list, + spamfilter_enabled_accounts: prefs_default.spamfilter_enabled_accounts, + ...getDynamicSettingsDefaults(['use_specific_integration', 'connection_type']), + do_debug: prefs_default.do_debug, + }); + // console.log(">>>>>>>>>>>>>>>> prefs_aats: " + JSON.stringify(prefs_aats)); + for await (let message of messages) { + let curr_fullMessage = null; + let msg_text = null; + let body_text = ''; - let prefs_aats = await browser.storage.sync.get({ - add_tags_maxnum: prefs_default.add_tags_maxnum, - connection_type: prefs_default.connection_type, - add_tags_force_lang: prefs_default.add_tags_force_lang, - default_chatgpt_lang: prefs_default.default_chatgpt_lang, - add_tags_auto_force_existing: prefs_default.add_tags_auto_force_existing, - add_tags_enabled_accounts: prefs_default.add_tags_enabled_accounts, - add_tags_exclusions_exact_match: prefs_default.add_tags_exclusions_exact_match, - add_tags_auto_uselist: prefs_default.add_tags_auto_uselist, - add_tags_auto_uselist_list: prefs_default.add_tags_auto_uselist_list, - spamfilter_enabled_accounts: prefs_default.spamfilter_enabled_accounts, - ...getDynamicSettingsDefaults(['use_specific_integration', 'connection_type']), - do_debug: prefs_default.do_debug, - }); - // console.log(">>>>>>>>>>>>>>>> prefs_aats: " + JSON.stringify(prefs_aats)); - for await (let message of messages) { - let curr_fullMessage = null; - let msg_text = null; - let body_text = ''; - - if (addTagsAuto || spamFilter) { - curr_fullMessage = await browser.messages.getFull(message.id); - msg_text = getMailBody(curr_fullMessage); - taLog.log("Starting from the HTML body if present and converting to plain text..."); - body_text = htmlBodyToPlainText(msg_text.html); - if( body_text.length == 0 ){ - taLog.log("No HTML found in the message body, using plain text..."); - body_text = msg_text.text.replace(/\s+/g, ' ').trim(); - } - } - - if (addTagsAuto) { - if(prefs_aats.add_tags_enabled_accounts.length > 0){ - let accountId = message.folder.accountId; - if(!prefs_aats.add_tags_enabled_accounts.includes(accountId)){ - taLog.log("Account " + accountId + " not enabled for add_tags, skipping..."); - continue; + if (addTagsAuto || spamFilter) { + curr_fullMessage = await browser.messages.getFull(message.id); + msg_text = getMailBody(curr_fullMessage); + taLog.log("Starting from the HTML body if present and converting to plain text..."); + body_text = htmlBodyToPlainText(msg_text.html); + if( body_text.length == 0 ){ + taLog.log("No HTML found in the message body, using plain text..."); + body_text = msg_text.text.replace(/\s+/g, ' ').trim(); } } - 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: " + JSON.stringify(curr_prompt_add_tags)); - let chatgpt_lang = await taPromptUtils.getDefaultLang(curr_prompt_add_tags); - specialFullPrompt_add_tags = await taPromptUtils.preparePrompt({ - curr_prompt: curr_prompt_add_tags, - curr_message: message, - chatgpt_lang: chatgpt_lang, - body_text: body_text, - subject_text: curr_fullMessage.headers.subject, - msg_text: msg_text, - tags_full_list: 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, prefs_aats.add_tags_auto_uselist, prefs_aats.add_tags_auto_uselist_list); - taLog.log("Special prompt: " + specialFullPrompt_add_tags); - // console.log(">>>>>>>>>> curr_prompt_add_tags.model: " + curr_prompt_add_tags.model); - // console.log(">>>>>>>>>>>>>>>>> getConnectionType add_tags:" + JSON.stringify(getConnectionType(prefs_aats, curr_prompt_add_tags, 'add_tags'))); - let cmd_addTags = new mzta_specialCommand({ - prompt: specialFullPrompt_add_tags, - llm: getConnectionType(prefs_aats, curr_prompt_add_tags, 'add_tags'), - custom_model: curr_prompt_add_tags.model ? curr_prompt_add_tags.model : '', - do_debug: prefs_aats.do_debug, - config: curr_prompt_add_tags - }); - await cmd_addTags.initWorker(); - let tags_current_email = []; - try { - tags_current_email = taPromptUtils.getTagsFromResponse(await cmd_addTags.sendPrompt(), prefs_aats.add_tags_auto_uselist, prefs_aats.add_tags_auto_uselist_list); - } catch (err) { - console.error("[ThunderAI | Auto add_tags] Error getting tags: ", err); - } - taLog.log("tags_current_email: " + JSON.stringify(tags_current_email)); - let _data = { messageId: message.id, tags: tags_current_email }; - _assign_tags(_data, !prefs_aats.add_tags_auto_force_existing, prefs_aats.add_tags_exclusions_exact_match); - } - - if (spamFilter) { - if(prefs_aats.spamfilter_enabled_accounts.length > 0){ - let accountId = message.folder.accountId; - if(!prefs_aats.spamfilter_enabled_accounts.includes(accountId)){ - taLog.log("Account " + accountId + " not enabled for spamfilter, skipping..."); - continue; + + if (addTagsAuto) { + if(prefs_aats.add_tags_enabled_accounts.length > 0){ + let accountId = message.folder.accountId; + if(!prefs_aats.add_tags_enabled_accounts.includes(accountId)){ + taLog.log("Account " + accountId + " not enabled for add_tags, skipping..."); + continue; + } } + 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: " + JSON.stringify(curr_prompt_add_tags)); + let chatgpt_lang = await taPromptUtils.getDefaultLang(curr_prompt_add_tags); + specialFullPrompt_add_tags = await taPromptUtils.preparePrompt({ + curr_prompt: curr_prompt_add_tags, + curr_message: message, + chatgpt_lang: chatgpt_lang, + body_text: body_text, + subject_text: curr_fullMessage.headers.subject, + msg_text: msg_text, + tags_full_list: 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, prefs_aats.add_tags_auto_uselist, prefs_aats.add_tags_auto_uselist_list); + taLog.log("Special prompt: " + specialFullPrompt_add_tags); + // console.log(">>>>>>>>>> curr_prompt_add_tags.model: " + curr_prompt_add_tags.model); + // console.log(">>>>>>>>>>>>>>>>> getConnectionType add_tags:" + JSON.stringify(getConnectionType(prefs_aats, curr_prompt_add_tags, 'add_tags'))); + let cmd_addTags = new mzta_specialCommand({ + prompt: specialFullPrompt_add_tags, + llm: getConnectionType(prefs_aats, curr_prompt_add_tags, 'add_tags'), + custom_model: curr_prompt_add_tags.model ? curr_prompt_add_tags.model : '', + do_debug: prefs_aats.do_debug, + config: curr_prompt_add_tags + }); + await cmd_addTags.initWorker(); + let tags_current_email = []; + try { + tags_current_email = taPromptUtils.getTagsFromResponse(await cmd_addTags.sendPrompt(), prefs_aats.add_tags_auto_uselist, prefs_aats.add_tags_auto_uselist_list); + } catch (err) { + console.error("[ThunderAI | Auto add_tags] Error getting tags: ", err); + } + taLog.log("tags_current_email: " + JSON.stringify(tags_current_email)); + let _data = { messageId: message.id, tags: tags_current_email }; + _assign_tags(_data, !prefs_aats.add_tags_auto_force_existing, prefs_aats.add_tags_exclusions_exact_match); } - let curr_prompt_spamfilter = await getSpamFilterPrompt(); - // console.log(">>>>>>>>>>>>> curr_prompt_spamfilter: " + JSON.stringify(curr_prompt_spamfilter)); - let chatgpt_lang = await taPromptUtils.getDefaultLang(curr_prompt_spamfilter); - let specialFullPrompt_spamfilter = await taPromptUtils.preparePrompt({ - curr_prompt: curr_prompt_spamfilter, - curr_message: message, - chatgpt_lang: chatgpt_lang, - body_text: body_text, - subject_text: curr_fullMessage.headers.subject, - msg_text: msg_text - }); - taLog.log("Special prompt: " + specialFullPrompt_spamfilter); - // console.log(">>>>>>>> Special prompt for spamfilter: " + specialFullPrompt_spamfilter); - let cmd_spamfilter = new mzta_specialCommand({ - prompt: specialFullPrompt_spamfilter, - llm: getConnectionType(prefs_aats, curr_prompt_spamfilter, 'spamfilter'), - custom_model: curr_prompt_spamfilter.model ? curr_prompt_spamfilter.model : '', - do_debug: prefs_aats.do_debug, - config: curr_prompt_spamfilter - }); - await cmd_spamfilter.initWorker(); - let spamfilter_result = ''; - taLog.log("Sending the prompt..."); - try { - spamfilter_result = (await cmd_spamfilter.sendPrompt()).trim(); - } catch (err) { - console.error("[ThunderAI | SpamFilter] Error getting spamfilter: ", err); - } - taLog.log("spamfilter_result: " + spamfilter_result); - let jsonObj = {}; - taLog.log("Decoding the AI response..."); - try { - jsonObj = extractJsonObject(spamfilter_result); - } catch (e) { - console.error("[ThunderAI | SpamFilter] Error extracting JSON from AI response: ", e); - } - taLog.log("SpamFilter jsonObj: " + JSON.stringify(jsonObj)); - - let report_data = {}; - report_data.report_date = new Date(); - report_data.headerMessageId = message.headerMessageId; - report_data.spamValue = jsonObj.spamValue; - report_data.explanation = jsonObj.explanation; - report_data.subject = curr_fullMessage.headers.subject; - report_data.from = curr_fullMessage.headers.from; - report_data.message_date = new Date(message.date); - report_data.moved = false; - report_data.SpamThreshold = prefs_init.spamfilter_threshold; - - if (jsonObj.spamValue >= prefs_init.spamfilter_threshold) { - taLog.log("Marking as spam [" + message.headerMessageId + "]"); - messenger.messages.update(message.id, { junk: true }); - let spamFolder = await messenger.folders.query({ accountId: message.folder.accountId, specialUse: ['junk'] }); - messenger.messages.move([message.id], spamFolder[0].id); - report_data.moved = true; - taLog.log("Marked as spam [" + message.headerMessageId + "]"); - } - - taSpamReport.saveReportData(report_data, message.headerMessageId); - - // Check if the message is currently displayed and update the banner - if (prefs_init.spamfilter_show_msg_panel) { - let tabs = await browser.tabs.query({ active: true, currentWindow: true }); - if (tabs.length > 0) { - let activeTab = tabs[0]; - let displayedMessage = await browser.messageDisplay.getDisplayedMessage(activeTab.id); - if (displayedMessage && displayedMessage.id === message.id) { - browser.tabs.sendMessage(activeTab.id, { command: "showSpamReport", data: report_data }); + + if (spamFilter) { + if(prefs_aats.spamfilter_enabled_accounts.length > 0){ + let accountId = message.folder.accountId; + if(!prefs_aats.spamfilter_enabled_accounts.includes(accountId)){ + taLog.log("Account " + accountId + " not enabled for spamfilter, skipping..."); + continue; + } + } + let curr_prompt_spamfilter = await getSpamFilterPrompt(); + // console.log(">>>>>>>>>>>>> curr_prompt_spamfilter: " + JSON.stringify(curr_prompt_spamfilter)); + let chatgpt_lang = await taPromptUtils.getDefaultLang(curr_prompt_spamfilter); + let specialFullPrompt_spamfilter = await taPromptUtils.preparePrompt({ + curr_prompt: curr_prompt_spamfilter, + curr_message: message, + chatgpt_lang: chatgpt_lang, + body_text: body_text, + subject_text: curr_fullMessage.headers.subject, + msg_text: msg_text + }); + taLog.log("Special prompt: " + specialFullPrompt_spamfilter); + // console.log(">>>>>>>> Special prompt for spamfilter: " + specialFullPrompt_spamfilter); + let cmd_spamfilter = new mzta_specialCommand({ + prompt: specialFullPrompt_spamfilter, + llm: getConnectionType(prefs_aats, curr_prompt_spamfilter, 'spamfilter'), + custom_model: curr_prompt_spamfilter.model ? curr_prompt_spamfilter.model : '', + do_debug: prefs_aats.do_debug, + config: curr_prompt_spamfilter + }); + await cmd_spamfilter.initWorker(); + let spamfilter_result = ''; + taLog.log("Sending the prompt..."); + try { + spamfilter_result = (await cmd_spamfilter.sendPrompt()).trim(); + } catch (err) { + console.error("[ThunderAI | SpamFilter] Error getting spamfilter: ", err); + } + taLog.log("spamfilter_result: " + spamfilter_result); + let jsonObj = {}; + taLog.log("Decoding the AI response..."); + try { + jsonObj = extractJsonObject(spamfilter_result); + } catch (e) { + console.error("[ThunderAI | SpamFilter] Error extracting JSON from AI response: ", e); + } + taLog.log("SpamFilter jsonObj: " + JSON.stringify(jsonObj)); + + let report_data = {}; + report_data.report_date = new Date(); + report_data.headerMessageId = message.headerMessageId; + report_data.spamValue = jsonObj.spamValue; + report_data.explanation = jsonObj.explanation; + report_data.subject = curr_fullMessage.headers.subject; + report_data.from = curr_fullMessage.headers.from; + report_data.message_date = new Date(message.date); + report_data.moved = false; + report_data.SpamThreshold = prefs_init.spamfilter_threshold; + + if (jsonObj.spamValue >= prefs_init.spamfilter_threshold) { + taLog.log("Marking as spam [" + message.headerMessageId + "]"); + messenger.messages.update(message.id, { junk: true }); + let spamFolder = await messenger.folders.query({ accountId: message.folder.accountId, specialUse: ['junk'] }); + messenger.messages.move([message.id], spamFolder[0].id); + report_data.moved = true; + taLog.log("Marked as spam [" + message.headerMessageId + "]"); + } + + taSpamReport.saveReportData(report_data, message.headerMessageId); + + // Check if the message is currently displayed and update the banner + if (prefs_init.spamfilter_show_msg_panel) { + let tabs = await browser.tabs.query({ active: true, currentWindow: true }); + if (tabs.length > 0) { + let activeTab = tabs[0]; + let displayedMessage = await browser.messageDisplay.getDisplayedMessage(activeTab.id); + if (displayedMessage && displayedMessage.id === message.id) { + browser.tabs.sendMessage(activeTab.id, { command: "showSpamReport", data: report_data }); + } } } } } } + + if (summarize) { + // we have three prompts, the actual assignment for the LLM, the email + // template prompt, and the email separator prompt + const specialPrompts = await getSpecialPrompts(); + const prompt = specialPrompts.find((prompt) => prompt.id === 'prompt_summarize'); + const prompt_email = specialPrompts.find((prompt) => prompt.id === 'prompt_summarize_email_template'); + const prompt_email_separator = specialPrompts.find((prompt) => prompt.id === 'prompt_summarize_email_separator'); + + const tabs = await browser.tabs.query({ active: true, currentWindow: true }); + const chatgpt_lang = await taPromptUtils.getDefaultLang(prompt); + + // replace placeholders in the prompts the assignment prompt and email + // separator prompt do not have a message as context, so there is only + // limited things to replace + const prompt_string = await taPromptUtils.preparePrompt({ + curr_prompt: prompt, + chatgpt_lang: chatgpt_lang, + }); + const prompt_email_separator_string = await taPromptUtils.preparePrompt({ + curr_prompt: prompt_email_separator, + chatgpt_lang: chatgpt_lang, + }); + + + // assemble all email messages into one string and add the assignment prompt + const messages_list = []; + for await (let curr_message of messages) { + + // extract body of current message as text + const curr_message_full = await browser.messages.getFull(curr_message.id); + const curr_body_full_html = getMailBody(curr_message_full); + let curr_body_full_text = htmlBodyToPlainText(curr_body_full_html.html); + if( curr_body_full_text.length === 0) { + taLog.log("No HTML found in the message body, using plain text..."); + curr_body_full_text = curr_message_full.text; + } + + messages_list.push(await taPromptUtils.preparePrompt({ + curr_prompt: prompt_email, + curr_message: curr_message, + chatgpt_lang: chatgpt_lang, + body_text: curr_body_full_text, + subject_text: curr_message_full.headers.subject, + msg_text: curr_body_full_html, + })); + }; + const messages_string = messages_list.join(prompt_email_separator_string); + + const full_prompt = prompt_string + prompt_email_separator_string + messages_string; + + // console.log(full_prompt); + + // send the prompt to the chat interface + openChatGPT( + full_prompt, + prompt.action, + tabs[0].id, + prompt.name, + prompt.need_custom_text, + prompt + ); + } + taWorkingStatus.stopWorking(); } From 33901882cfe7179e88bb69f8850377885b09a610 Mon Sep 17 00:00:00 2001 From: bittin1ddc447d824349b2 Date: Sun, 15 Feb 2026 10:53:39 +0100 Subject: [PATCH 24/34] Translated using Weblate (Swedish) Currently translated at 100.0% (470 of 470 strings) Translation: ThunderAI for Thunderbird/main Translate-URL: https://hosted.weblate.org/projects/thunderai/main/sv/ --- _locales/sv/messages.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/_locales/sv/messages.json b/_locales/sv/messages.json index f447b3d8..0b4ea84a 100644 --- a/_locales/sv/messages.json +++ b/_locales/sv/messages.json @@ -1412,5 +1412,11 @@ }, "SpamReport_infoline": { "message": "Denna information sparas endast för den aktuella sessionen." + }, + "prefs_OptionText_chatgpt_web_load_wait_time": { + "message": "Väntetid för sidinläsning" + }, + "prefs_OptionText_chatgpt_web_load_wait_time_info": { + "message": "Tid i millisekunder att vänta på att ChatGPT-sidan ska laddas innan ytterligare innehåll laddas. Standardvärdet är 1000 ms. Om en anpassad GPT eller ett anpassat projekt definieras läggs ytterligare 1000 ms till detta värde." } } From fb83ad86fdaddc28c42bc45c5d5d66a81c68f4c2 Mon Sep 17 00:00:00 2001 From: mic Date: Sun, 15 Feb 2026 22:04:37 +0100 Subject: [PATCH 25/34] info comment added --- mzta-background.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mzta-background.js b/mzta-background.js index de4c33b4..14ddacdc 100644 --- a/mzta-background.js +++ b/mzta-background.js @@ -1092,6 +1092,9 @@ async function processEmails(args) { taWorkingStatus.startWorking(); + // We keep two different loops, one for addTagsAuto and spamFilter and one for summarize + // because summarize is never called when an email is received, but only when using the context menu item + if (addTagsAuto || spamFilter) { let prefs_aats = await browser.storage.sync.get({ add_tags_maxnum: prefs_default.add_tags_maxnum, From 25a979e9fba3223198741116fc3e2d00b60f754e Mon Sep 17 00:00:00 2001 From: mic Date: Mon, 16 Feb 2026 21:53:53 +0100 Subject: [PATCH 26/34] added methods and logic to save the processing status. see #658 --- js/mzta-spamreport.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/js/mzta-spamreport.js b/js/mzta-spamreport.js index 3674cdc0..b4640cd0 100644 --- a/js/mzta-spamreport.js +++ b/js/mzta-spamreport.js @@ -19,11 +19,24 @@ export const taSpamReport = { logger: console, _data_prefix: 'mzta-spam-report-', + _processing_prefix: 'mzta-spam-processing-', _max_reports: 100, + async setProcessing(data_id) { + const key = this._processing_prefix + data_id; + await browser.storage.session.set({ [key]: true }); + }, + + async isProcessing(data_id) { + const key = this._processing_prefix + data_id; + let output = await browser.storage.session.get(key); + return output[key] || false; + }, + async saveReportData(data, data_id) { const key = this._data_prefix + data_id; await browser.storage.session.set({ [key]: data }); + await browser.storage.session.remove(this._processing_prefix + data_id); }, async loadReportData(data_id) { @@ -35,6 +48,7 @@ export const taSpamReport = { async removeReportData(data_id) { const key = this._data_prefix + data_id; await browser.storage.session.remove(key); + await browser.storage.session.remove(this._processing_prefix + data_id); }, async getAllReportData() { @@ -52,7 +66,7 @@ export const taSpamReport = { async clearReportData() { let allData = await browser.storage.session.get(null); - let keysToDelete = Object.keys(allData).filter(key => key.startsWith(this._data_prefix)); + let keysToDelete = Object.keys(allData).filter(key => key.startsWith(this._data_prefix) || key.startsWith(this._processing_prefix)); for (let key of keysToDelete) { await browser.storage.session.remove(key); From ae4459610607e1e271c695429b5c293348a1d10e Mon Sep 17 00:00:00 2001 From: mic Date: Mon, 16 Feb 2026 21:54:17 +0100 Subject: [PATCH 27/34] showing the processing status for the antispam filter. see #658 --- _locales/en/messages.json | 4 ++++ js/mzta-compose-script.js | 31 +++++++++++++++++++++++++++++++ mzta-background.js | 16 ++++++++++++++++ 3 files changed, 51 insertions(+) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 97410de8..bdaec663 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1888,5 +1888,9 @@ "copy_text": { "message": "copy", "description": "" + }, + "spam_check_in_progress": { + "message": "Spam check in progress...", + "description": "" } } diff --git a/js/mzta-compose-script.js b/js/mzta-compose-script.js index 09e64272..d85a4914 100644 --- a/js/mzta-compose-script.js +++ b/js/mzta-compose-script.js @@ -617,7 +617,38 @@ switch (message.command) { break; + case "showSpamCheckInProgress": + if(document.getElementById('mzta-spam-report-banner')) return Promise.resolve(true); + if(document.getElementById('mzta-spam-check-progress')) return Promise.resolve(true); + + const containerProgress = document.createElement('div'); + containerProgress.id = 'mzta-spam-check-progress'; + + const isDarkProgress = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; + + let bgColorProgress = isDarkProgress ? '#003366' : '#e6f2ff'; + let textColorProgress = isDarkProgress ? '#cce5ff' : '#004085'; + let borderColorProgress = isDarkProgress ? '#004085' : '#b8daff'; + + containerProgress.style.cssText = `background-color: ${bgColorProgress}; color: ${textColorProgress}; border-bottom: 1px solid ${borderColorProgress}; padding: 8px 12px; font-family: system-ui, -apple-system, sans-serif; font-size: 13px; display: flex; align-items: center; gap: 15px; width: 100%; box-sizing: border-box;`; + + const textProgress = document.createElement('strong'); + textProgress.textContent = browser.i18n.getMessage("spam_check_in_progress"); + + const loadingImg = document.createElement('img'); + loadingImg.src = browser.runtime.getURL("/images/loading.gif"); + loadingImg.style.cssText = "height: 16px; width: 16px;"; + + containerProgress.appendChild(loadingImg); + containerProgress.appendChild(textProgress); + + document.body.insertBefore(containerProgress, document.body.firstChild); + return Promise.resolve(true); + case "showSpamReport": + const progressBanner = document.getElementById('mzta-spam-check-progress'); + if(progressBanner) progressBanner.remove(); + const data = message.data; if(document.getElementById('mzta-spam-report-banner')) return Promise.resolve(true); diff --git a/mzta-background.js b/mzta-background.js index 14ddacdc..4e2e7699 100644 --- a/mzta-background.js +++ b/mzta-background.js @@ -371,6 +371,8 @@ messenger.runtime.onMessage.addListener((message, sender, sendResponse) => { let report = await taSpamReport.loadReportData(message.headerMessageId); if (report) { browser.tabs.sendMessage(tabId, { command: "showSpamReport", data: report }); + } else if (await taSpamReport.isProcessing(message.headerMessageId)) { + browser.tabs.sendMessage(tabId, { command: "showSpamCheckInProgress" }); } } catch (e) { taLog.error("Error in checkSpamReport: " + e); @@ -1180,6 +1182,20 @@ async function processEmails(args) { continue; } } + + await taSpamReport.setProcessing(message.headerMessageId); + + if (prefs_init.spamfilter_show_msg_panel) { + let tabs = await browser.tabs.query({ active: true, currentWindow: true }); + if (tabs.length > 0) { + let activeTab = tabs[0]; + let displayedMessage = await browser.messageDisplay.getDisplayedMessage(activeTab.id); + if (displayedMessage && displayedMessage.id === message.id) { + browser.tabs.sendMessage(activeTab.id, { command: "showSpamCheckInProgress" }); + } + } + } + let curr_prompt_spamfilter = await getSpamFilterPrompt(); // console.log(">>>>>>>>>>>>> curr_prompt_spamfilter: " + JSON.stringify(curr_prompt_spamfilter)); let chatgpt_lang = await taPromptUtils.getDefaultLang(curr_prompt_spamfilter); From bd72c45df22f74b38356c0fe482f1b9bc7f0e962 Mon Sep 17 00:00:00 2001 From: mic Date: Mon, 16 Feb 2026 21:57:53 +0100 Subject: [PATCH 28/34] saveError method added. see #658 --- js/mzta-spamreport.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/js/mzta-spamreport.js b/js/mzta-spamreport.js index b4640cd0..f1f7d5d4 100644 --- a/js/mzta-spamreport.js +++ b/js/mzta-spamreport.js @@ -39,6 +39,16 @@ export const taSpamReport = { await browser.storage.session.remove(this._processing_prefix + data_id); }, + async saveError(data_id, error_message) { + let data = { + spamValue: -999, + explanation: error_message, + report_date: new Date(), + headerMessageId: data_id + }; + await this.saveReportData(data, data_id); + }, + async loadReportData(data_id) { const key = this._data_prefix + data_id; let output = await browser.storage.session.get(key); From c4176ebfec34bac5029eb221df69d7a95295ecb2 Mon Sep 17 00:00:00 2001 From: mic Date: Mon, 16 Feb 2026 22:22:39 +0100 Subject: [PATCH 29/34] saveError now returns the saved data. see #658 --- js/mzta-spamreport.js | 1 + 1 file changed, 1 insertion(+) diff --git a/js/mzta-spamreport.js b/js/mzta-spamreport.js index f1f7d5d4..f18a59f1 100644 --- a/js/mzta-spamreport.js +++ b/js/mzta-spamreport.js @@ -47,6 +47,7 @@ export const taSpamReport = { headerMessageId: data_id }; await this.saveReportData(data, data_id); + return data; }, async loadReportData(data_id) { From dc5e23a4d568d8cfa56cd6d7cb22a1d167b216f7 Mon Sep 17 00:00:00 2001 From: mic Date: Mon, 16 Feb 2026 22:23:15 +0100 Subject: [PATCH 30/34] correctly showing spam elaboration errors. see #658 --- mzta-background.js | 45 +++++++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/mzta-background.js b/mzta-background.js index 4e2e7699..56edd9d6 100644 --- a/mzta-background.js +++ b/mzta-background.js @@ -1084,6 +1084,23 @@ const newEmailListener = (folder, messagesList) => { return _newEmailListener(); } +async function updateSpamPanel(messageId, command, data = null) { + if (prefs_init.spamfilter_show_msg_panel) { + let tabs = await browser.tabs.query({ active: true, currentWindow: true }); + if (tabs.length > 0) { + let activeTab = tabs[0]; + let displayedMessage = await browser.messageDisplay.getDisplayedMessage(activeTab.id); + if (displayedMessage && displayedMessage.headerMessageId === messageId) { + let msg = { command: command }; + if (data) { + msg.data = data; + } + browser.tabs.sendMessage(activeTab.id, msg); + } + } + } +} + async function processEmails(args) { const { messages, @@ -1185,16 +1202,7 @@ async function processEmails(args) { await taSpamReport.setProcessing(message.headerMessageId); - if (prefs_init.spamfilter_show_msg_panel) { - let tabs = await browser.tabs.query({ active: true, currentWindow: true }); - if (tabs.length > 0) { - let activeTab = tabs[0]; - let displayedMessage = await browser.messageDisplay.getDisplayedMessage(activeTab.id); - if (displayedMessage && displayedMessage.id === message.id) { - browser.tabs.sendMessage(activeTab.id, { command: "showSpamCheckInProgress" }); - } - } - } + await updateSpamPanel(message.headerMessageId, "showSpamCheckInProgress"); let curr_prompt_spamfilter = await getSpamFilterPrompt(); // console.log(">>>>>>>>>>>>> curr_prompt_spamfilter: " + JSON.stringify(curr_prompt_spamfilter)); @@ -1223,6 +1231,9 @@ async function processEmails(args) { spamfilter_result = (await cmd_spamfilter.sendPrompt()).trim(); } catch (err) { console.error("[ThunderAI | SpamFilter] Error getting spamfilter: ", err); + let err_data = await taSpamReport.saveError(message.headerMessageId, err.message || String(err)); + await updateSpamPanel(message.headerMessageId, "showSpamReport", err_data); + continue; } taLog.log("spamfilter_result: " + spamfilter_result); let jsonObj = {}; @@ -1231,6 +1242,9 @@ async function processEmails(args) { jsonObj = extractJsonObject(spamfilter_result); } catch (e) { console.error("[ThunderAI | SpamFilter] Error extracting JSON from AI response: ", e); + let err_data = await taSpamReport.saveError(message.headerMessageId, e.message || String(e)); + await updateSpamPanel(message.headerMessageId, "showSpamReport", err_data); + continue; } taLog.log("SpamFilter jsonObj: " + JSON.stringify(jsonObj)); @@ -1257,16 +1271,7 @@ async function processEmails(args) { taSpamReport.saveReportData(report_data, message.headerMessageId); // Check if the message is currently displayed and update the banner - if (prefs_init.spamfilter_show_msg_panel) { - let tabs = await browser.tabs.query({ active: true, currentWindow: true }); - if (tabs.length > 0) { - let activeTab = tabs[0]; - let displayedMessage = await browser.messageDisplay.getDisplayedMessage(activeTab.id); - if (displayedMessage && displayedMessage.id === message.id) { - browser.tabs.sendMessage(activeTab.id, { command: "showSpamReport", data: report_data }); - } - } - } + await updateSpamPanel(message.headerMessageId, "showSpamReport", report_data); } } } From b551692768b1bf5159c963e9fd46b2fa9ac79ced Mon Sep 17 00:00:00 2001 From: mic Date: Mon, 16 Feb 2026 22:25:44 +0100 Subject: [PATCH 31/34] showing antispam errors on message. see #658 --- js/mzta-compose-script.js | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/js/mzta-compose-script.js b/js/mzta-compose-script.js index d85a4914..a92a688f 100644 --- a/js/mzta-compose-script.js +++ b/js/mzta-compose-script.js @@ -661,7 +661,11 @@ switch (message.command) { let textColor = '#333'; let borderColor = '#ccc'; - if (data.spamValue >= (data.SpamThreshold || 50)) { + if (data.spamValue == -999) { + bgColor = isDark ? '#332701' : '#fff3cd'; + textColor = isDark ? '#ffeb80' : '#856404'; + borderColor = isDark ? '#664d03' : '#ffeeba'; + } else if (data.spamValue >= (data.SpamThreshold || 50)) { bgColor = isDark ? '#5a1a1a' : '#ffe6e6'; textColor = isDark ? '#ffcccc' : '#cc0000'; borderColor = '#cc0000'; @@ -671,13 +675,21 @@ switch (message.command) { borderColor = '#006600'; } - container.style.cssText = `background-color: ${bgColor}; color: ${textColor}; border-bottom: 1px solid ${borderColor}; padding: 8px 12px; font-family: system-ui, -apple-system, sans-serif; font-size: 13px; display: flex; align-items: center; gap: 15px; width: 100%; box-sizing: border-box;`; + container.style.cssText = `background-color: ${bgColor}; color: ${textColor}; border-bottom: 1px solid ${borderColor}; padding: 8px 12px; font-family: system-ui, -apple-system, sans-serif; font-size: 13px; display: flex; align-items: start; gap: 15px; width: 100%; box-sizing: border-box;`; const scoreText = document.createElement('strong'); - scoreText.textContent = ((data.spamValue >= (data.SpamThreshold || 50)) ? browser.i18n.getMessage("Spam") : browser.i18n.getMessage("Valid")) + " [" + data.spamValue + "/100]"; + if (data.spamValue == -999) { + scoreText.textContent = browser.i18n.getMessage("apiwebchat_error"); + } else { + scoreText.textContent = ((data.spamValue >= (data.SpamThreshold || 50)) ? browser.i18n.getMessage("Spam") : browser.i18n.getMessage("Valid")) + " [" + data.spamValue + "/100]"; + } const reasonText = document.createElement('span'); - reasonText.textContent = browser.i18n.getMessage("Explanation") + ": " + data.explanation; + if (data.spamValue == -999) { + reasonText.textContent = data.explanation; + } else { + reasonText.textContent = browser.i18n.getMessage("Explanation") + ": " + data.explanation; + } const closeBtn = document.createElement('span'); closeBtn.textContent = '×'; From 296fcf21be4532b8bb92fd2dbc91c3f9516ce9f1 Mon Sep 17 00:00:00 2001 From: mic Date: Mon, 16 Feb 2026 22:30:20 +0100 Subject: [PATCH 32/34] if a message has already been checked for spam and the user asks for another check, the previous banne is removed. see #658 --- js/mzta-compose-script.js | 4 +++- mzta-background.js | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/js/mzta-compose-script.js b/js/mzta-compose-script.js index a92a688f..e30298fd 100644 --- a/js/mzta-compose-script.js +++ b/js/mzta-compose-script.js @@ -618,7 +618,9 @@ switch (message.command) { break; case "showSpamCheckInProgress": - if(document.getElementById('mzta-spam-report-banner')) return Promise.resolve(true); + const oldBanner = document.getElementById('mzta-spam-report-banner'); + if(oldBanner) oldBanner.remove(); + if(document.getElementById('mzta-spam-check-progress')) return Promise.resolve(true); const containerProgress = document.createElement('div'); diff --git a/mzta-background.js b/mzta-background.js index 56edd9d6..18cfea21 100644 --- a/mzta-background.js +++ b/mzta-background.js @@ -1200,6 +1200,7 @@ async function processEmails(args) { } } + await taSpamReport.removeReportData(message.headerMessageId); await taSpamReport.setProcessing(message.headerMessageId); await updateSpamPanel(message.headerMessageId, "showSpamCheckInProgress"); From 490427802df39a33d9716ab9a6515e2817bb4a5e Mon Sep 17 00:00:00 2001 From: mic Date: Mon, 16 Feb 2026 22:31:14 +0100 Subject: [PATCH 33/34] release notes updated --- CHANGELOG.md | 2 +- options/mzta-release-notes.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15d5888e..c3efc6fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,7 @@
  • [All APIs] Added an option to get a calendar event without selecting some text, but using the full text body of the email [#518].
  • [All APIs] Added a new menu item to create a calendar event from the text saved in the clipboard [#362].
  • Added a button to copy a prompt in the Custom Prompts page [#598].
  • -
  • [All APIs] Showing the spam filter info at the top of the message. The data is saved only for the session in which the message has been checked for spam [#506].
  • +
  • [All APIs] Showing the spam filter info at the top of the message. The data is saved only for the session in which the message has been checked for spam [#506, #658].
  • Fix: Now it's possibile to use multiple additional_text placeholders in a single prompt, also using custom placeholders [#554].
  • When using the additional_text placeholder is now possibile to specify an ID that will be shown in the form asking for the text [#525].
  • [ChatGPT Web] Added an option to define a custom time to wait for the page load. Sometimes, on slow PCs, the ChatGPT page loads slowly and ThunderAI inject its content too early. With this option you can adjust the waiting time [#634].
  • diff --git a/options/mzta-release-notes.html b/options/mzta-release-notes.html index 3c5ec795..672ad7d6 100644 --- a/options/mzta-release-notes.html +++ b/options/mzta-release-notes.html @@ -23,7 +23,7 @@
  • [All APIs] Added an option to get a calendar event without selecting some text, but using the full text body of the email [#518].
  • [All APIs] Added a new menu item to create a calendar event from the text saved in the clipboard [#362].
  • Added a button to copy a prompt in the Custom Prompts page [#598].
  • -
  • [All APIs] Showing the spam filter info at the top of the message. The data is saved only for the session in which the message has been checked for spam [#506].
  • +
  • [All APIs] Showing the spam filter info at the top of the message. The data is saved only for the session in which the message has been checked for spam [#506, #658].
  • Fix: Now it's possibile to use multiple additional_text placeholders in a single prompt, also using custom placeholders [#554].
  • When using the additional_text placeholder is now possibile to specify an ID that will be shown in the form asking for the text [#525].
  • [ChatGPT Web] Added an option to define a custom time to wait for the page load. Sometimes, on slow PCs, the ChatGPT page loads slowly and ThunderAI inject its content too early. With this option you can adjust the waiting time [#634].
  • From c3f5e2be7e7cd904e2d1b1d06c3dcac77083d911 Mon Sep 17 00:00:00 2001 From: Andreas Pettersson Date: Tue, 17 Feb 2026 00:18:15 +0100 Subject: [PATCH 34/34] Translated using Weblate (Swedish) Currently translated at 100.0% (471 of 471 strings) Translation: ThunderAI for Thunderbird/main Translate-URL: https://hosted.weblate.org/projects/thunderai/main/sv/ --- _locales/sv/messages.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/_locales/sv/messages.json b/_locales/sv/messages.json index 0b4ea84a..f4066bc8 100644 --- a/_locales/sv/messages.json +++ b/_locales/sv/messages.json @@ -1418,5 +1418,8 @@ }, "prefs_OptionText_chatgpt_web_load_wait_time_info": { "message": "Tid i millisekunder att vänta på att ChatGPT-sidan ska laddas innan ytterligare innehåll laddas. Standardvärdet är 1000 ms. Om en anpassad GPT eller ett anpassat projekt definieras läggs ytterligare 1000 ms till detta värde." + }, + "spam_check_in_progress": { + "message": "Skräppostkontroll pågår..." } }