Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
84a65ac
resolve rebase conflicts
gdkrmr Jan 3, 2026
050a3fd
Add MZTA summary page
gdkrmr Dec 19, 2025
4ab6da2
change all "summary" -> "summarize"
gdkrmr Dec 19, 2025
02787e2
Fix context menu ID typos
gdkrmr Dec 19, 2025
8abb7e3
Add summarize prompt to specialPrompts
gdkrmr Dec 19, 2025
2c07aa6
Remove unused imports from mzta-summarize.js
gdkrmr Dec 19, 2025
7eb9a2c
fixes saving of summarize prompt
gdkrmr Dec 19, 2025
fe427c2
Add Email Template Editor to Summarize Panel
gdkrmr Dec 23, 2025
f29d833
WIP: Add Summarize context menu action and handler
gdkrmr Dec 23, 2025
7595c70
Implement summarization internals
gdkrmr Jan 2, 2026
c3e27f0
Fix malformed label closing tag in options page
gdkrmr Jan 2, 2026
3d2c2b9
add customizable model options for summarize
gdkrmr Jan 3, 2026
66cc357
move email separators to extra prompt field
gdkrmr Jan 7, 2026
24edcc0
make some variables const
gdkrmr Jan 7, 2026
c6bf2ed
allow placeholders in other summarize prompts
gdkrmr Jan 7, 2026
c261c1d
fix bug
gdkrmr Jan 7, 2026
30a7de3
better explanation
gdkrmr Jan 7, 2026
d601121
better descriptions for prompts
gdkrmr Jan 10, 2026
51c8141
handle disabling summarize options button
gdkrmr Jan 10, 2026
8679e1c
remove checking for non-optional permission
gdkrmr Jan 16, 2026
90dcf84
remove stray console logging
gdkrmr Jan 16, 2026
f147a04
remove summarize_context_menu option in favor of summarize option
gdkrmr Jan 16, 2026
cb6c936
correct prompt naming
gdkrmr Jan 16, 2026
7202b5a
fix prompt names
gdkrmr Jan 16, 2026
b247dad
adapt code copied from add_tags
gdkrmr Jan 16, 2026
f6fcaac
update prompts
gdkrmr Jan 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions _locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1003,6 +1003,34 @@
"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\": [[email protected],[email protected],[email protected]]\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": ""
},
"prompt_summarize_full_text": {
"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": {
"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%}\n\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": ""
},
"prompt_get_task": {
"message": "Add a new task",
"description": ""
Expand Down Expand Up @@ -1067,6 +1095,22 @@
"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. 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": ""
Expand Down Expand Up @@ -1195,6 +1239,22 @@
"message": "Manage spam filter settings",
"description": ""
},
"prefs_OptionText_summarize": {
"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": ""
},
"prefs_OptionText_btnManageSummarizeInfo": {
"message": "Manage summarize settings",
"description": ""
},
"SpamFilter_PageTitle": {
"message": "Manage Spam Filter Settings",
"description": ""
Expand All @@ -1219,6 +1279,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": ""
Expand Down Expand Up @@ -1295,6 +1367,10 @@
"message": "Analyze for spam",
"description": ""
},
"context_menu_mzta-summarize": {
"message": "Summarize",
"description": ""
},
"prefs_OptionText_add_tags_context_menu": {
"message": "Show the \"Add tags\" context menu item",
"description": ""
Expand Down
50 changes: 49 additions & 1 deletion js/mzta-prompts.js
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,54 @@ 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",
},
{
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",
},
{
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",
}
];


Expand Down Expand Up @@ -587,4 +635,4 @@ export async function clearPromptAPI(id){
}
// console.log(">>>>>>>>>>>>> clearPromptAPI _prompt AFTER: " + JSON.stringify(_prompt));
await savePrompt(_prompt);
}
}
2 changes: 2 additions & 0 deletions js/mzta-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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_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',
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no icon yet. If you have a good idea, go ahead and add one.

};

export function getLanguageDisplayName(languageCode) {
Expand Down
97 changes: 96 additions & 1 deletion mzta-background.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import {
extractJsonObject,
contextMenuID_AddTags,
contextMenuID_Spamfilter,
contextMenuID_Summarize,
contextMenuIconsPath,
sanitizeChatGPTModelData,
sanitizeChatGPTWebCustomData,
Expand All @@ -57,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';
Expand Down Expand Up @@ -802,6 +806,7 @@ 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,
Expand Down Expand Up @@ -917,6 +922,15 @@ function setupStorageChangeListener() {
removeContextMenu(contextMenuID_Spamfilter);
}
}
if (changes.summarize) {
if (changes.summarize.newValue){
if (prefs_init.summarize){
addContextMenu(contextMenuID_Summarize);
}
} else {
removeContextMenu(contextMenuID_Summarize);
}
}
reload_pref_init();
}
});
Expand Down Expand Up @@ -977,6 +991,11 @@ 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: Summarize
if(prefs_init.summarize && checkAPIIntegration(prefs_init.connection_type && prefs_init.summarize_use_specific_integration, prefs_init.summarize_connection_type)) {
addContextMenu(contextMenuID_Summarize);
}
}

addContextMenuItems();
Expand All @@ -985,17 +1004,93 @@ 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) {
// info.selectedMessages is of type MessageList
summarizeEmails(getMessages(info.selectedMessages));
}
});

async function summarizeEmails(messages) {
taWorkingStatus.startWorking();

// we have three prompts, the actual assignment for the LLM, the email
// template prompt, and the email separator prompt
const specialPrompts = await getSpecialPrompts();
const prompt = specialPrompts.find((prompt) => prompt.id === 'prompt_summarize');
const prompt_email = specialPrompts.find((prompt) => prompt.id === 'prompt_summarize_email_template');
const prompt_email_separator = specialPrompts.find((prompt) => prompt.id === 'prompt_summarize_email_separator');

const tabs = await browser.tabs.query({ active: true, currentWindow: true });
const chatgpt_lang = await taPromptUtils.getDefaultLang(prompt);

// replace placeholders in the prompts the assignment prompt and email
// separator prompt do not have a message as context, so there is only
// limited things to replace
const prompt_string = await taPromptUtils.preparePrompt({
curr_prompt: prompt,
chatgpt_lang: chatgpt_lang,
});
const prompt_email_separator_string = await taPromptUtils.preparePrompt({
curr_prompt: prompt_email_separator,
chatgpt_lang: chatgpt_lang,
});


// assemble all email messages into one string and add the assignment prompt
const messages_list = [];
for await (let curr_message of messages) {

// extract body of current message as text
const curr_message_full = await browser.messages.getFull(curr_message.id);
const curr_body_full_html = getMailBody(curr_message_full);
const curr_body_full_text = htmlBodyToPlainText(curr_body_full_html.html);
if( curr_body_full_text.length === 0) {
taLog.log("No HTML found in the message body, using plain text...");
curr_body_full_text = curr_message_full.text;
}

messages_list.push(await taPromptUtils.preparePrompt({
curr_prompt: prompt_email,
curr_message: curr_message,
chatgpt_lang: chatgpt_lang,
body_text: curr_body_full_text,
subject_text: curr_message_full.headers.subject,
msg_text: curr_body_full_html,
}));
};
const messages_string = messages_list.join(prompt_email_separator_string);

const full_prompt = prompt_string + prompt_email_separator_string + messages_string;

// console.log(full_prompt);

// send the prompt to the chat interface
openChatGPT(
full_prompt,
prompt.action,
tabs[0].id,
prompt.name,
prompt.need_custom_text,
prompt
)

taWorkingStatus.stopWorking();
return {ok : '1'};
}


// Listening for new received emails
const newEmailListener = (folder, messagesList) => {
Expand Down
3 changes: 2 additions & 1 deletion options/mzta-options-default.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

const special_prompts_with_integration = ['add_tags', 'spamfilter'];
const special_prompts_with_integration = ['add_tags', 'spamfilter', 'summarize'];

export const integration_options_config = {
chatgpt: {
Expand Down Expand Up @@ -133,5 +133,6 @@ export const prefs_default = {
spamfilter_threshold: 70,
spamfilter_context_menu: true,
spamfilter_enabled_accounts: [],
summarize: false,
...generated_prefs
}
11 changes: 11 additions & 0 deletions options/mzta-options.html
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,17 @@
</label>
</td>
</tr>
<tr class="summarize_tr">
<td><span class="opt_title">__MSG_prefs_OptionText_summarize__</span>
<br><button id="btnManageSummarizeInfo" class="btn_small">__MSG_prefs_OptionText_btnManageSummarizeInfo__</button></td>
<td>
<label>
<input type="checkbox" id="summarize" name="summarize" class="option-input" />
__MSG_prefs_OptionText_summarize_Info__
<br><span class="warn_API_needed" id="summarize_warn_API_needed">__MSG_warn_API_needed__</span>
</label>
</td>
</tr>"
<tr class="get_calendar_event_tr">
<td><span class="opt_title">__MSG_prefs_OptionText_get_calendar_event__</span>
<br><button id="btnManageCalendarEventInfo" class="btn_small">__MSG_prefs_OptionText_btnManageCalendarEventInfo__</button></td>
Expand Down
Loading