From 84a65acd8fab550f1ae9197abe10356689fde724 Mon Sep 17 00:00:00 2001 From: Guido Kraemer Date: Sat, 3 Jan 2026 11:49:37 +0100 Subject: [PATCH 001/205] resolve rebase conflicts --- _locales/en/messages.json | 16 ++++++++++++++++ js/mzta-utils.js | 2 ++ mzta-background.js | 18 ++++++++++++++++++ options/mzta-options-default.js | 2 ++ options/mzta-options.html | 13 ++++++++++++- 5 files changed, 50 insertions(+), 1 deletion(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 1e8b9cac..432b6f34 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1195,6 +1195,18 @@ "message": "Manage spam filter settings", "description": "" }, + "prefs_OptionText_summarize": { + "message": "Summarize mail", + "description": "" + }, + "prefs_OptionText_summarize_Info": { + "message": "If checked, adds an option to context menu to summarize mail.", + "description": "" + }, + "prefs_OptionText_btnManageSummarizeInfo": { + "message": "Manage summarize settings", + "description": "" + }, "SpamFilter_PageTitle": { "message": "Manage Spam Filter Settings", "description": "" @@ -1295,6 +1307,10 @@ "message": "Analyze for spam", "description": "" }, + "context_menu_mzta-summary": { + "message": "Summarize", + "description": "" + }, "prefs_OptionText_add_tags_context_menu": { "message": "Show the \"Add tags\" context menu item", "description": "" diff --git a/js/mzta-utils.js b/js/mzta-utils.js index 67ce0823..79216f9d 100644 --- a/js/mzta-utils.js +++ b/js/mzta-utils.js @@ -25,9 +25,11 @@ export const getMenuContextDisplay = () => 'message_display_action_menu'; export const contextMenuID_AddTags = 'mzta-add-tags'; export const contextMenuID_Spamfilter = 'mzta-spamfilter'; +export const contextMenuID_Summary = 'mzta-summary'; export const contextMenuIconsPath = { [contextMenuID_AddTags]: 'moz-extension:images/autotags.png', [contextMenuID_Spamfilter]: 'moz-extension:images/spamfilter.png', + // [contextMenuID_Summary]: 'moz-extension:images/summary.png', }; export function getLanguageDisplayName(languageCode) { diff --git a/mzta-background.js b/mzta-background.js index e2161b66..ce3d1d7b 100644 --- a/mzta-background.js +++ b/mzta-background.js @@ -45,6 +45,7 @@ import { extractJsonObject, contextMenuID_AddTags, contextMenuID_Spamfilter, + contextMenuID_Summary, contextMenuIconsPath, sanitizeChatGPTModelData, sanitizeChatGPTWebCustomData, @@ -802,10 +803,12 @@ async function reload_pref_init(){ add_tags_auto_force_existing: prefs_default.add_tags_auto_force_existing, add_tags_auto_only_inbox: prefs_default.add_tags_auto_only_inbox, spamfilter: prefs_default.spamfilter, + summarize: prefs_default.summarize, spamfilter_threshold: prefs_default.spamfilter_threshold, dynamic_menu_force_enter: prefs_default.dynamic_menu_force_enter, add_tags_context_menu: prefs_default.add_tags_context_menu, spamfilter_context_menu: prefs_default.spamfilter_context_menu, + summarize_context_menu: prefs_default.summarize_context_menu, ...getDynamicSettingsDefaults(['use_specific_integration', 'connection_type']) }); _process_incoming = prefs_init.add_tags_auto || prefs_init.spamfilter; @@ -917,6 +920,15 @@ function setupStorageChangeListener() { removeContextMenu(contextMenuID_Spamfilter); } } + if (changes.summarize) { + if (changes.summarize.newValue){ + if (prefs_init.summarize_context_menu){ + addContextMenu(contextMenuID_Summary); + } + } else { + removeContextMenu(contextMenuID_Summary); + } + } reload_pref_init(); } }); @@ -977,6 +989,12 @@ function addContextMenuItems() { if(prefs_init.spamfilter && prefs_init.spamfilter_context_menu && checkAPIIntegration(prefs_init.connection_type, prefs_init.spamfilter_use_specific_integration,prefs_init.spamfilter_connection_type)){ addContextMenu(contextMenuID_Spamfilter); } + + // Add Context menu: Summary + if(prefs_init.summarize && prefs_init.summarize_context_menu && checkAPIIntegration(prefs_init.connection_type && prefs_init.summarize_use_specific_integration, prefs_init.summarize_connection_type)) { + console.log("adding summary to context menu") + addContextMenu(contextMenuID_Summary); + } } addContextMenuItems(); diff --git a/options/mzta-options-default.js b/options/mzta-options-default.js index 66b24890..e14b2808 100644 --- a/options/mzta-options-default.js +++ b/options/mzta-options-default.js @@ -133,5 +133,7 @@ export const prefs_default = { spamfilter_threshold: 70, spamfilter_context_menu: true, spamfilter_enabled_accounts: [], + summarize: false, + summarize_context_menu: true, ...generated_prefs } diff --git a/options/mzta-options.html b/options/mzta-options.html index 0e24f172..99078369 100644 --- a/options/mzta-options.html +++ b/options/mzta-options.html @@ -147,6 +147,17 @@ + + __MSG_prefs_OptionText_summarize__ +
+ + + + " __MSG_prefs_OptionText_get_calendar_event__
@@ -164,7 +175,7 @@ + From 050a3fd1f75e794cc885a0398b0c6d77f2de7b15 Mon Sep 17 00:00:00 2001 From: Guido Kraemer Date: Fri, 19 Dec 2025 10:00:10 +0100 Subject: [PATCH 002/205] Add MZTA summary page --- pages/summary/mzta-summary.css | 0 pages/summary/mzta-summary.html | 0 pages/summary/mzta-summary.js | 0 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 pages/summary/mzta-summary.css create mode 100644 pages/summary/mzta-summary.html create mode 100644 pages/summary/mzta-summary.js diff --git a/pages/summary/mzta-summary.css b/pages/summary/mzta-summary.css new file mode 100644 index 00000000..e69de29b diff --git a/pages/summary/mzta-summary.html b/pages/summary/mzta-summary.html new file mode 100644 index 00000000..e69de29b diff --git a/pages/summary/mzta-summary.js b/pages/summary/mzta-summary.js new file mode 100644 index 00000000..e69de29b From 4ab6da2faf08ad0e1e4e357ccc306e7724bff2c6 Mon Sep 17 00:00:00 2001 From: Guido Kraemer Date: Fri, 19 Dec 2025 13:09:25 +0100 Subject: [PATCH 003/205] change all "summary" -> "summarize" --- _locales/en/messages.json | 22 ++++- js/mzta-utils.js | 4 +- mzta-background.js | 8 +- options/mzta-options.js | 4 + pages/summarize/mzta-summarize.css | 123 ++++++++++++++++++++++++++++ pages/summarize/mzta-summarize.html | 30 +++++++ pages/summarize/mzta-summarize.js | 77 +++++++++++++++++ pages/summary/mzta-summary.css | 0 pages/summary/mzta-summary.html | 0 pages/summary/mzta-summary.js | 0 10 files changed, 261 insertions(+), 7 deletions(-) create mode 100644 pages/summarize/mzta-summarize.css create mode 100644 pages/summarize/mzta-summarize.html create mode 100644 pages/summarize/mzta-summarize.js delete mode 100644 pages/summary/mzta-summary.css delete mode 100644 pages/summary/mzta-summary.html delete mode 100644 pages/summary/mzta-summary.js diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 432b6f34..c98079bd 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1003,6 +1003,14 @@ "message": "Extract all relevant details required to generate a calendar event from the following text. The extracted information should include:\n- Event Title\n- Start Date and Time (including timezone, if specified)\n- End Date and Time (including timezone, if specified)\n- Full day (if mentioned)\n- Attendees\nEnsure the data is formatted clearly and consistently so that it can be directly used for creating a calendar event.\nIf there are relative time references, consider that the date and time of the email are \"{%mail_datetime%}\". Calculate the start date and time based on this reference. If the calculated start date and time are earlier than \"{%current_datetime%}\", recalculate the start date and time using \"{%current_datetime%}\" as the base.\nIf the duration is not specified, set it to one hour.\nThese are the attendees: {%author%}, {%recipients%}, {%cc_list%}. If present, exclude my address: {%account_email_address%}.\nIf you're not able to get one or more of the required information, please respond with an empty string.\nGenerate a response in JSON format only. Do not include any additional text or explanations; provide only the JSON. Here is the format to be used:\n{\n\"startDate\": \"YYYYMMDDTHHMMSS\",\n\"endDate\": \"YYYYMMDDTHHMMSS\",\n\"summary\": \"Calendar event summary here\",\n\"forceAllDay\": false,\n\"attendees\": [attendee1@example.com,attendee2@example.com,attendee3@example.com]\n}\nHere's the text:\"{%selected_text%}\"", "description": "" }, + "prompt_summarize": { + "message": "Summarize this email or these emails", + "description": "" + }, + "prompt_summarize_full_text": { + "message": "Summarize the following email or emails into a bullet point list. If there are multiple emails, consider the entire thread for the summary.", + "description": "" + }, "prompt_get_task": { "message": "Add a new task", "description": "" @@ -1231,6 +1239,18 @@ "message": "Spam Filter Options", "description": "" }, + "Summarize_PageTitle": { + "message": "Manage Summarize Settings", + "description": "" + }, + "Summarize_info_default": { + "message": "In this page you can modify the default prompt used to summarize emails.", + "description": "" + }, + "Summarize_prompt_text_title": { + "message": "Current prompt text", + "description": "" + }, "prefs_OptionText_use_specific_integration": { "message": "Use specific Model and API", "description": "" @@ -1307,7 +1327,7 @@ "message": "Analyze for spam", "description": "" }, - "context_menu_mzta-summary": { + "context_menu_mzta-summarize": { "message": "Summarize", "description": "" }, diff --git a/js/mzta-utils.js b/js/mzta-utils.js index 79216f9d..eb4f8d49 100644 --- a/js/mzta-utils.js +++ b/js/mzta-utils.js @@ -25,11 +25,11 @@ export const getMenuContextDisplay = () => 'message_display_action_menu'; export const contextMenuID_AddTags = 'mzta-add-tags'; export const contextMenuID_Spamfilter = 'mzta-spamfilter'; -export const contextMenuID_Summary = 'mzta-summary'; +export const contextMenuID_Summarize = 'mzta-summarize'; export const contextMenuIconsPath = { [contextMenuID_AddTags]: 'moz-extension:images/autotags.png', [contextMenuID_Spamfilter]: 'moz-extension:images/spamfilter.png', - // [contextMenuID_Summary]: 'moz-extension:images/summary.png', + // [contextMenuID_Summarize]: 'moz-extension:images/summarize.png', }; export function getLanguageDisplayName(languageCode) { diff --git a/mzta-background.js b/mzta-background.js index ce3d1d7b..5ceb483e 100644 --- a/mzta-background.js +++ b/mzta-background.js @@ -45,7 +45,7 @@ import { extractJsonObject, contextMenuID_AddTags, contextMenuID_Spamfilter, - contextMenuID_Summary, + contextMenuID_Summarize, contextMenuIconsPath, sanitizeChatGPTModelData, sanitizeChatGPTWebCustomData, @@ -990,10 +990,10 @@ function addContextMenuItems() { addContextMenu(contextMenuID_Spamfilter); } - // Add Context menu: Summary + // Add Context menu: Summarize if(prefs_init.summarize && prefs_init.summarize_context_menu && checkAPIIntegration(prefs_init.connection_type && prefs_init.summarize_use_specific_integration, prefs_init.summarize_connection_type)) { - console.log("adding summary to context menu") - addContextMenu(contextMenuID_Summary); + console.log("adding summarize to context menu") + addContextMenu(contextMenuID_Summariz); } } diff --git a/options/mzta-options.js b/options/mzta-options.js index 16b130fd..3b2ac351 100644 --- a/options/mzta-options.js +++ b/options/mzta-options.js @@ -291,6 +291,10 @@ document.addEventListener('DOMContentLoaded', async () => { document.getElementById('btnManageSpamFilterInfo').addEventListener('click', () => { openTab('/pages/spamfilter/mzta-spamfilter.html'); }); + + document.getElementById('btnManageSummarizeInfo').addEventListener('click', () => { + openTab('/pages/summarize/mzta-summarize.html'); + }); document.getElementById('btnManageCalendarEventInfo').addEventListener('click', () => { openTab('/pages/get-calendar-event/mzta-get-calendar-event.html'); diff --git a/pages/summarize/mzta-summarize.css b/pages/summarize/mzta-summarize.css new file mode 100644 index 00000000..173353e7 --- /dev/null +++ b/pages/summarize/mzta-summarize.css @@ -0,0 +1,123 @@ +#summarize_prompt_container { + width: 90%; + margin: 20px auto; +} + +#summarize_prompt_text { + width: 100%; +} + +.infoline { + font-size: 0.8em; + font-style: italic; +} + +.btn_div { + width: 100%; + display: flex; + justify-content: space-between; +} + +table#miczPrefs { + padding: 10px; + border-spacing: 0px; + width: 90%; + margin: 0px auto 40px auto; + border: 1px solid #ccc; +} + +.section_title { + font-weight: bold; +} + +table#miczPrefs td { + border-top: 1px solid #ccc; + border-bottom: 1px solid #ccc; + vertical-align: top; + padding: 2px; +} + +table#miczPrefs tr:first-child td { + border-top: none; +} + +table#miczPrefs tr:last-child td { + border-bottom: none; +} + +.autocomplete-container { + position: relative; +} + +.autocomplete-list { + position: absolute; + top: 100%; + left: 0; + right: 0; + background-color: white; + border: 1px solid #ccc; + z-index: 1000; + max-height: 200px; + overflow-y: auto; + padding: 0; + margin: 0; + list-style: none; + font-size: small; +} + +.autocomplete-list li { + padding: 8px; + cursor: pointer; +} + +.autocomplete-list li:hover { + background-color: #f0f0f0; +} + +.autocomplete-list li.active { + background-color: #ddd; +} + +.hidden { + display: none; +} + +.unsaved { + color: red; +} + +@media (prefers-color-scheme: dark) { + body { + background-color: #1c1b22; + color: rgb(251, 251, 254); + } + + a:link { + color: #409eff; + } + a:visited { + color: #409eff; + } + a:hover { + color: #66b1ff; + } + a:active { + color: #66b1ff; + } + a:active { + color: #66b1ff; + } + + .autocomplete-list { + background-color: #2e2f36; + border: 1px solid #2e2f36; + } + + .autocomplete-list li:hover { + background-color: #4c4e58; + } + + .autocomplete-list li.active { + background-color: #4c4e58; + } +} diff --git a/pages/summarize/mzta-summarize.html b/pages/summarize/mzta-summarize.html new file mode 100644 index 00000000..7433ec82 --- /dev/null +++ b/pages/summarize/mzta-summarize.html @@ -0,0 +1,30 @@ + + + + + ThunderAI - __MSG_Summarize_PageTitle__ + + + + +
+

__MSG_Summarize_PageTitle__

+

__MSG_Summarize_info_default__

+
+
+ __MSG_Summarize_prompt_text_title__ + +
__MSG_prefs_OptionText_btnManagePrompts_infoline__ __MSG_more_info_string__ +
__MSG_prefs_OptionText_AdvancedPromptResponse_infoline2__
+
+
+ + +
+
+
+
+ + + + diff --git a/pages/summarize/mzta-summarize.js b/pages/summarize/mzta-summarize.js new file mode 100644 index 00000000..646cb096 --- /dev/null +++ b/pages/summarize/mzta-summarize.js @@ -0,0 +1,77 @@ +/* + * 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 . + */ + +import { prefs_default } from "../../options/mzta-options-default.js"; +import { taLogger } from "../../js/mzta-logger.js"; +import { getSpecialPrompts, setSpecialPrompts } from "../../js/mzta-prompts.js"; +import { getPlaceholders } from "../../js/mzta-placeholders.js"; +import { textareaAutocomplete } from "../../js/mzta-placeholders-autocomplete.js"; +import { isAPIKeyValue } from "../../js/mzta-utils.js"; + +let autocompleteSuggestions = []; +let taLog = new taLogger("mzta-summarize-page", true); + +document.addEventListener("DOMContentLoaded", async () => { + + i18n.updateDocument(); + + + const summarize_textarea = document.getElementById("summarize_prompt_text"); + const summarize_save_btn = document.getElementById("btn_save_prompt"); + const summarize_reset_btn = document.getElementById("btn_reset_prompt"); + + let specialPrompts = await getSpecialPrompts(); + let summarize_prompt = specialPrompts.find((prompt) => prompt.id === 'prompt_summarize'); + + summarize_textarea.addEventListener("input", (event) => { + summarize_reset_btn.disabled = (event.target.value === browser.i18n.getMessage('prompt_summarize_full_text')); + summarize_save_btn.disabled = (event.target.value === summarize_prompt.text); + if (summarize_save_btn.disabled) { + document.getElementById("summarize_prompt_unsaved").classList.add("hidden"); + } else { + document.getElementById("summarize_prompt_unsaved").classList.remove("hidden"); + } + }); + + summarize_reset_btn.addEventListener("click", () => { + summarize_textarea.value = browser.i18n.getMessage("prompt_summarize_full_text"); + summarize_reset_btn.disabled = true; + let event = new Event("input", { bubbles: true, cancelable: true }); + summarize_textarea.dispatchEvent(event); + }); + + summarize_save_btn.addEventListener("click", () => { + specialPrompts.find(prompt => prompt.id === 'prompt_summarize') + setSpecialPrompts(specialPrompts); + summarize_save_btn.disabled = true; + document.getElementById("summarize_prompt_unsaved").classList.add("hidden"); + browser.runtime.sendMessage({ command: "reload_menus" }); + }); + + if(summarize_prompt.text === 'prompt_summarize_full_text'){ + summarize_prompt.text = browser.i18n.getMessage(summarize_prompt.text); + } + summarize_textarea.value = summarize_prompt.text; + summarize_reset_btn.disabled = (summarize_textarea.value === browser.i18n.getMessage("prompt_summarize_full_text")); + + autocompleteSuggestions = (await getPlaceholders(true)) + .filter((p) => p.id !== "additional_text") + .map((p) => ({ command: `{%${p.id}%}`, type: p.type })); + textareaAutocomplete(summarize_textarea, autocompleteSuggestions, 1); +}); + diff --git a/pages/summary/mzta-summary.css b/pages/summary/mzta-summary.css deleted file mode 100644 index e69de29b..00000000 diff --git a/pages/summary/mzta-summary.html b/pages/summary/mzta-summary.html deleted file mode 100644 index e69de29b..00000000 diff --git a/pages/summary/mzta-summary.js b/pages/summary/mzta-summary.js deleted file mode 100644 index e69de29b..00000000 From 02787e2f25584c736b93efbbfeac58b753aa1197 Mon Sep 17 00:00:00 2001 From: Guido Kraemer Date: Fri, 19 Dec 2025 17:41:36 +0100 Subject: [PATCH 004/205] Fix context menu ID typos --- mzta-background.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mzta-background.js b/mzta-background.js index 5ceb483e..4ef33580 100644 --- a/mzta-background.js +++ b/mzta-background.js @@ -923,10 +923,10 @@ function setupStorageChangeListener() { if (changes.summarize) { if (changes.summarize.newValue){ if (prefs_init.summarize_context_menu){ - addContextMenu(contextMenuID_Summary); + addContextMenu(contextMenuID_Summarize); } } else { - removeContextMenu(contextMenuID_Summary); + removeContextMenu(contextMenuID_Summarize); } } reload_pref_init(); @@ -993,7 +993,7 @@ function addContextMenuItems() { // Add Context menu: Summarize if(prefs_init.summarize && prefs_init.summarize_context_menu && checkAPIIntegration(prefs_init.connection_type && prefs_init.summarize_use_specific_integration, prefs_init.summarize_connection_type)) { console.log("adding summarize to context menu") - addContextMenu(contextMenuID_Summariz); + addContextMenu(contextMenuID_Summarize); } } From 8abb7e349bdf53db81452307a0a6e4eee8b15f8b Mon Sep 17 00:00:00 2001 From: Guido Kraemer Date: Fri, 19 Dec 2025 17:50:31 +0100 Subject: [PATCH 005/205] Add summarize prompt to specialPrompts --- js/mzta-prompts.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/js/mzta-prompts.js b/js/mzta-prompts.js index b598cbce..4ea5d552 100644 --- a/js/mzta-prompts.js +++ b/js/mzta-prompts.js @@ -324,6 +324,22 @@ const specialPrompts = [ is_default: "1", is_special: "1", }, + { + id: 'prompt_summarize', + name: "__MSG_prompt_summarize__", + text: "prompt_summarize_full_text", + type: "1", + action: "0", + need_selected: "0", + need_signature: "0", + need_custom_text: "0", + define_response_lang: "0", + use_diff_viewer: "0", + api_type: '', + api_model: '', + is_default: "1", + is_special: "1", + } ]; From 2c07aa6336cb9aa58cd7dcda78210f8c236f47a6 Mon Sep 17 00:00:00 2001 From: Guido Kraemer Date: Fri, 19 Dec 2025 17:51:24 +0100 Subject: [PATCH 006/205] Remove unused imports from mzta-summarize.js --- pages/summarize/mzta-summarize.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pages/summarize/mzta-summarize.js b/pages/summarize/mzta-summarize.js index 646cb096..4eb831c0 100644 --- a/pages/summarize/mzta-summarize.js +++ b/pages/summarize/mzta-summarize.js @@ -16,12 +16,10 @@ * along with this program. If not, see . */ -import { prefs_default } from "../../options/mzta-options-default.js"; import { taLogger } from "../../js/mzta-logger.js"; import { getSpecialPrompts, setSpecialPrompts } from "../../js/mzta-prompts.js"; import { getPlaceholders } from "../../js/mzta-placeholders.js"; import { textareaAutocomplete } from "../../js/mzta-placeholders-autocomplete.js"; -import { isAPIKeyValue } from "../../js/mzta-utils.js"; let autocompleteSuggestions = []; let taLog = new taLogger("mzta-summarize-page", true); @@ -37,7 +35,7 @@ document.addEventListener("DOMContentLoaded", async () => { let specialPrompts = await getSpecialPrompts(); let summarize_prompt = specialPrompts.find((prompt) => prompt.id === 'prompt_summarize'); - + summarize_textarea.addEventListener("input", (event) => { summarize_reset_btn.disabled = (event.target.value === browser.i18n.getMessage('prompt_summarize_full_text')); summarize_save_btn.disabled = (event.target.value === summarize_prompt.text); From 7eb9a2cf56e685e6dbbafb79d4f4651cfea438ef Mon Sep 17 00:00:00 2001 From: Guido Kraemer Date: Fri, 19 Dec 2025 18:06:45 +0100 Subject: [PATCH 007/205] fixes saving of summarize prompt --- pages/summarize/mzta-summarize.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/summarize/mzta-summarize.js b/pages/summarize/mzta-summarize.js index 4eb831c0..93bb67e8 100644 --- a/pages/summarize/mzta-summarize.js +++ b/pages/summarize/mzta-summarize.js @@ -54,7 +54,7 @@ document.addEventListener("DOMContentLoaded", async () => { }); summarize_save_btn.addEventListener("click", () => { - specialPrompts.find(prompt => prompt.id === 'prompt_summarize') + specialPrompts.find(prompt => prompt.id === 'prompt_summarize').text = summarize_textarea.value; setSpecialPrompts(specialPrompts); summarize_save_btn.disabled = true; document.getElementById("summarize_prompt_unsaved").classList.add("hidden"); From fe427c29eec5c1e55a39562428b678136e259916 Mon Sep 17 00:00:00 2001 From: Guido Kraemer Date: Tue, 23 Dec 2025 13:22:47 +0100 Subject: [PATCH 008/205] Add Email Template Editor to Summarize Panel --- _locales/en/messages.json | 10 +++++- js/mzta-prompts.js | 16 +++++++++ pages/summarize/mzta-summarize.css | 4 +++ pages/summarize/mzta-summarize.html | 51 ++++++++++++++++++++++++++--- pages/summarize/mzta-summarize.js | 42 +++++++++++++++++++----- 5 files changed, 108 insertions(+), 15 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index c98079bd..c7ceded5 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1008,7 +1008,11 @@ "description": "" }, "prompt_summarize_full_text": { - "message": "Summarize the following email or emails into a bullet point list. If there are multiple emails, consider the entire thread for the summary.", + "message": "Summarize the following email or emails into a bullet point list. If there are multiple emails, consider the entire thread for the summary.\n------------------\n\n", + "description": "" + }, + "prompt_summarize_email_template": { + "message": "From: {%author%}\nTo: {%recipients%}\nCC: {%cc_list%}\nSubject: {%mail_subject%}\nDate: {%mail_datetime%}\nAttachments:\n{%mail_attachments_info%}\nBody:\n{%mail_text_body%}\n---------------\n\n", "description": "" }, "prompt_get_task": { @@ -1075,6 +1079,10 @@ "message": "You can change the prompt as you wish, but the response received from the AI must be in JSON format as specified in the default prompt!", "description": "" }, + "prefs_OptionText_Summarize_infoline2": { + "message": "You can change the prompt as you wish, the first field is the main prompt, the second field is the template for a single mail. The list of emails will be appended to the main prompt.", + "description": "" + }, "prefs_OptionText_get_calendar_event_Sparks_not_present": { "message": "To use the calendar event and task features, please install the ThunderAI Sparks addon.", "description": "" diff --git a/js/mzta-prompts.js b/js/mzta-prompts.js index 4ea5d552..f68a3997 100644 --- a/js/mzta-prompts.js +++ b/js/mzta-prompts.js @@ -339,6 +339,22 @@ const specialPrompts = [ api_model: '', is_default: "1", is_special: "1", + }, + { + id: 'prompt_summarize_email_template', + name: "__MSG_prompt_summarize_email_template__", + text: "prompt_summarize_email_template_full_text", + type: "1", + action: "0", + need_selected: "0", + need_signature: "0", + need_custom_text: "0", + define_response_lang: "0", + use_diff_viewer: "0", + api_type: '', + api_model: '', + is_default: "1", + is_special: "1", } ]; diff --git a/pages/summarize/mzta-summarize.css b/pages/summarize/mzta-summarize.css index 173353e7..f4bbc091 100644 --- a/pages/summarize/mzta-summarize.css +++ b/pages/summarize/mzta-summarize.css @@ -3,6 +3,10 @@ margin: 20px auto; } +#summarize_email_template_text { + width: 100%; +} + #summarize_prompt_text { width: 100%; } diff --git a/pages/summarize/mzta-summarize.html b/pages/summarize/mzta-summarize.html index 7433ec82..54405840 100644 --- a/pages/summarize/mzta-summarize.html +++ b/pages/summarize/mzta-summarize.html @@ -12,17 +12,58 @@

__MSG_Summarize_info_default__

- __MSG_Summarize_prompt_text_title__ - -
__MSG_prefs_OptionText_btnManagePrompts_infoline__ __MSG_more_info_string__ -
__MSG_prefs_OptionText_AdvancedPromptResponse_infoline2__
+ + + __MSG_Summarize_prompt_text_title__ + +
+ + + __MSG_prefs_OptionText_btnManagePrompts_infoline__ + + __MSG_more_info_string__ + + +
+ + __MSG_prefs_OptionText_Summarize_infoline2__ +
+ +
+ +
+
-
+
+ + +
+ + +
+ + +
+ +
+
+ + +
+
diff --git a/pages/summarize/mzta-summarize.js b/pages/summarize/mzta-summarize.js index 93bb67e8..45f7463f 100644 --- a/pages/summarize/mzta-summarize.js +++ b/pages/summarize/mzta-summarize.js @@ -32,41 +32,65 @@ document.addEventListener("DOMContentLoaded", async () => { const summarize_textarea = document.getElementById("summarize_prompt_text"); const summarize_save_btn = document.getElementById("btn_save_prompt"); const summarize_reset_btn = document.getElementById("btn_reset_prompt"); + const summarize_textarea_email_template = document.getElementById("summarize_email_template_text"); + const summarize_reset_email_template_btn = document.getElementById("btn_reset_email_template"); + const summarize_save_email_template_btn = document.getElementById("btn_save_email_template"); let specialPrompts = await getSpecialPrompts(); let summarize_prompt = specialPrompts.find((prompt) => prompt.id === 'prompt_summarize'); + let summarize_email_template = specialPrompts.find((prompt) => prompt.id === 'prompt_summarize_email_template'); summarize_textarea.addEventListener("input", (event) => { summarize_reset_btn.disabled = (event.target.value === browser.i18n.getMessage('prompt_summarize_full_text')); summarize_save_btn.disabled = (event.target.value === summarize_prompt.text); - if (summarize_save_btn.disabled) { - document.getElementById("summarize_prompt_unsaved").classList.add("hidden"); - } else { - document.getElementById("summarize_prompt_unsaved").classList.remove("hidden"); - } }); - + + summarize_textarea_email_template.addEventListener("input", (event) => { + summarize_reset_email_template_btn.disabled = (event.target.value === browser.i18n.getMessage('prompt_summarize_email_template')); + summarize_save_email_template_btn.disabled = (event.target.value === summarize_email_template.text); + }); + + summarize_reset_email_template_btn.addEventListener("click", () => { + summarize_textarea_email_template.value = browser.i18n.getMessage("prompt_summarize_email_template"); + summarize_reset_email_template_btn.disabled = true; + let event = new Event("input", { bubbles: true, cancelable: true }); + summarize_textarea_email_template.dispatchEvent(event); + }); + summarize_reset_btn.addEventListener("click", () => { summarize_textarea.value = browser.i18n.getMessage("prompt_summarize_full_text"); summarize_reset_btn.disabled = true; let event = new Event("input", { bubbles: true, cancelable: true }); summarize_textarea.dispatchEvent(event); }); - + + summarize_save_email_template_btn.addEventListener("click", () => { + specialPrompts.find(prompt => prompt.id === 'prompt_summarize_email_template').text = summarize_textarea_email_template.value; + setSpecialPrompts(specialPrompts); + summarize_save_email_template_btn.disabled = true; + browser.runtime.sendMessage({ command: "reload_menus" }); + }); + summarize_save_btn.addEventListener("click", () => { specialPrompts.find(prompt => prompt.id === 'prompt_summarize').text = summarize_textarea.value; setSpecialPrompts(specialPrompts); summarize_save_btn.disabled = true; - document.getElementById("summarize_prompt_unsaved").classList.add("hidden"); browser.runtime.sendMessage({ command: "reload_menus" }); }); if(summarize_prompt.text === 'prompt_summarize_full_text'){ summarize_prompt.text = browser.i18n.getMessage(summarize_prompt.text); } + if(summarize_email_template === 'prompt_summarize_email_template'){ + summarize_email_template.text = browser.i18n.getMessage(summarize_email_template.text); + } + + summarize_textarea_email_template.value = summarize_email_template.text; + summarize_reset_email_template_btn.disabled = (summarize_email_template.value === browser.i18n.getMessage("prompt_summarize_email_template")); + summarize_textarea.value = summarize_prompt.text; summarize_reset_btn.disabled = (summarize_textarea.value === browser.i18n.getMessage("prompt_summarize_full_text")); - + autocompleteSuggestions = (await getPlaceholders(true)) .filter((p) => p.id !== "additional_text") .map((p) => ({ command: `{%${p.id}%}`, type: p.type })); From f29d8337adeaca64b9df4ed8b76c0ea347a6ad97 Mon Sep 17 00:00:00 2001 From: Guido Kraemer Date: Tue, 23 Dec 2025 13:23:09 +0100 Subject: [PATCH 009/205] WIP: Add Summarize context menu action and handler --- mzta-background.js | 53 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/mzta-background.js b/mzta-background.js index 4ef33580..8d1659ab 100644 --- a/mzta-background.js +++ b/mzta-background.js @@ -1003,17 +1003,70 @@ addContextMenuItems(); browser.menus.onClicked.addListener( (info, tab) => { let _add_tags = false let _spamfilter = false + let _summarize = false; if(info.menuItemId === contextMenuID_AddTags){ _add_tags = true; } if(info.menuItemId === contextMenuID_Spamfilter){ _spamfilter = true; } + if(info.menuItemId === contextMenuID_Summarize) { + _summarize = true; + } if(_add_tags || _spamfilter){ processEmails(getMessages(info.selectedMessages), _add_tags, _spamfilter); } + if(_summarize) { + summarizeEmails(getMessages(info.selectedMessages)); + } }); +async function summarizeEmails(messages) { + taWorkingStatus.startWorking(); + // generate the special prompt for summarization + + // - make a long string of all the emails + let emails_content = ''; + for await (let message of messages) { +////// TODO: make this customizable through placeholders! +// plan: make a second option field where placeholders can be used and +// simply add the result from that tepmlate to the end of the prompt option +// field + emails_content += "\n\n---\n\n"; + let curr_fullMessage = await browser.messages.getFull(message.id); + + // from + curr_fullMessage + + // to + // cc + // bcc + // subject + // date + // attachment info + + // body + let msg_text = getMailBody(curr_fullMessage) + let 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(); + }; + emails_content += body_text; + + }; + emails_content += "\n\n---\n\n"; + + // - join prompt and emails + + taWorkingStatus.stopWorking(); + // send to LLM + + console.log(emails_content); + +} + // Listening for new received emails const newEmailListener = (folder, messagesList) => { From 7595c70a3834fc97a0139f30026f8d03bbeddaee Mon Sep 17 00:00:00 2001 From: Guido Kraemer Date: Fri, 2 Jan 2026 20:39:35 +0100 Subject: [PATCH 010/205] Implement summarization internals --- mzta-background.js | 92 ++++++++++++++++++++++++++-------------------- 1 file changed, 52 insertions(+), 40 deletions(-) diff --git a/mzta-background.js b/mzta-background.js index 8d1659ab..5b92c76c 100644 --- a/mzta-background.js +++ b/mzta-background.js @@ -58,7 +58,10 @@ import { } from './js/mzta-utils.js'; import { taPromptUtils } from './js/mzta-utils-prompt.js'; import { mzta_specialCommand } from './js/mzta-special-commands.js'; -import { getSpamFilterPrompt } from './js/mzta-prompts.js'; +import { + getSpamFilterPrompt, + getSpecialPrompts +} from './js/mzta-prompts.js'; import { taSpamReport } from './js/mzta-spamreport.js'; import { taWorkingStatus } from './js/mzta-working-status.js'; import { addTags_getExclusionList, checkExcludedTag } from './js/mzta-addatags-exclusion-list.js'; @@ -1017,54 +1020,63 @@ browser.menus.onClicked.addListener( (info, tab) => { processEmails(getMessages(info.selectedMessages), _add_tags, _spamfilter); } if(_summarize) { + // info.selectedMessages is of type MessageList summarizeEmails(getMessages(info.selectedMessages)); } }); async function summarizeEmails(messages) { taWorkingStatus.startWorking(); - // generate the special prompt for summarization - - // - make a long string of all the emails - let emails_content = ''; - for await (let message of messages) { -////// TODO: make this customizable through placeholders! -// plan: make a second option field where placeholders can be used and -// simply add the result from that tepmlate to the end of the prompt option -// field - emails_content += "\n\n---\n\n"; - let curr_fullMessage = await browser.messages.getFull(message.id); - - // from - curr_fullMessage - - // to - // cc - // bcc - // subject - // date - // attachment info - - // body - let msg_text = getMailBody(curr_fullMessage) - let 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(); - }; - emails_content += body_text; - - }; - emails_content += "\n\n---\n\n"; - // - join prompt and emails + // we have two prompts, the actual assignment for the LLM and the email + // template 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 tabs = await browser.tabs.query({ active: true, currentWindow: true }); + const chatgpt_lang = await taPromptUtils.getDefaultLang(prompt); + + // assemble all email messages into one string and add the assignment prompt + let messages_list = []; + for await (let curr_message of messages) { + + // extract body of current message as text + let curr_message_full = await browser.messages.getFull(curr_message.id); + let 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, + })); + }; + let messages_string = messages_list.join("\n\n--- Next Email ---\n\n"); + + let full_prompt = prompt.text + "\n\n\n" + 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(); - // send to LLM - - console.log(emails_content); - + return {ok : '1'}; } From c3e27f03564703ce5344faa6c3ec9fe32932bc30 Mon Sep 17 00:00:00 2001 From: Guido Kraemer Date: Fri, 2 Jan 2026 21:42:44 +0100 Subject: [PATCH 011/205] Fix malformed label closing tag in options page --- options/mzta-options.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/options/mzta-options.html b/options/mzta-options.html index 99078369..02b0f6ea 100644 --- a/options/mzta-options.html +++ b/options/mzta-options.html @@ -175,7 +175,7 @@ From 3d2c2b93208771187f6ad93882a5efe477f076d5 Mon Sep 17 00:00:00 2001 From: Guido Kraemer Date: Sat, 3 Jan 2026 22:18:00 +0100 Subject: [PATCH 012/205] add customizable model options for summarize --- _locales/en/messages.json | 8 ++ pages/summarize/mzta-summarize.css | 92 +++++++++++--- pages/summarize/mzta-summarize.html | 24 +++- pages/summarize/mzta-summarize.js | 181 +++++++++++++++++++++++++--- 4 files changed, 270 insertions(+), 35 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index c7ceded5..a7a0102d 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1003,6 +1003,10 @@ "message": "Extract all relevant details required to generate a calendar event from the following text. The extracted information should include:\n- Event Title\n- Start Date and Time (including timezone, if specified)\n- End Date and Time (including timezone, if specified)\n- Full day (if mentioned)\n- Attendees\nEnsure the data is formatted clearly and consistently so that it can be directly used for creating a calendar event.\nIf there are relative time references, consider that the date and time of the email are \"{%mail_datetime%}\". Calculate the start date and time based on this reference. If the calculated start date and time are earlier than \"{%current_datetime%}\", recalculate the start date and time using \"{%current_datetime%}\" as the base.\nIf the duration is not specified, set it to one hour.\nThese are the attendees: {%author%}, {%recipients%}, {%cc_list%}. If present, exclude my address: {%account_email_address%}.\nIf you're not able to get one or more of the required information, please respond with an empty string.\nGenerate a response in JSON format only. Do not include any additional text or explanations; provide only the JSON. Here is the format to be used:\n{\n\"startDate\": \"YYYYMMDDTHHMMSS\",\n\"endDate\": \"YYYYMMDDTHHMMSS\",\n\"summary\": \"Calendar event summary here\",\n\"forceAllDay\": false,\n\"attendees\": [attendee1@example.com,attendee2@example.com,attendee3@example.com]\n}\nHere's the text:\"{%selected_text%}\"", "description": "" }, + "Summarize_prompt_prefs_title": { + "message": "Add Summarization Options", + "description": "" + }, "prompt_summarize": { "message": "Summarize this email or these emails", "description": "" @@ -1215,6 +1219,10 @@ "message": "Summarize mail", "description": "" }, + "prefs_OptionText_summarize_use_specific_integration_Info": { + "message": "If checked, the Model and API specified below will be used for summarizing email(s), regardless the one choosen in the ThunderAI options page.", + "description": "" + }, "prefs_OptionText_summarize_Info": { "message": "If checked, adds an option to context menu to summarize mail.", "description": "" diff --git a/pages/summarize/mzta-summarize.css b/pages/summarize/mzta-summarize.css index f4bbc091..26b08e10 100644 --- a/pages/summarize/mzta-summarize.css +++ b/pages/summarize/mzta-summarize.css @@ -16,6 +16,49 @@ font-style: italic; } +#account_selector_container{ + display: none; +} + +#account_selector_checkboxes{ + padding: 10px; + border: 1px solid gray; + width: 24em; + margin-bottom: 10px; + margin-top: 10px; +} + +.specific_integration{ + background-color: #dfeaff; +} + +table { + border-collapse: separate; + border-spacing: 0; +} + +.group td { + border-top: none; + border-bottom: none; +} + +.specific_integration_sub td:first-child { + border-left: 10px solid #dfeaff; +} + +.specific_integration_sub td:last-child { + border-right: 10px solid #dfeaff; +} + +#connection_ui_end td{ + height: 6px; + background-color: #dfeaff; +} + +#connection_ui_end{ + display: none; +} + .btn_div { width: 100%; display: flex; @@ -30,6 +73,11 @@ table#miczPrefs { border: 1px solid #ccc; } +.addtags_table_title{ + width: 90%; + margin: auto; +} + .section_title { font-weight: bold; } @@ -90,27 +138,21 @@ table#miczPrefs tr:last-child td { color: red; } +label:has(input[type="checkbox"]) { + cursor: pointer; +} + @media (prefers-color-scheme: dark) { body { background-color: #1c1b22; color: rgb(251, 251, 254); } - a:link { - color: #409eff; - } - a:visited { - color: #409eff; - } - a:hover { - color: #66b1ff; - } - a:active { - color: #66b1ff; - } - a:active { - color: #66b1ff; - } + a:link { color: #409eff; } + a:visited { color: #409eff; } + a:hover { color: #66b1ff; } + a:active { color: #66b1ff; } + a:active { color: #66b1ff; } .autocomplete-list { background-color: #2e2f36; @@ -124,4 +166,24 @@ table#miczPrefs tr:last-child td { .autocomplete-list li.active { background-color: #4c4e58; } + + .summarize_auto{ + background-color: #415c35; + } + + .specific_integration{ + background-color: #2E3A4F; + } + + .specific_integration_sub td:first-child { + border-left: 10px solid #2E3A4F; + } + + .specific_integration_sub td:last-child { + border-right: 10px solid #2E3A4F; + } + + #connection_ui_end td{ + background-color: #2E3A4F; + } } diff --git a/pages/summarize/mzta-summarize.html b/pages/summarize/mzta-summarize.html index 54405840..82679339 100644 --- a/pages/summarize/mzta-summarize.html +++ b/pages/summarize/mzta-summarize.html @@ -4,13 +4,29 @@ ThunderAI - __MSG_Summarize_PageTitle__ + -
-

__MSG_Summarize_PageTitle__

-

__MSG_Summarize_info_default__

-
+
+

__MSG_Summarize_PageTitle__

+

__MSG_Summarize_info_default__

+
+
__MSG_Summarize_prompt_prefs_title__
+ + + + + + + +
__MSG_prefs_OptionText_use_specific_integration__ + + +
diff --git a/pages/summarize/mzta-summarize.js b/pages/summarize/mzta-summarize.js index 45f7463f..20252fc4 100644 --- a/pages/summarize/mzta-summarize.js +++ b/pages/summarize/mzta-summarize.js @@ -16,29 +16,71 @@ * along with this program. If not, see . */ +import { prefs_default, integration_options_config } from '../../options/mzta-options-default.js'; import { taLogger } from "../../js/mzta-logger.js"; -import { getSpecialPrompts, setSpecialPrompts } from "../../js/mzta-prompts.js"; -import { getPlaceholders } from "../../js/mzta-placeholders.js"; +import { + getSpecialPrompts, + setSpecialPrompts +} from "../../js/mzta-prompts.js"; +import { + getPlaceholders, + mapPlaceholderToSuggestion +} from "../../js/mzta-placeholders.js"; import { textareaAutocomplete } from "../../js/mzta-placeholders-autocomplete.js"; +import { + getAccountsList, + normalizeStringList, + isAPIKeyValue +} from "../../js/mzta-utils.js"; +import { + initializeSpecificIntegrationUI +} from "../_lib/connection-ui.js"; let autocompleteSuggestions = []; let taLog = new taLogger("mzta-summarize-page", true); document.addEventListener("DOMContentLoaded", async () => { - i18n.updateDocument(); - - - const summarize_textarea = document.getElementById("summarize_prompt_text"); - const summarize_save_btn = document.getElementById("btn_save_prompt"); - const summarize_reset_btn = document.getElementById("btn_reset_prompt"); - const summarize_textarea_email_template = document.getElementById("summarize_email_template_text"); - const summarize_reset_email_template_btn = document.getElementById("btn_reset_email_template"); - const summarize_save_email_template_btn = document.getElementById("btn_save_email_template"); - let specialPrompts = await getSpecialPrompts(); let summarize_prompt = specialPrompts.find((prompt) => prompt.id === 'prompt_summarize'); let summarize_email_template = specialPrompts.find((prompt) => prompt.id === 'prompt_summarize_email_template'); + + if (summarize_prompt && summarize_prompt.api_type && summarize_prompt.api_type !== '') { + let update_prefs = {}; + update_prefs['summarize_connection_type'] = summarize_prompt.api_type; + + let integration = summarize_prompt.api_type.replace('_api', ''); + if (integration_options_config && integration_options_config[integration]) { + for (const key of Object.keys(integration_options_config[integration])) { + if (summarize_prompt[key] !== undefined) { + update_prefs[`summarize_${integration}_${key}`] = summarize_prompt[key]; + } + } + } + await browser.storage.sync.set(update_prefs); + } + + await initializeSpecificIntegrationUI({ + prefix: 'summarize', + promptId: 'prompt_summarize', + taLog: taLog, + restoreOptionsCallback: restoreOptions + }); + + i18n.updateDocument(); + + document.querySelectorAll(".option-input").forEach(element => { + element.addEventListener("change", saveOptions); + }); + let prefs_summarize = await browser.storage.sync.get({ summarize_enabled_accounts: [], connection_type: 'chatgpt_web' }); + + let summarize_textarea = document.getElementById("summarize_prompt_text"); + let summarize_save_btn = document.getElementById("btn_save_prompt"); + let summarize_reset_btn = document.getElementById("btn_reset_prompt"); + let summarize_textarea_email_template = document.getElementById("summarize_email_template_text"); + let summarize_reset_email_template_btn = document.getElementById("btn_reset_email_template"); + let summarize_save_email_template_btn = document.getElementById("btn_save_email_template"); + summarize_textarea.addEventListener("input", (event) => { summarize_reset_btn.disabled = (event.target.value === browser.i18n.getMessage('prompt_summarize_full_text')); @@ -84,16 +126,123 @@ document.addEventListener("DOMContentLoaded", async () => { if(summarize_email_template === 'prompt_summarize_email_template'){ summarize_email_template.text = browser.i18n.getMessage(summarize_email_template.text); } - summarize_textarea_email_template.value = summarize_email_template.text; summarize_reset_email_template_btn.disabled = (summarize_email_template.value === browser.i18n.getMessage("prompt_summarize_email_template")); - summarize_textarea.value = summarize_prompt.text; summarize_reset_btn.disabled = (summarize_textarea.value === browser.i18n.getMessage("prompt_summarize_full_text")); - + autocompleteSuggestions = (await getPlaceholders(true)) .filter((p) => p.id !== "additional_text") - .map((p) => ({ command: `{%${p.id}%}`, type: p.type })); + .map(mapPlaceholderToSuggestion); textareaAutocomplete(summarize_textarea, autocompleteSuggestions, 1); + textareaAutocomplete(summarize_textarea_email_template, autocompleteSuggestions, 1); + }); +// Methods to manage options, derived from: /options/mzta-options.js + +function saveOptions(e) { + e.preventDefault(); + let options = {}; + let element = e.target; + // console.log(">>>>>>>>>> Saving option: " + element.id + " = " + element.value); + switch (element.type) { + case 'checkbox': + options[element.id] = element.checked; + break; + case 'number': + options[element.id] = element.valueAsNumber; + break; + case 'text': + case 'password': + options[element.id] = element.value.trim(); + break; + case 'select-one': + // console.log(">>>>>>>>>> Saving option [select-one]: " + element.id + " = " + element.value); + options[element.id] = element.value; + break; + case 'textarea': + if(element.id === 'add_tags_auto_uselist_list') { + element.value = normalizeStringList(element.value, 1); + } + options[element.id] = normalizeStringList(element.value); + break; + default: + console.error("[ThunderAI] Unhandled input type:", element.type); + } + + browser.storage.sync.set(options); +} + +async function restoreOptions() { + function setCurrentChoice(result) { + document.querySelectorAll(".option-input").forEach(element => { + taLog.log("Options restoring " + element.id + " = " + (isAPIKeyValue(element.id) ? "****************" : result[element.id])); + switch (element.type) { + case 'checkbox': + element.checked = result[element.id] || false; + break; + case 'number': + let default_number_value = 0; + if(element.id == 'chatgpt_win_height') default_number_value = prefs_default.chatgpt_win_height; + if(element.id == 'chatgpt_win_width') default_number_value = prefs_default.chatgpt_win_width; + element.value = result[element.id] ?? default_number_value; + break; + case 'text': + case 'textarea': + case 'password': + let default_text_value = ''; + if(element.id == 'default_chatgpt_lang') default_text_value = prefs_default.default_chatgpt_lang; + if(element.id === 'add_tags_auto_uselist_list') { + result[element.id] = normalizeStringList(result[element.id], 1); + } + element.value = result[element.id] || default_text_value; + break; + default: + if (element.tagName === 'SELECT') { + let default_select_value = ''; + const restoreValue = result[element.id] || default_select_value; + // Check if option exists + let optionExists = Array.from(element.options).some(opt => opt.value === restoreValue); + // If it doesn't exist and restoreValue is not empty, create it + if (!optionExists && restoreValue !== '') { + let newOption = new Option(restoreValue, restoreValue); + element.add(newOption); + } + // Set value + element.value = restoreValue; + if (element.value === '') { + element.selectedIndex = -1; + } + }else{ + console.error("[ThunderAI] Unhandled input type:", element.type); + } + } + }); + } + + let getting = await browser.storage.sync.get(prefs_default); + + let specialPrompts = await getSpecialPrompts(); + let addtags_prompt = specialPrompts.find(prompt => prompt.id === 'prompt_add_tags'); + + if (addtags_prompt) { + if (addtags_prompt.api_type && addtags_prompt.api_type !== '') { + getting['add_tags_connection_type'] = addtags_prompt.api_type; + } else { + getting['add_tags_connection_type'] = getting['connection_type']; + } + for (const [integration, options] of Object.entries(integration_options_config)) { + for (const key of Object.keys(options)) { + const propName = `${integration}_${key}`; + if (addtags_prompt[propName] !== undefined && addtags_prompt[propName] !== '') { + getting[`add_tags_${propName}`] = addtags_prompt[propName]; + } else { + getting[`add_tags_${propName}`] = getting[propName]; + } + } + } + } + + setCurrentChoice(getting); +} From 66cc35746f7b8299117a8b852c83b7eb56019db7 Mon Sep 17 00:00:00 2001 From: Guido Kraemer Date: Wed, 7 Jan 2026 20:40:15 +0100 Subject: [PATCH 013/205] move email separators to extra prompt field --- _locales/en/messages.json | 8 +++++-- js/mzta-prompts.js | 18 ++++++++++++++- mzta-background.js | 11 +++++---- pages/summarize/mzta-summarize.css | 4 ++++ pages/summarize/mzta-summarize.html | 16 +++++++++++++ pages/summarize/mzta-summarize.js | 36 +++++++++++++++++++++++++++++ 6 files changed, 85 insertions(+), 8 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index a7a0102d..08f5a2f8 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1012,11 +1012,15 @@ "description": "" }, "prompt_summarize_full_text": { - "message": "Summarize the following email or emails into a bullet point list. If there are multiple emails, consider the entire thread for the summary.\n------------------\n\n", + "message": "Summarize the following email or emails into a bullet point list. If there are multiple emails, consider the entire thread for the summary.", "description": "" }, "prompt_summarize_email_template": { - "message": "From: {%author%}\nTo: {%recipients%}\nCC: {%cc_list%}\nSubject: {%mail_subject%}\nDate: {%mail_datetime%}\nAttachments:\n{%mail_attachments_info%}\nBody:\n{%mail_text_body%}\n---------------\n\n", + "message": "From: {%author%}\nTo: {%recipients%}\nCC: {%cc_list%}\nSubject: {%mail_subject%}\nDate: {%mail_datetime%}\nAttachments:\n{%mail_attachments_info%}\nBody:\n{%mail_text_body%}", + "description": "" + }, + "prompt_summarize_email_separator": { + "message": "\n\n---------- NEXT EMAIL ----------\n\n", "description": "" }, "prompt_get_task": { diff --git a/js/mzta-prompts.js b/js/mzta-prompts.js index f68a3997..76736064 100644 --- a/js/mzta-prompts.js +++ b/js/mzta-prompts.js @@ -355,6 +355,22 @@ const specialPrompts = [ api_model: '', is_default: "1", is_special: "1", + }, + { + id: 'prompt_summarize_email_separator', + name: "__MSG_prompt_summarize_email_separator__", + text: "prompt_summarize_email_separator_full_text", + type: "1", + action: "0", + need_selected: "0", + need_signature: "0", + need_custom_text: "0", + define_response_lang: "0", + use_diff_viewer: "0", + api_type: '', + api_model: '', + is_default: "1", + is_special: "1", } ]; @@ -619,4 +635,4 @@ export async function clearPromptAPI(id){ } // console.log(">>>>>>>>>>>>> clearPromptAPI _prompt AFTER: " + JSON.stringify(_prompt)); await savePrompt(_prompt); -} \ No newline at end of file +} diff --git a/mzta-background.js b/mzta-background.js index 5b92c76c..46370b6f 100644 --- a/mzta-background.js +++ b/mzta-background.js @@ -1028,11 +1028,12 @@ browser.menus.onClicked.addListener( (info, tab) => { async function summarizeEmails(messages) { taWorkingStatus.startWorking(); - // we have two prompts, the actual assignment for the LLM and the email - // template prompt. + // 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); @@ -1059,11 +1060,11 @@ async function summarizeEmails(messages) { msg_text: curr_body_full_html, })); }; - let messages_string = messages_list.join("\n\n--- Next Email ---\n\n"); + const messages_string = messages_list.join(prompt_email_separator_string); - let full_prompt = prompt.text + "\n\n\n" + messages_string; + const full_prompt = prompt_string + prompt_email_separator_string + messages_string; - console.log(full_prompt); + // console.log(full_prompt); // send the prompt to the chat interface openChatGPT( diff --git a/pages/summarize/mzta-summarize.css b/pages/summarize/mzta-summarize.css index 26b08e10..add7a975 100644 --- a/pages/summarize/mzta-summarize.css +++ b/pages/summarize/mzta-summarize.css @@ -11,6 +11,10 @@ width: 100%; } +#summarize_email_separator_text { + width: 100%; +} + .infoline { font-size: 0.8em; font-style: italic; diff --git a/pages/summarize/mzta-summarize.html b/pages/summarize/mzta-summarize.html index 82679339..12c94b3c 100644 --- a/pages/summarize/mzta-summarize.html +++ b/pages/summarize/mzta-summarize.html @@ -79,6 +79,22 @@ __MSG_save__
+ + +
+ + +
+ +
+
+ + +
diff --git a/pages/summarize/mzta-summarize.js b/pages/summarize/mzta-summarize.js index 20252fc4..bd57c0c5 100644 --- a/pages/summarize/mzta-summarize.js +++ b/pages/summarize/mzta-summarize.js @@ -44,6 +44,7 @@ document.addEventListener("DOMContentLoaded", async () => { let specialPrompts = await getSpecialPrompts(); let summarize_prompt = specialPrompts.find((prompt) => prompt.id === 'prompt_summarize'); let summarize_email_template = specialPrompts.find((prompt) => prompt.id === 'prompt_summarize_email_template'); + let summarize_email_separator = specialPrompts.find((prompt) => prompt.id === 'prompt_summarize_email_separator'); if (summarize_prompt && summarize_prompt.api_type && summarize_prompt.api_type !== '') { let update_prefs = {}; @@ -80,8 +81,12 @@ document.addEventListener("DOMContentLoaded", async () => { let summarize_textarea_email_template = document.getElementById("summarize_email_template_text"); let summarize_reset_email_template_btn = document.getElementById("btn_reset_email_template"); let summarize_save_email_template_btn = document.getElementById("btn_save_email_template"); + let summarize_email_separator_textarea = document.getElementById("summarize_email_separator_text"); + let summarize_email_separator_save_btn = document.getElementById("btn_save_email_separator"); + let summarize_email_separator_reset_btn = document.getElementById("btn_reset_email_separator"); + // on changing textareas summarize_textarea.addEventListener("input", (event) => { summarize_reset_btn.disabled = (event.target.value === browser.i18n.getMessage('prompt_summarize_full_text')); summarize_save_btn.disabled = (event.target.value === summarize_prompt.text); @@ -91,7 +96,14 @@ document.addEventListener("DOMContentLoaded", async () => { summarize_reset_email_template_btn.disabled = (event.target.value === browser.i18n.getMessage('prompt_summarize_email_template')); summarize_save_email_template_btn.disabled = (event.target.value === summarize_email_template.text); }); + + summarize_email_separator_textarea.addEventListener("input", (event) => { + summarize_email_separator_reset_btn.disabled = (event.target.value === browser.i18n.getMessage('prompt_summarize_email_separator')); + summarize_email_separator_save_btn.disabled = (event.target.value === summarize_email_separator.text); + }); + + // on clicking buttons, reset summarize_reset_email_template_btn.addEventListener("click", () => { summarize_textarea_email_template.value = browser.i18n.getMessage("prompt_summarize_email_template"); summarize_reset_email_template_btn.disabled = true; @@ -105,7 +117,15 @@ document.addEventListener("DOMContentLoaded", async () => { let event = new Event("input", { bubbles: true, cancelable: true }); summarize_textarea.dispatchEvent(event); }); + + summarize_email_separator_reset_btn.addEventListener("click", () => { + summarize_email_separator_textarea.value = browser.i18n.getMessage("prompt_summarize_email_separator"); + summarize_email_separator_reset_btn.disabled = true; + let event = new Event("input", { bubbles: true, cancelable: true }); + summarize_email_separator_textarea.dispatchEvent(event); + }); + // on clicking buttons, save summarize_save_email_template_btn.addEventListener("click", () => { specialPrompts.find(prompt => prompt.id === 'prompt_summarize_email_template').text = summarize_textarea_email_template.value; setSpecialPrompts(specialPrompts); @@ -119,6 +139,14 @@ document.addEventListener("DOMContentLoaded", async () => { summarize_save_btn.disabled = true; browser.runtime.sendMessage({ command: "reload_menus" }); }); + + summarize_email_separator_save_btn.addEventListener("click", () => { + specialPrompts.find(prompt => prompt.id === 'prompt_summarize_email_separator').text = summarize_email_separator_textarea.value; + setSpecialPrompts(specialPrompts); + summarize_email_separator_save_btn.disabled = true; + browser.runtime.sendMessage({ command: "reload_menus" }); + }); + if(summarize_prompt.text === 'prompt_summarize_full_text'){ summarize_prompt.text = browser.i18n.getMessage(summarize_prompt.text); @@ -126,16 +154,24 @@ document.addEventListener("DOMContentLoaded", async () => { if(summarize_email_template === 'prompt_summarize_email_template'){ summarize_email_template.text = browser.i18n.getMessage(summarize_email_template.text); } + if(summarize_email_separator.text === 'prompt_summarize_email_separator'){ + summarize_email_separator.text = browser.i18n.getMessage(summarize_email_separator.text); + } + summarize_textarea_email_template.value = summarize_email_template.text; summarize_reset_email_template_btn.disabled = (summarize_email_template.value === browser.i18n.getMessage("prompt_summarize_email_template")); summarize_textarea.value = summarize_prompt.text; summarize_reset_btn.disabled = (summarize_textarea.value === browser.i18n.getMessage("prompt_summarize_full_text")); + summarize_email_separator_textarea.value = summarize_email_separator.text; + summarize_email_separator_reset_btn.disabled = (summarize_email_separator.value === browser.i18n.getMessage("prompt_summarize_email_separator")); autocompleteSuggestions = (await getPlaceholders(true)) .filter((p) => p.id !== "additional_text") .map(mapPlaceholderToSuggestion); + textareaAutocomplete(summarize_textarea, autocompleteSuggestions, 1); textareaAutocomplete(summarize_textarea_email_template, autocompleteSuggestions, 1); + textareaAutocomplete(summarize_email_separator_textarea, autocompleteSuggestions, 1); }); From 24edcc09e6875b15fa500ad1c23a5d132b918118 Mon Sep 17 00:00:00 2001 From: Guido Kraemer Date: Wed, 7 Jan 2026 20:40:43 +0100 Subject: [PATCH 014/205] make some variables const --- mzta-background.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mzta-background.js b/mzta-background.js index 46370b6f..40532152 100644 --- a/mzta-background.js +++ b/mzta-background.js @@ -1039,13 +1039,13 @@ async function summarizeEmails(messages) { const chatgpt_lang = await taPromptUtils.getDefaultLang(prompt); // assemble all email messages into one string and add the assignment prompt - let messages_list = []; + const messages_list = []; for await (let curr_message of messages) { // extract body of current message as text - let curr_message_full = await browser.messages.getFull(curr_message.id); - let curr_body_full_html = getMailBody(curr_message_full); - let curr_body_full_text = htmlBodyToPlainText(curr_body_full_html.html); + 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; From c6bf2edba50f53babfbc13b2aba0a788ee30edfa Mon Sep 17 00:00:00 2001 From: Guido Kraemer Date: Wed, 7 Jan 2026 20:40:58 +0100 Subject: [PATCH 015/205] allow placeholders in other summarize prompts --- mzta-background.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/mzta-background.js b/mzta-background.js index 40532152..c06d5c0b 100644 --- a/mzta-background.js +++ b/mzta-background.js @@ -1037,6 +1037,19 @@ async function summarizeEmails(messages) { 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 = []; From c261c1da9049be324977c141c709d082cc10e158 Mon Sep 17 00:00:00 2001 From: Guido Kraemer Date: Wed, 7 Jan 2026 20:41:18 +0100 Subject: [PATCH 016/205] fix bug --- pages/summarize/mzta-summarize.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/summarize/mzta-summarize.js b/pages/summarize/mzta-summarize.js index bd57c0c5..ea8d8364 100644 --- a/pages/summarize/mzta-summarize.js +++ b/pages/summarize/mzta-summarize.js @@ -151,7 +151,7 @@ document.addEventListener("DOMContentLoaded", async () => { if(summarize_prompt.text === 'prompt_summarize_full_text'){ summarize_prompt.text = browser.i18n.getMessage(summarize_prompt.text); } - if(summarize_email_template === 'prompt_summarize_email_template'){ + if(summarize_email_template.text === 'prompt_summarize_email_template'){ summarize_email_template.text = browser.i18n.getMessage(summarize_email_template.text); } if(summarize_email_separator.text === 'prompt_summarize_email_separator'){ From 30a7de3756575fc8c292422dbfd8f5ad4fc3ff95 Mon Sep 17 00:00:00 2001 From: Guido Kraemer Date: Wed, 7 Jan 2026 20:43:42 +0100 Subject: [PATCH 017/205] better explanation --- _locales/en/messages.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 08f5a2f8..f64feb2d 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1088,7 +1088,7 @@ "description": "" }, "prefs_OptionText_Summarize_infoline2": { - "message": "You can change the prompt as you wish, the first field is the main prompt, the second field is the template for a single mail. The list of emails will be appended to the main prompt.", + "message": "You can change the prompt as you wish, the first field is the main prompt, the second field is the template for a single mail. The list of emails will be appended to the main prompt. Emails will be separated by the separator specified in the third field.", "description": "" }, "prefs_OptionText_get_calendar_event_Sparks_not_present": { From d601121852e8ab6095de7d8e3d177afc4067a065 Mon Sep 17 00:00:00 2001 From: Guido Kraemer Date: Sat, 10 Jan 2026 22:59:26 +0100 Subject: [PATCH 018/205] better descriptions for prompts --- _locales/en/messages.json | 12 ++++++++++++ pages/summarize/mzta-summarize.html | 22 +++++++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index f64feb2d..2d7329ec 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1091,6 +1091,18 @@ "message": "You can change the prompt as you wish, the first field is the main prompt, the second field is the template for a single mail. The list of emails will be appended to the main prompt. Emails will be separated by the separator specified in the third field.", "description": "" }, + "prefs_OptionText_Summarize_main_prompt": { + "message": "The main prompt describing the task to be performed on all selected emails:", + "description": "" + }, + "prefs_OptionText_Summarize_email_template": { + "message": "The template for a single email:", + "description": "" + }, + "prefs_OptionText_Summarize_email_separator": { + "message": "The separator between emails:", + "description": "" + }, "prefs_OptionText_get_calendar_event_Sparks_not_present": { "message": "To use the calendar event and task features, please install the ThunderAI Sparks addon.", "description": "" diff --git a/pages/summarize/mzta-summarize.html b/pages/summarize/mzta-summarize.html index 12c94b3c..a8db111d 100644 --- a/pages/summarize/mzta-summarize.html +++ b/pages/summarize/mzta-summarize.html @@ -27,6 +27,8 @@ + +
@@ -47,8 +49,14 @@
- + + + __MSG_prefs_OptionText_Summarize_main_prompt__ + + +
+
@@ -65,6 +73,12 @@
+ + __MSG_prefs_OptionText_Summarize_email_template__ + + +
+
@@ -81,6 +95,12 @@
+ + __MSG_prefs_OptionText_Summarize_email_separator__ + + +
+
From 51c81418a38e007b9d5ba181a87332bb25217c45 Mon Sep 17 00:00:00 2001 From: Guido Kraemer Date: Sat, 10 Jan 2026 23:00:20 +0100 Subject: [PATCH 019/205] handle disabling summarize options button --- options/mzta-options-default.js | 2 +- options/mzta-options.html | 2 +- options/mzta-options.js | 41 +++++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/options/mzta-options-default.js b/options/mzta-options-default.js index e14b2808..6fd52d1d 100644 --- a/options/mzta-options-default.js +++ b/options/mzta-options-default.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -const special_prompts_with_integration = ['add_tags', 'spamfilter']; +const special_prompts_with_integration = ['add_tags', 'spamfilter', 'summarize']; export const integration_options_config = { chatgpt: { diff --git a/options/mzta-options.html b/options/mzta-options.html index 02b0f6ea..534f33b8 100644 --- a/options/mzta-options.html +++ b/options/mzta-options.html @@ -154,7 +154,7 @@ " diff --git a/options/mzta-options.js b/options/mzta-options.js index 3b2ac351..46a7439d 100644 --- a/options/mzta-options.js +++ b/options/mzta-options.js @@ -167,6 +167,27 @@ function disable_SpamFilter(prefs_opt){ } } +function disable_Summarize(prefs_opt){ + let summarize = document.getElementById('summarize'); + let conntype_select = document.getElementById("connection_type"); + const tempPrefs = { + connection_type: conntype_select.value, + ...prefs_opt + }; + let summarize_disabled = (getConnectionType(tempPrefs, null, 'summarize') === "chatgpt_web"); + let summarize_checked_original = summarize.checked; + summarize.checked = summarize_disabled ? false : summarize.checked; + if(!summarize.checked){ + let summarize_info_btn = document.getElementById('btnManageSummarizeInfo'); + summarize_info_btn.disabled = 'disabled'; + } + let summarize_warn_API_needed = document.getElementById('summarize_warn_API_needed'); + summarize_warn_API_needed.style.display = (summarize_disabled) ? 'inline-block' : 'none'; + if(summarize_checked_original != summarize.checked){ + browser.storage.sync.set({summarize: summarize.checked}); + } +} + async function disable_GetCalendarEvent(){ let get_calendar_event = document.getElementById('get_calendar_event'); let get_task = document.getElementById('get_task'); @@ -262,6 +283,24 @@ document.addEventListener('DOMContentLoaded', async () => { }); spamfilter_info_btn.disabled = spamfilter_el.checked ? '' : 'disabled'; + let summarize_el = document.getElementById('summarize'); + let summarize_info_btn = document.getElementById('btnManageSummarizeInfo'); + summarize_el.addEventListener('click', (event) => { + async function _summarize_el_change() { + if (event.target.checked) { + let granted = await messenger.permissions.request({ permissions: ["messagesRead"] }); + if (!granted) { + event.target.checked = false; + summarize_info_btn.disabled = 'disabled'; + browser.storage.sync.set({summarize: false}); + } + } + } + _summarize_el_change(); + summarize_info_btn.disabled = event.target.checked ? '' : 'disabled'; + }); + summarize_info_btn.disabled = summarize_el.checked ? '' : 'disabled'; + let get_calendar_event_el = document.getElementById('get_calendar_event'); let get_calendar_event_info_btn = document.getElementById('btnManageCalendarEventInfo'); get_calendar_event_el.addEventListener('click', (event) => { @@ -321,12 +360,14 @@ document.addEventListener('DOMContentLoaded', async () => { conntype_select.addEventListener("change", disable_MaxPromptLength); conntype_select.addEventListener("change", () => disable_AddTags(prefs_opt)); conntype_select.addEventListener("change", () => disable_SpamFilter(prefs_opt)); + conntype_select.addEventListener("change", () => disable_Summarize(prefs_opt)); conntype_select.addEventListener("change", disable_GetCalendarEvent); showConnectionOptions(conntype_select); disable_MaxPromptLength(); disable_AddTags(prefs_opt); disable_SpamFilter(prefs_opt); + disable_Summarize(prefs_opt); disable_GetCalendarEvent(); document.getElementById('reset_max_prompt_length').addEventListener('click', resetMaxPromptLength); From 8679e1cb085b5b3882c1ec7f0b72985ff94a3e84 Mon Sep 17 00:00:00 2001 From: Guido Kraemer Date: Fri, 16 Jan 2026 12:55:56 +0100 Subject: [PATCH 020/205] remove checking for non-optional permission --- options/mzta-options.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/options/mzta-options.js b/options/mzta-options.js index 46a7439d..7563cd51 100644 --- a/options/mzta-options.js +++ b/options/mzta-options.js @@ -286,17 +286,6 @@ document.addEventListener('DOMContentLoaded', async () => { let summarize_el = document.getElementById('summarize'); let summarize_info_btn = document.getElementById('btnManageSummarizeInfo'); summarize_el.addEventListener('click', (event) => { - async function _summarize_el_change() { - if (event.target.checked) { - let granted = await messenger.permissions.request({ permissions: ["messagesRead"] }); - if (!granted) { - event.target.checked = false; - summarize_info_btn.disabled = 'disabled'; - browser.storage.sync.set({summarize: false}); - } - } - } - _summarize_el_change(); summarize_info_btn.disabled = event.target.checked ? '' : 'disabled'; }); summarize_info_btn.disabled = summarize_el.checked ? '' : 'disabled'; From 90dcf8437b4d54a18cb0547e47ef82b4542139a3 Mon Sep 17 00:00:00 2001 From: Guido Kraemer Date: Fri, 16 Jan 2026 12:56:35 +0100 Subject: [PATCH 021/205] remove stray console logging --- mzta-background.js | 1 - 1 file changed, 1 deletion(-) diff --git a/mzta-background.js b/mzta-background.js index c06d5c0b..da862ccb 100644 --- a/mzta-background.js +++ b/mzta-background.js @@ -995,7 +995,6 @@ function addContextMenuItems() { // Add Context menu: Summarize if(prefs_init.summarize && prefs_init.summarize_context_menu && checkAPIIntegration(prefs_init.connection_type && prefs_init.summarize_use_specific_integration, prefs_init.summarize_connection_type)) { - console.log("adding summarize to context menu") addContextMenu(contextMenuID_Summarize); } } From f147a0453b8e3781b3fb7b1d77322d74a07edb77 Mon Sep 17 00:00:00 2001 From: Guido Kraemer Date: Fri, 16 Jan 2026 12:57:06 +0100 Subject: [PATCH 022/205] remove summarize_context_menu option in favor of summarize option --- mzta-background.js | 5 ++--- options/mzta-options-default.js | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/mzta-background.js b/mzta-background.js index da862ccb..d433581a 100644 --- a/mzta-background.js +++ b/mzta-background.js @@ -811,7 +811,6 @@ async function reload_pref_init(){ dynamic_menu_force_enter: prefs_default.dynamic_menu_force_enter, add_tags_context_menu: prefs_default.add_tags_context_menu, spamfilter_context_menu: prefs_default.spamfilter_context_menu, - summarize_context_menu: prefs_default.summarize_context_menu, ...getDynamicSettingsDefaults(['use_specific_integration', 'connection_type']) }); _process_incoming = prefs_init.add_tags_auto || prefs_init.spamfilter; @@ -925,7 +924,7 @@ function setupStorageChangeListener() { } if (changes.summarize) { if (changes.summarize.newValue){ - if (prefs_init.summarize_context_menu){ + if (prefs_init.summarize){ addContextMenu(contextMenuID_Summarize); } } else { @@ -994,7 +993,7 @@ function addContextMenuItems() { } // Add Context menu: Summarize - if(prefs_init.summarize && prefs_init.summarize_context_menu && checkAPIIntegration(prefs_init.connection_type && prefs_init.summarize_use_specific_integration, prefs_init.summarize_connection_type)) { + if(prefs_init.summarize && checkAPIIntegration(prefs_init.connection_type && prefs_init.summarize_use_specific_integration, prefs_init.summarize_connection_type)) { addContextMenu(contextMenuID_Summarize); } } diff --git a/options/mzta-options-default.js b/options/mzta-options-default.js index 6fd52d1d..d5ccf9b2 100644 --- a/options/mzta-options-default.js +++ b/options/mzta-options-default.js @@ -134,6 +134,5 @@ export const prefs_default = { spamfilter_context_menu: true, spamfilter_enabled_accounts: [], summarize: false, - summarize_context_menu: true, ...generated_prefs } From cb6c93625b1bbda21b0766c6a25e7073a5285723 Mon Sep 17 00:00:00 2001 From: Guido Kraemer Date: Fri, 16 Jan 2026 13:39:22 +0100 Subject: [PATCH 023/205] correct prompt naming --- _locales/en/messages.json | 8 ++++++++ mzta-background.js | 6 +++--- pages/summarize/mzta-summarize.js | 12 ++++++------ 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 2d7329ec..b52a5539 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1016,10 +1016,18 @@ "description": "" }, "prompt_summarize_email_template": { + "message": "Summarize email template", + "description": "" + }, + "prompt_summarize_email_template_full_text": { "message": "From: {%author%}\nTo: {%recipients%}\nCC: {%cc_list%}\nSubject: {%mail_subject%}\nDate: {%mail_datetime%}\nAttachments:\n{%mail_attachments_info%}\nBody:\n{%mail_text_body%}", "description": "" }, "prompt_summarize_email_separator": { + "message": "Email separator", + "description": "" + }, + "prompt_summarize_email_separator_full_text": { "message": "\n\n---------- NEXT EMAIL ----------\n\n", "description": "" }, diff --git a/mzta-background.js b/mzta-background.js index d433581a..e7d2e3ac 100644 --- a/mzta-background.js +++ b/mzta-background.js @@ -1029,9 +1029,9 @@ async function summarizeEmails(messages) { // 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 prompt = specialPrompts.find((prompt) => prompt.id === 'prompt_summarize_full_text'); + const prompt_email = specialPrompts.find((prompt) => prompt.id === 'prompt_summarize_email_template_full_text'); + const prompt_email_separator = specialPrompts.find((prompt) => prompt.id === 'prompt_summarize_email_separator_full_text'); const tabs = await browser.tabs.query({ active: true, currentWindow: true }); const chatgpt_lang = await taPromptUtils.getDefaultLang(prompt); diff --git a/pages/summarize/mzta-summarize.js b/pages/summarize/mzta-summarize.js index ea8d8364..8df7d8b7 100644 --- a/pages/summarize/mzta-summarize.js +++ b/pages/summarize/mzta-summarize.js @@ -93,19 +93,19 @@ document.addEventListener("DOMContentLoaded", async () => { }); summarize_textarea_email_template.addEventListener("input", (event) => { - summarize_reset_email_template_btn.disabled = (event.target.value === browser.i18n.getMessage('prompt_summarize_email_template')); + summarize_reset_email_template_btn.disabled = (event.target.value === browser.i18n.getMessage('prompt_summarize_email_template_full_text')); summarize_save_email_template_btn.disabled = (event.target.value === summarize_email_template.text); }); summarize_email_separator_textarea.addEventListener("input", (event) => { - summarize_email_separator_reset_btn.disabled = (event.target.value === browser.i18n.getMessage('prompt_summarize_email_separator')); + summarize_email_separator_reset_btn.disabled = (event.target.value === browser.i18n.getMessage('prompt_summarize_email_separator_full_text')); summarize_email_separator_save_btn.disabled = (event.target.value === summarize_email_separator.text); }); // on clicking buttons, reset summarize_reset_email_template_btn.addEventListener("click", () => { - summarize_textarea_email_template.value = browser.i18n.getMessage("prompt_summarize_email_template"); + summarize_textarea_email_template.value = browser.i18n.getMessage("prompt_summarize_email_template_full_text"); summarize_reset_email_template_btn.disabled = true; let event = new Event("input", { bubbles: true, cancelable: true }); summarize_textarea_email_template.dispatchEvent(event); @@ -119,7 +119,7 @@ document.addEventListener("DOMContentLoaded", async () => { }); summarize_email_separator_reset_btn.addEventListener("click", () => { - summarize_email_separator_textarea.value = browser.i18n.getMessage("prompt_summarize_email_separator"); + summarize_email_separator_textarea.value = browser.i18n.getMessage("prompt_summarize_email_separator_full_text"); summarize_email_separator_reset_btn.disabled = true; let event = new Event("input", { bubbles: true, cancelable: true }); summarize_email_separator_textarea.dispatchEvent(event); @@ -151,10 +151,10 @@ document.addEventListener("DOMContentLoaded", async () => { if(summarize_prompt.text === 'prompt_summarize_full_text'){ summarize_prompt.text = browser.i18n.getMessage(summarize_prompt.text); } - if(summarize_email_template.text === 'prompt_summarize_email_template'){ + if(summarize_email_template.text === 'prompt_summarize_email_template_full_text'){ summarize_email_template.text = browser.i18n.getMessage(summarize_email_template.text); } - if(summarize_email_separator.text === 'prompt_summarize_email_separator'){ + if(summarize_email_separator.text === 'prompt_summarize_email_separator_full_text'){ summarize_email_separator.text = browser.i18n.getMessage(summarize_email_separator.text); } From 7202b5a3e29e11839722a6fd6a4bff8065d0b30a Mon Sep 17 00:00:00 2001 From: Guido Kraemer Date: Fri, 16 Jan 2026 14:21:35 +0100 Subject: [PATCH 024/205] fix prompt names --- mzta-background.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mzta-background.js b/mzta-background.js index e7d2e3ac..d433581a 100644 --- a/mzta-background.js +++ b/mzta-background.js @@ -1029,9 +1029,9 @@ async function summarizeEmails(messages) { // 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_full_text'); - const prompt_email = specialPrompts.find((prompt) => prompt.id === 'prompt_summarize_email_template_full_text'); - const prompt_email_separator = specialPrompts.find((prompt) => prompt.id === 'prompt_summarize_email_separator_full_text'); + 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); From b247dad80f1b103de31ff5c0b39dd1099fed3c06 Mon Sep 17 00:00:00 2001 From: Guido Kraemer Date: Fri, 16 Jan 2026 14:22:07 +0100 Subject: [PATCH 025/205] adapt code copied from add_tags --- pages/summarize/mzta-summarize.js | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/pages/summarize/mzta-summarize.js b/pages/summarize/mzta-summarize.js index 8df7d8b7..71634058 100644 --- a/pages/summarize/mzta-summarize.js +++ b/pages/summarize/mzta-summarize.js @@ -198,9 +198,6 @@ function saveOptions(e) { options[element.id] = element.value; break; case 'textarea': - if(element.id === 'add_tags_auto_uselist_list') { - element.value = normalizeStringList(element.value, 1); - } options[element.id] = normalizeStringList(element.value); break; default: @@ -229,9 +226,6 @@ async function restoreOptions() { case 'password': let default_text_value = ''; if(element.id == 'default_chatgpt_lang') default_text_value = prefs_default.default_chatgpt_lang; - if(element.id === 'add_tags_auto_uselist_list') { - result[element.id] = normalizeStringList(result[element.id], 1); - } element.value = result[element.id] || default_text_value; break; default: @@ -260,21 +254,21 @@ async function restoreOptions() { let getting = await browser.storage.sync.get(prefs_default); let specialPrompts = await getSpecialPrompts(); - let addtags_prompt = specialPrompts.find(prompt => prompt.id === 'prompt_add_tags'); + let addtags_prompt = specialPrompts.find(prompt => prompt.id === 'prompt_summarize'); if (addtags_prompt) { if (addtags_prompt.api_type && addtags_prompt.api_type !== '') { - getting['add_tags_connection_type'] = addtags_prompt.api_type; + getting['summarize_connection_type'] = addtags_prompt.api_type; } else { - getting['add_tags_connection_type'] = getting['connection_type']; + getting['summarize_connection_type'] = getting['connection_type']; } for (const [integration, options] of Object.entries(integration_options_config)) { for (const key of Object.keys(options)) { const propName = `${integration}_${key}`; if (addtags_prompt[propName] !== undefined && addtags_prompt[propName] !== '') { - getting[`add_tags_${propName}`] = addtags_prompt[propName]; + getting[`summarize_${propName}`] = addtags_prompt[propName]; } else { - getting[`add_tags_${propName}`] = getting[propName]; + getting[`summarize_${propName}`] = getting[propName]; } } } From f6fcaac0bb9947ad1117b3ebe07dbfd4b31e28f0 Mon Sep 17 00:00:00 2001 From: Guido Kraemer Date: Fri, 16 Jan 2026 15:09:47 +0100 Subject: [PATCH 026/205] update prompts --- _locales/en/messages.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index b52a5539..3ce55cc4 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1012,7 +1012,7 @@ "description": "" }, "prompt_summarize_full_text": { - "message": "Summarize the following email or emails into a bullet point list. If there are multiple emails, consider the entire thread for the summary.", + "message": "You are an assistant that summarizes email conversations.\n\nGiven an email thread, produce a concise, accurate summary that captures:\n\n- The main topic or purpose of the conversation\n- Key decisions, conclusions, or agreements\n- Important questions, requests, or action items\n- Who is responsible for each action item (if stated)\n\nOmit greetings, signatures, quoted text, and redundant back-and-forth.\nDo not add assumptions or information not present in the emails.\n\nWrite the summary in clear, neutral language suitable for a busy professional.", "description": "" }, "prompt_summarize_email_template": { @@ -1020,7 +1020,7 @@ "description": "" }, "prompt_summarize_email_template_full_text": { - "message": "From: {%author%}\nTo: {%recipients%}\nCC: {%cc_list%}\nSubject: {%mail_subject%}\nDate: {%mail_datetime%}\nAttachments:\n{%mail_attachments_info%}\nBody:\n{%mail_text_body%}", + "message": "From: {%author%}\nTo: {%recipients%}\nCC: {%cc_list%}\nSubject: {%mail_subject%}\nDate: {%mail_datetime%}\nAttachments:\n{%mail_attachments_info%}\n\nBody:\n{%mail_text_body%}", "description": "" }, "prompt_summarize_email_separator": { From 4576549bab8a14dd9381c8506d1a4bb69533b4f0 Mon Sep 17 00:00:00 2001 From: mic Date: Thu, 29 Jan 2026 22:53:33 +0100 Subject: [PATCH 027/205] version set to 4.0.0 --- CHANGELOG.md | 4 ++++ manifest.json | 2 +- options/mzta-release-notes.html | 4 ++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 31d24e43..7c0d2847 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ +

Version 4.0.0 - ??/??/2026

+
    +
  • ...
  • +

Version 3.8.3 - 22/01/2026

  • Fix: Correctly saving the API settings in new custom prompts [#623].
  • diff --git a/manifest.json b/manifest.json index c70f8713..961a7d9e 100644 --- a/manifest.json +++ b/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "ThunderAI", "description": "__MSG_extensionDescription__", - "version": "3.8.3", + "version": "4.0.0", "author": "Mic (m@micz.it)", "homepage_url": "https://micz.it/thunderbird-addon-thunderai/", "browser_specific_settings": { diff --git a/options/mzta-release-notes.html b/options/mzta-release-notes.html index c87ef83c..6c27cd96 100644 --- a/options/mzta-release-notes.html +++ b/options/mzta-release-notes.html @@ -7,6 +7,10 @@

    ThunderAI Release Notes

    +

    Version 4.0.0 - ??/??/2026

    +
      +
    • ...
    • +

    Version 3.8.3 - 22/01/2026

    • Fix: Correctly saving the API settings in new custom prompts [#623].
    • From 63b80d4e42f43d64cf35541a35dd1f09f8f4d1ca Mon Sep 17 00:00:00 2001 From: mic Date: Thu, 29 Jan 2026 23:29:27 +0100 Subject: [PATCH 028/205] summarize icon added. see #615 --- README.md | 1 + images/summarize.png | Bin 0 -> 1131 bytes js/mzta-utils.js | 2 +- 3 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 images/summarize.png diff --git a/README.md b/README.md index 95d1828f..847034c6 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,7 @@ _The language status represents the percentage of translated strings in the late - [JessiGue](https://www.flaticon.com/authors/jessigue) for the show/hide icon for api key fields - [Iconka.com](https://www.iconarchive.com/artist/iconka.html) for the autotag context menu icon - [Icojam](https://www.iconarchive.com/artist/icojam.html) for the spam filter context menu icon +- [Roundicons](https://www.flaticon.com/authors/roundicons) for the summarize context menu icon
      diff --git a/images/summarize.png b/images/summarize.png new file mode 100644 index 0000000000000000000000000000000000000000..9c829f0e7973c4edd301a37369a651dd57285e0f GIT binary patch literal 1131 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!n2Vh}LpV4%Za?&Y0OWEOctjR6 zFfbkhVa69WwIM)3$r9IylHmNblJdl&REF~Ma=pyF?Be9af>gcyqV(DCY@`?%n3Mv1 zLR|m<{|{7;2Kf2;1q1}FfxyZND8<9W!^_Ld$jHdb$_gae*w|E6R1_5zPZfim-yJu%?lSvWBRnl&G4nn1;TXy}!7m zl$3?Dlxvu@jI6YUv$UO$bYQxSyn>8Vkc>-+Tu{1PY?)kKxqNi7e0+s`QjJ1Jt71-< zQhu+ps+zKfwpz_R^|}QbjY~9|muYpb)78<@Gcwkjx>w)8P=ERXgV{$7W*;+{d)#pT zDZ}}vjTW6VUUI>B=_M0mV@nGQOKTe|I|qA5XL~0XCubKIXJ=OrPY-WjA1^N-KmU-B z(9rOR_}JL^xVZSZ_{5~-q=dwz#KfGetfGQ~lET8u^75+k^6JXU)~2Se_V%v!j=r9r z34MJN`}!vJ_fMWMVcO)$^JdSUKWEOe#fz6OS+aV?iZv@&u3fci-Rjlr*R0vFX3h4^ zn|E#7wr}_DgM0TL+PCk>fdj`59Xfva@Y$0m&z(AT{?w_9XU<%`c=6h$OE<1uxpnQ@ z?d#X?-n@D5=FJDUZ$G?q=kdLJPaiyZ_VD5JM~_}Se*E&ulb27QzIyiT&5IXrU%Ytt z^5y$iuRgqf{ps!7&+p!SdH??Uft?3|ai&%h(^hteEs_U$IoBCe*gV@Db~CK7|ETUE{-7)hm#W$9ti{oDs!*!C@?fOF!*@N zH*#;E&W56(RT{#~tDl5@I=D>4kL{nNh{T4i(Q_QrlvUZzgr@~8aruAEb=6|F9w`TI z=3lF4PY*~q@GQo*TKn+IC(;*^W=)v$M$%y)qov)+lP5n`6+6CRo&2%E(t_v2cP1%; z)q4ewtm8P&{ApW)`GUJDR+c9%yP6_2u9ldT&2;rN@ymZ5X*^4u?M{QMw1&}w>yFN= zYjl6I`CR|n$8DyscE3T$JRy!FINo&|QfGjhrdLpfNwhTtP0z85oTWp00i_>zopr0Q-8u-v9sr literal 0 HcmV?d00001 diff --git a/js/mzta-utils.js b/js/mzta-utils.js index eb4f8d49..dca1a52a 100644 --- a/js/mzta-utils.js +++ b/js/mzta-utils.js @@ -29,7 +29,7 @@ export const contextMenuID_Summarize = 'mzta-summarize'; export const contextMenuIconsPath = { [contextMenuID_AddTags]: 'moz-extension:images/autotags.png', [contextMenuID_Spamfilter]: 'moz-extension:images/spamfilter.png', - // [contextMenuID_Summarize]: 'moz-extension:images/summarize.png', + [contextMenuID_Summarize]: 'moz-extension:images/summarize.png', }; export function getLanguageDisplayName(languageCode) { From f489e65483d63b8c93e32176a0c43da0860e0385 Mon Sep 17 00:00:00 2001 From: Mic Date: Thu, 29 Jan 2026 23:36:00 +0100 Subject: [PATCH 029/205] correctly adding context menu item for summarize when activating the feature. fixes #626 --- mzta-background.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/mzta-background.js b/mzta-background.js index 1f009330..d7a6f629 100644 --- a/mzta-background.js +++ b/mzta-background.js @@ -885,6 +885,7 @@ function setupStorageChangeListener() { if(newConnectionType === "chatgpt_web"){ removeContextMenu(contextMenuID_AddTags); removeContextMenu(contextMenuID_Spamfilter); + removeContextMenu(contextMenuID_Summarize); } addContextMenuItems(); } @@ -924,11 +925,9 @@ function setupStorageChangeListener() { } if (changes.summarize) { if (changes.summarize.newValue){ - if (prefs_init.summarize){ - addContextMenu(contextMenuID_Summarize); - } + addContextMenu(contextMenuID_Summarize); } else { - removeContextMenu(contextMenuID_Summarize); + removeContextMenu(contextMenuID_Summarize); } } reload_pref_init(); @@ -993,7 +992,7 @@ function addContextMenuItems() { } // Add Context menu: Summarize - if(prefs_init.summarize && checkAPIIntegration(prefs_init.connection_type && prefs_init.summarize_use_specific_integration, prefs_init.summarize_connection_type)) { + if(prefs_init.summarize && checkAPIIntegration(prefs_init.connection_type, prefs_init.summarize_use_specific_integration, prefs_init.summarize_connection_type)) { addContextMenu(contextMenuID_Summarize); } } From 3ba98c7d90156b59bf1555578d51881883a7f697 Mon Sep 17 00:00:00 2001 From: Mic Date: Thu, 29 Jan 2026 23:45:00 +0100 Subject: [PATCH 030/205] Removed the spamfilter_context_menu and add_tags_context_menu options and always show the context menu when the corresponding feature is active. See #609 --- _locales/en/messages.json | 16 -------------- mzta-background.js | 30 +++++---------------------- options/mzta-options-default.js | 2 -- pages/addtags/mzta-add-tags.html | 10 +-------- pages/spamfilter/mzta-spamfilter.html | 10 +-------- 5 files changed, 7 insertions(+), 61 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 3ce55cc4..307b30e6 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1371,22 +1371,6 @@ "message": "Summarize", "description": "" }, - "prefs_OptionText_add_tags_context_menu": { - "message": "Show the \"Add tags\" context menu item", - "description": "" - }, - "prefs_OptionText_add_tags_context_menu_Info": { - "message": "If checked, the \"Add tags\" context menu item will be shown when right-clicking on an email in the messages list.", - "description": "" - }, - "prefs_OptionText_spamfilter_context_menu": { - "message": "Show the \"Analyze for spam\" context menu item", - "description": "" - }, - "prefs_OptionText_spamfilter_context_menu_Info": { - "message": "If checked, the \"Analyze for spam\" context menu item will be shown when right-clicking on an email in the messages list.", - "description": "" - }, "noActiveCalendar": { "message": "No editable calendar found!", "description": "" diff --git a/mzta-background.js b/mzta-background.js index d7a6f629..1b5d9bde 100644 --- a/mzta-background.js +++ b/mzta-background.js @@ -809,8 +809,6 @@ async function reload_pref_init(){ summarize: prefs_default.summarize, spamfilter_threshold: prefs_default.spamfilter_threshold, dynamic_menu_force_enter: prefs_default.dynamic_menu_force_enter, - add_tags_context_menu: prefs_default.add_tags_context_menu, - spamfilter_context_menu: prefs_default.spamfilter_context_menu, ...getDynamicSettingsDefaults(['use_specific_integration', 'connection_type']) }); _process_incoming = prefs_init.add_tags_auto || prefs_init.spamfilter; @@ -891,34 +889,16 @@ function setupStorageChangeListener() { } // context menu changes for add_tags and spamfilter - if (changes.add_tags_context_menu) { - if(changes.add_tags_context_menu.newValue){ + if (changes.add_tags) { + if(changes.add_tags.newValue){ addContextMenu(contextMenuID_AddTags); }else{ removeContextMenu(contextMenuID_AddTags); } } - if (changes.add_tags) { - if(changes.add_tags.newValue){ - if(prefs_init.add_tags_context_menu){ - addContextMenu(contextMenuID_AddTags); - } - }else{ - removeContextMenu(contextMenuID_AddTags); - } - } - if (changes.spamfilter_context_menu) { - if(changes.spamfilter_context_menu.newValue){ - addContextMenu(contextMenuID_Spamfilter); - }else{ - removeContextMenu(contextMenuID_Spamfilter); - } - } if (changes.spamfilter) { if(changes.spamfilter.newValue){ - if(prefs_init.spamfilter_context_menu){ - addContextMenu(contextMenuID_Spamfilter); - } + addContextMenu(contextMenuID_Spamfilter); }else{ removeContextMenu(contextMenuID_Spamfilter); } @@ -982,12 +962,12 @@ function removeContextMenu(menu_id) { function addContextMenuItems() { // Add Context menu: Add tags - if(prefs_init.add_tags && prefs_init.add_tags_context_menu && checkAPIIntegration(prefs_init.connection_type, prefs_init.add_tags_use_specific_integration,prefs_init.add_tags_connection_type)){ + if(prefs_init.add_tags && checkAPIIntegration(prefs_init.connection_type, prefs_init.add_tags_use_specific_integration,prefs_init.add_tags_connection_type)){ addContextMenu(contextMenuID_AddTags); } // Add Context menu: Spamfilter - if(prefs_init.spamfilter && prefs_init.spamfilter_context_menu && checkAPIIntegration(prefs_init.connection_type, prefs_init.spamfilter_use_specific_integration,prefs_init.spamfilter_connection_type)){ + if(prefs_init.spamfilter && checkAPIIntegration(prefs_init.connection_type, prefs_init.spamfilter_use_specific_integration,prefs_init.spamfilter_connection_type)){ addContextMenu(contextMenuID_Spamfilter); } diff --git a/options/mzta-options-default.js b/options/mzta-options-default.js index d5ccf9b2..164cd511 100644 --- a/options/mzta-options-default.js +++ b/options/mzta-options-default.js @@ -123,7 +123,6 @@ export const prefs_default = { add_tags_auto_only_inbox: true, add_tags_auto_uselist: false, add_tags_auto_uselist_list: '', - add_tags_context_menu: true, add_tags_enabled_accounts: [], get_calendar_event: true, get_task: true, @@ -131,7 +130,6 @@ export const prefs_default = { calendar_timezone: '', spamfilter: false, spamfilter_threshold: 70, - spamfilter_context_menu: true, spamfilter_enabled_accounts: [], summarize: false, ...generated_prefs diff --git a/pages/addtags/mzta-add-tags.html b/pages/addtags/mzta-add-tags.html index b6c35fd6..59104df0 100644 --- a/pages/addtags/mzta-add-tags.html +++ b/pages/addtags/mzta-add-tags.html @@ -101,15 +101,7 @@ - - __MSG_prefs_OptionText_add_tags_context_menu__ - - - - + __MSG_prefs_OptionText_add_tags_auto_force_existing__ diff --git a/pages/spamfilter/mzta-spamfilter.html b/pages/spamfilter/mzta-spamfilter.html index 271b4264..c8ad47f4 100644 --- a/pages/spamfilter/mzta-spamfilter.html +++ b/pages/spamfilter/mzta-spamfilter.html @@ -35,15 +35,7 @@ - - __MSG_prefs_OptionText_spamfilter_context_menu__ - - - - +
      __MSG_SpamFilter_prompt_text_title__ From 0b7e81cd7b93a07478a9730608e1225aad128806 Mon Sep 17 00:00:00 2001 From: Mic Date: Fri, 30 Jan 2026 22:09:04 +0100 Subject: [PATCH 031/205] GEMINI.MD file added --- GEMINI.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 GEMINI.md diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 00000000..1eb93734 --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,36 @@ +# ThunderAI Project Agent Context + +## Project Overview +ThunderAI is a Mozilla Thunderbird add-on that integrates Large Language Models (LLMs) such as ChatGPT, Gemini, Claude, and Ollama for advanced email management. It allows users to analyze, draft, correct, tag, and create calendar events directly from the email client. + +## Technical Stack +- **Languages:** JavaScript (ES6+), HTML5, CSS3. +- **Environment:** Thunderbird MailExtension API (based on WebExtensions). +- **Core Logic:** Primarily located in `background.js` and UI-specific scripts. +- **API Integrations:** Specific logic is contained within dedicated API provider files (e.g., `api_openai.js`, `api_google_gemini.js`). + +## Development Rules & Style Guide +- **Code Style:** Use modern JavaScript. Prefer `async/await` over chained Promises. +- **Naming Conventions:** Use `camelCase` for variables and functions. +- **Security:** - Never hardcode API keys in the source code. + - Use `messenger.storage.local` for data persistence. + - Strictly adhere to CORS policies for external API calls. +- **i18n:** + - The project supports multiple languages via `_locales/`. When adding UI strings, always use `messenger.i18n.getMessage()`. + - When you need to modify language file, modify only the english version. + +## Files & Directories Structure +- `manifest.json`: Extension entry point and permission definitions. +- `api_*.js`: Modules for integration with different AI providers. +- `options/`: Configuration and settings pages. +- `_locales/`: Translation files (JSON format). +- `graphics/`: Visual assets and icons. + +## Important Commands & Workflow +- **Testing:** Since this is a Thunderbird add-on, testing is performed by loading the extension as a "Temporary Add-on" via Thunderbird's `Debug Add-ons` menu. +- **Build:** The project does not use complex build tools (no Webpack/Vite by default); it is a "pure" MailExtension. + +## Agent Instructions +1. **Context Awareness:** When modifying API-related files, ensure compatibility with the existing placeholder system (e.g., `{%mail_body%}`, `{%additional_text%}`). +2. **Permission Review:** Before modifying `manifest.json`, verify that any new permissions requested are strictly necessary for the feature. +3. **Compatibility:** Ensure code is compatible with the latest Thunderbird ESR (Extended Support Release) versions. \ No newline at end of file From 310dbf88189a1ff3443d5bc1175b56a3e9122668 Mon Sep 17 00:00:00 2001 From: mic Date: Fri, 30 Jan 2026 22:21:37 +0100 Subject: [PATCH 032/205] 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 7c0d2847..f63c2394 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@

      Version 4.0.0 - ??/??/2026

        +
      • [All APIs] Added a special prompt to summarize one or more emails, using a context menu command [#615]. Thanks to Guido Kraemer for his great work on this feature.
      • +
      • [All APIs] "Analyze for spam" and "Add tags" context menu items are always shown when the corresponding feature is enabled [#609].
      • ...

      Version 3.8.3 - 22/01/2026

      diff --git a/options/mzta-release-notes.html b/options/mzta-release-notes.html index 6c27cd96..6248de5d 100644 --- a/options/mzta-release-notes.html +++ b/options/mzta-release-notes.html @@ -9,6 +9,8 @@

      ThunderAI Release Notes

      Version 4.0.0 - ??/??/2026

        +
      • [All APIs] Added a special prompt to summarize one or more emails, using a context menu command [#615]. Thanks to Guido Kraemer for his great work on this feature.
      • +
      • [All APIs] "Analyze for spam" and "Add tags" context menu items are always shown when the corresponding feature is enabled [#609].
      • ...

      Version 3.8.3 - 22/01/2026

      From 72737d538b2d998ed018097812a7a2e10b423435 Mon Sep 17 00:00:00 2001 From: mic Date: Fri, 30 Jan 2026 22:22:59 +0100 Subject: [PATCH 033/205] strict_min_version set to 140.0. see #616 --- manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index 961a7d9e..8014a58a 100644 --- a/manifest.json +++ b/manifest.json @@ -8,7 +8,7 @@ "browser_specific_settings": { "gecko": { "id": "thunderai@micz.it", - "strict_min_version": "115.0" + "strict_min_version": "140.0" } }, "message_display_action": { From 6463e180af634043dca83a76c60c56f0130062b2 Mon Sep 17 00:00:00 2001 From: mic Date: Fri, 30 Jan 2026 22:24:17 +0100 Subject: [PATCH 034/205] tb115 segmenter removed. see #616 --- js/tb115_segmenter/tb115_loader.js | 65 ---------------------------- js/tb115_segmenter/tb115_polyfill.js | 49 --------------------- manifest.json | 2 +- 3 files changed, 1 insertion(+), 115 deletions(-) delete mode 100644 js/tb115_segmenter/tb115_loader.js delete mode 100644 js/tb115_segmenter/tb115_polyfill.js diff --git a/js/tb115_segmenter/tb115_loader.js b/js/tb115_segmenter/tb115_loader.js deleted file mode 100644 index df476c7c..00000000 --- a/js/tb115_segmenter/tb115_loader.js +++ /dev/null @@ -1,65 +0,0 @@ -/* - * ThunderAI [https://micz.it/thunderbird-addon-thunderai/] - * Copyright (C) 2024 - 2026 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 . - */ -// Firefox 115 and, consequently, Thunderbird 115 are not compatible with Intl.Segmenter. -// ChatGPT is using Intl.Segmenter, so we have to do something about it or it won't work... - -// console.log("[ThunderAI] Intl.Segmenter: " + Intl.Segmenter); - -(function() { - if('Segmenter' in Intl){ - // console.log('[ThunderAI] TB128+ detected. Intl.Segmenter is already supported.'); - return; - } - // Creates a From c2e2336f4d806507da2dc3d7fdde9e51fa1a2020 Mon Sep 17 00:00:00 2001 From: mic Date: Sun, 22 Feb 2026 23:07:51 +0100 Subject: [PATCH 198/205] Added "Antispam by ThunderAI" --- _locales/en/messages.json | 4 ++++ js/mzta-compose-script.js | 12 +++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index c6aacb70..bfc1d731 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1897,6 +1897,10 @@ "message": "Spam check in progress...", "description": "" }, + "antispam_by": { + "message": "Antispam by", + "description": "" + }, "prefs_THStats_1": { "message": "Do you want beautiful statistics about your emails?", "description": "" diff --git a/js/mzta-compose-script.js b/js/mzta-compose-script.js index e30298fd..7dfd4168 100644 --- a/js/mzta-compose-script.js +++ b/js/mzta-compose-script.js @@ -641,8 +641,13 @@ switch (message.command) { loadingImg.src = browser.runtime.getURL("/images/loading.gif"); loadingImg.style.cssText = "height: 16px; width: 16px;"; + const brandingProgress = document.createElement('span'); + brandingProgress.textContent = browser.i18n.getMessage("antispam_by") + " ThunderAI"; + brandingProgress.style.cssText = 'margin-left: auto; font-style: italic; font-size: 11px; opacity: 0.7;'; + containerProgress.appendChild(loadingImg); containerProgress.appendChild(textProgress); + containerProgress.appendChild(brandingProgress); document.body.insertBefore(containerProgress, document.body.firstChild); return Promise.resolve(true); @@ -693,9 +698,13 @@ switch (message.command) { reasonText.textContent = browser.i18n.getMessage("Explanation") + ": " + data.explanation; } + const branding = document.createElement('span'); + branding.textContent = browser.i18n.getMessage("antispam_by") + " ThunderAI"; + branding.style.cssText = 'margin-left: auto; font-style: italic; font-size: 11px; opacity: 0.7;'; + const closeBtn = document.createElement('span'); closeBtn.textContent = '×'; - closeBtn.style.cssText = 'margin-left: auto; cursor: pointer; font-weight: bold; font-size: 16px; padding: 0 5px;'; + closeBtn.style.cssText = 'cursor: pointer; font-weight: bold; font-size: 16px; padding: 0 5px;'; closeBtn.title = browser.i18n.getMessage("chatgpt_win_close"); closeBtn.onclick = function() { container.remove(); @@ -704,6 +713,7 @@ switch (message.command) { container.appendChild(scoreText); container.appendChild(reasonText); + container.appendChild(branding); container.appendChild(closeBtn); document.body.insertBefore(container, document.body.firstChild); From fe5f1db7b6cdc1ee7a72bdc37d0a1cddf33776e0 Mon Sep 17 00:00:00 2001 From: Andreas Pettersson Date: Mon, 23 Feb 2026 16:52:53 +0100 Subject: [PATCH 199/205] Translated using Weblate (Swedish) Currently translated at 100.0% (475 of 475 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 325dbc42..90f0ea9f 100644 --- a/_locales/sv/messages.json +++ b/_locales/sv/messages.json @@ -1427,5 +1427,11 @@ }, "prefs_THStats_2": { "message": "Klicka här! Testa ThunderStats!" + }, + "antispam_by": { + "message": "Antispam av" + }, + "prefs_OptionText_calendar_no_selection_missing_placeholder": { + "message": "Prompten måste innehålla platshållaren {%mail_text_body_or_selected%} eller {%mail_html_body_or_selected%} för att aktivera det här alternativet. Lägg till en av dessa platshållare i prompten eller återställ den till standard." } } From e65699386c3868fe1382c3fdd39e814733a2674e Mon Sep 17 00:00:00 2001 From: Mic Date: Mon, 23 Feb 2026 12:14:08 +0100 Subject: [PATCH 200/205] Translated using Weblate (French) Currently translated at 100.0% (475 of 475 strings) Translation: ThunderAI for Thunderbird/main Translate-URL: https://hosted.weblate.org/projects/thunderai/main/fr/ --- _locales/fr/messages.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/_locales/fr/messages.json b/_locales/fr/messages.json index b823a370..dc41a8e9 100644 --- a/_locales/fr/messages.json +++ b/_locales/fr/messages.json @@ -1443,5 +1443,11 @@ }, "prefs_THStats_2": { "message": "Cliquez ici ! Essayez ThunderStats !" + }, + "prefs_OptionText_calendar_no_selection_missing_placeholder": { + "message": "L'invite doit contenir l'espace réservé {%mail_text_body_or_selected%} ou {%mail_html_body_or_selected%} pour activer cette option. Veuillez ajouter l'un de ces espaces réservés à l'invite ou rétablir la valeur par défaut." + }, + "antispam_by": { + "message": "Antispam par" } } From 6dc7b052c15a45c8b005e4af88a0a0072b05d3fc Mon Sep 17 00:00:00 2001 From: Mic Date: Mon, 23 Feb 2026 12:13:57 +0100 Subject: [PATCH 201/205] Translated using Weblate (German) Currently translated at 100.0% (475 of 475 strings) Translation: ThunderAI for Thunderbird/main Translate-URL: https://hosted.weblate.org/projects/thunderai/main/de/ --- _locales/de/messages.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/_locales/de/messages.json b/_locales/de/messages.json index 09aba133..66367494 100644 --- a/_locales/de/messages.json +++ b/_locales/de/messages.json @@ -1443,5 +1443,11 @@ }, "prefs_THStats_2": { "message": "Hier klicken! Probiere ThunderStats aus!" + }, + "prefs_OptionText_calendar_no_selection_missing_placeholder": { + "message": "Der Prompt muss den Platzhalter {%mail_text_body_or_selected%} oder {%mail_html_body_or_selected%} enthalten, um diese Option zu aktivieren. Bitte fügen Sie einen dieser Platzhalter zum Prompt hinzu oder setzen Sie ihn auf den Standardwert zurück." + }, + "antispam_by": { + "message": "Antispam von" } } From c569795098189f3c080d9889f3ee6b8e920a3248 Mon Sep 17 00:00:00 2001 From: Mic Date: Mon, 23 Feb 2026 12:13:38 +0100 Subject: [PATCH 202/205] Translated using Weblate (Italian) Currently translated at 100.0% (475 of 475 strings) Translation: ThunderAI for Thunderbird/main Translate-URL: https://hosted.weblate.org/projects/thunderai/main/it/ --- _locales/it/messages.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/_locales/it/messages.json b/_locales/it/messages.json index 5054722a..5bd0eb19 100644 --- a/_locales/it/messages.json +++ b/_locales/it/messages.json @@ -1443,5 +1443,11 @@ }, "prefs_THStats_2": { "message": "Clicca qui! Prova ThunderStats!" + }, + "prefs_OptionText_calendar_no_selection_missing_placeholder": { + "message": "Il prompt deve contenere il segnaposto {%mail_text_body_or_selected%} o {%mail_html_body_or_selected%} per abilitare questa opzione. Per favore, aggiungi uno di questi segnaposto al prompt o ripristina quello predefinito." + }, + "antispam_by": { + "message": "Antispam di" } } From 4f84da425636ac91494f6b70f3b8de1052ce37cc Mon Sep 17 00:00:00 2001 From: mic Date: Mon, 23 Feb 2026 21:55:10 +0100 Subject: [PATCH 203/205] Fixes ported from v3.8.5. --- js/mzta-placeholders.js | 13 ++++++++----- js/mzta-utils.js | 9 ++++++++- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/js/mzta-placeholders.js b/js/mzta-placeholders.js index c666e858..48de04a3 100644 --- a/js/mzta-placeholders.js +++ b/js/mzta-placeholders.js @@ -17,7 +17,10 @@ */ import { prefs_default } from '../options/mzta-options-default.js'; -import { getMailHeader } from './mzta-utils.js'; +import { + getMailHeader, + sanitizeMailHeaders + } from './mzta-utils.js'; /* ================= PLACEHOLDERS PROPERTIES ======================================== @@ -545,7 +548,7 @@ export const placeholdersUtils = { finalSubs['mail_folder_path'] = placeholdersUtils.failSafePlaceholders(curr_message.folder?.path); break; case 'mail_headers': - finalSubs['mail_headers:' + currPH.custom_value] = placeholdersUtils.failSafePlaceholders(await getMailHeader(curr_message, currPH.custom_value)); + finalSubs['mail_headers:' + currPH.custom_value] = placeholdersUtils.failSafePlaceholders(sanitizeMailHeaders(await getMailHeader(curr_message, currPH.custom_value))); break; case 'selected_text': finalSubs['selected_text'] = placeholdersUtils.failSafePlaceholders(selection_text); @@ -560,13 +563,13 @@ export const placeholdersUtils = { finalSubs['mail_html_body_or_selected'] = placeholdersUtils.failSafePlaceholders(selection_html || msg_text?.html); break; case 'author': - finalSubs['author'] = placeholdersUtils.failSafePlaceholders(curr_message.author); + finalSubs['author'] = placeholdersUtils.failSafePlaceholders(sanitizeMailHeaders(curr_message.author)); break; case 'recipients': - finalSubs['recipients'] = placeholdersUtils.failSafePlaceholders(curr_message.recipients?.join(", ")); + finalSubs['recipients'] = placeholdersUtils.failSafePlaceholders(sanitizeMailHeaders(curr_message.recipients?.join(", "))); break; case 'cc_list': - finalSubs['cc_list'] = placeholdersUtils.failSafePlaceholders(curr_message.ccList?.join(", ")); + finalSubs['cc_list'] = placeholdersUtils.failSafePlaceholders(sanitizeMailHeaders(curr_message.ccList?.join(", "))); break; case 'junk_score': finalSubs['junk_score'] = placeholdersUtils.failSafePlaceholders(curr_message.junkScore); diff --git a/js/mzta-utils.js b/js/mzta-utils.js index a8aabdb2..ab8d7ef5 100644 --- a/js/mzta-utils.js +++ b/js/mzta-utils.js @@ -207,7 +207,8 @@ export async function getMailHeader(curr_message, mail_header_id) { let full_message = await browser.messages.getFull(curr_message.id); // console.log(">>>>>>>>>>>> getMailHeader full_message: " + JSON.stringify(full_message)); if(full_message.hasOwnProperty("headers") && Object.keys(full_message.headers).some(header => header.toLowerCase() === mail_header_id.toLowerCase())){ - mail_header_value = full_message.headers[Object.keys(full_message.headers).find(header => header.toLowerCase() === mail_header_id.toLowerCase())]; + const raw_value = full_message.headers[Object.keys(full_message.headers).find(header => header.toLowerCase() === mail_header_id.toLowerCase())]; + mail_header_value = Array.isArray(raw_value) ? raw_value.join(", ") : raw_value; } // console.log(">>>>>>>>>>>> getMailHeader mail_header_value: " + mail_header_value) return mail_header_value; @@ -218,6 +219,12 @@ export function sanitizeHtml(input) { return input.replace(/<(?!br\s*\/?)[^>]+>/gi, ''); } +export function sanitizeMailHeaders(input){ + console.log(">>>>>>>>>>>> sanitizeMailHeaders input: " + JSON.stringify(input)); + if(!input) return ''; + return input.replace(//g, '>'); +} + export function stripHtmlKeepLines(htmlString) { // Replaces

      tags with a newline at the beginning // and removes all other HTML tags From 49c5791d644b623589e17c62b97903f6fe96edc9 Mon Sep 17 00:00:00 2001 From: mic Date: Mon, 23 Feb 2026 22:15:35 +0100 Subject: [PATCH 204/205] tom select updated to version 3.5.2. fixes #673 --- pages/_lib/tom-select.base.js | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/pages/_lib/tom-select.base.js b/pages/_lib/tom-select.base.js index 8e42b70b..2d1c3390 100644 --- a/pages/_lib/tom-select.base.js +++ b/pages/_lib/tom-select.base.js @@ -1,5 +1,5 @@ /** -* Tom Select v2.5.1 +* Tom Select v2.5.2 * Licensed under the Apache License, Version 2.0 (the "License"); */ @@ -2906,7 +2906,7 @@ if (self.settings.hideSelected) { result.items = result.items.filter(item => { let hashed = hash_key(item.id); - return !(hashed && self.items.indexOf(hashed) !== -1); + return !(hashed !== null && self.items.indexOf(hashed) !== -1); }); } return result; @@ -2985,6 +2985,13 @@ optgroup = optgroups[j]; let order = option.$order; let self_optgroup = self.optgroups[optgroup]; + if (self_optgroup === undefined && typeof self.settings.optionGroupRegister === 'function') { + var regGroup; + if (regGroup = self.settings.optionGroupRegister.apply(self, [optgroup])) { + self.registerOptionGroup(regGroup); + } + } + self_optgroup = self.optgroups[optgroup]; if (self_optgroup === undefined) { optgroup = ''; } else { @@ -3444,6 +3451,11 @@ } } + //remove input value when enabled + if (self.settings.clearAfterSelect) { + self.setTextboxValue(); + } + // refreshOptions after setActiveOption(), // otherwise setActiveOption() will be called by refreshOptions() with the wrong value if (!self.isPending && !self.settings.closeAfterSelect) { @@ -3456,11 +3468,6 @@ } else if (!self.isPending) { self.positionDropdown(); } - - //remove input value when enabled - if (self.settings.clearAfterSelect) { - self.setTextboxValue(); - } self.trigger('item_add', hashed, item); if (!self.isPending) { self.updateOriginalInput({ From fb6b1d6b8e61f580e3fac7f78c73f516c31e84c1 Mon Sep 17 00:00:00 2001 From: mic Date: Tue, 24 Feb 2026 21:26:22 +0100 Subject: [PATCH 205/205] GEMINI.md renamed in CLAUDE.md --- GEMINI.md => CLAUDE.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename GEMINI.md => CLAUDE.md (100%) diff --git a/GEMINI.md b/CLAUDE.md similarity index 100% rename from GEMINI.md rename to CLAUDE.md