diff --git a/_locales/en/messages.json b/_locales/en/messages.json
index 1e8b9cac..3ce55cc4 100644
--- a/_locales/en/messages.json
+++ b/_locales/en/messages.json
@@ -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\": [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": ""
+ },
+ "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": ""
@@ -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": ""
@@ -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": ""
@@ -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": ""
@@ -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": ""
diff --git a/js/mzta-prompts.js b/js/mzta-prompts.js
index b598cbce..76736064 100644
--- a/js/mzta-prompts.js
+++ b/js/mzta-prompts.js
@@ -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",
+ }
];
@@ -587,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/js/mzta-utils.js b/js/mzta-utils.js
index 67ce0823..eb4f8d49 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_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',
};
export function getLanguageDisplayName(languageCode) {
diff --git a/mzta-background.js b/mzta-background.js
index 4ab3abc4..1f009330 100644
--- a/mzta-background.js
+++ b/mzta-background.js
@@ -45,6 +45,7 @@ import {
extractJsonObject,
contextMenuID_AddTags,
contextMenuID_Spamfilter,
+ contextMenuID_Summarize,
contextMenuIconsPath,
sanitizeChatGPTModelData,
sanitizeChatGPTWebCustomData,
@@ -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';
@@ -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,
@@ -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();
}
});
@@ -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();
@@ -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) => {
diff --git a/options/mzta-options-default.js b/options/mzta-options-default.js
index 66b24890..d5ccf9b2 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: {
@@ -133,5 +133,6 @@ export const prefs_default = {
spamfilter_threshold: 70,
spamfilter_context_menu: true,
spamfilter_enabled_accounts: [],
+ summarize: false,
...generated_prefs
}
diff --git a/options/mzta-options.html b/options/mzta-options.html
index 0e24f172..534f33b8 100644
--- a/options/mzta-options.html
+++ b/options/mzta-options.html
@@ -147,6 +147,17 @@
+