diff --git a/_locales/en/messages.json b/_locales/en/messages.json
index 1e8b9cac..2d7329ec 100644
--- a/_locales/en/messages.json
+++ b/_locales/en/messages.json
@@ -1003,6 +1003,26 @@
"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": "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%}",
+ "description": ""
+ },
+ "prompt_summarize_email_separator": {
+ "message": "\n\n---------- NEXT EMAIL ----------\n\n",
+ "description": ""
+ },
"prompt_get_task": {
"message": "Add a new task",
"description": ""
@@ -1067,6 +1087,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 +1231,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 +1271,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 +1359,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 e2161b66..c06d5c0b 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,10 +806,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 +923,15 @@ function setupStorageChangeListener() {
removeContextMenu(contextMenuID_Spamfilter);
}
}
+ if (changes.summarize) {
+ if (changes.summarize.newValue){
+ if (prefs_init.summarize_context_menu){
+ addContextMenu(contextMenuID_Summarize);
+ }
+ } else {
+ removeContextMenu(contextMenuID_Summarize);
+ }
+ }
reload_pref_init();
}
});
@@ -977,6 +992,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: 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);
+ }
}
addContextMenuItems();
@@ -985,17 +1006,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..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: {
@@ -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..534f33b8 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__
|
diff --git a/options/mzta-options.js b/options/mzta-options.js
index 16b130fd..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) => {
@@ -291,6 +330,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');
@@ -317,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);
diff --git a/pages/summarize/mzta-summarize.css b/pages/summarize/mzta-summarize.css
new file mode 100644
index 00000000..add7a975
--- /dev/null
+++ b/pages/summarize/mzta-summarize.css
@@ -0,0 +1,193 @@
+#summarize_prompt_container {
+ width: 90%;
+ margin: 20px auto;
+}
+
+#summarize_email_template_text {
+ width: 100%;
+}
+
+#summarize_prompt_text {
+ width: 100%;
+}
+
+#summarize_email_separator_text {
+ width: 100%;
+}
+
+.infoline {
+ font-size: 0.8em;
+ 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;
+ justify-content: space-between;
+}
+
+table#miczPrefs {
+ padding: 10px;
+ border-spacing: 0px;
+ width: 90%;
+ margin: 0px auto 40px auto;
+ border: 1px solid #ccc;
+}
+
+.addtags_table_title{
+ width: 90%;
+ margin: auto;
+}
+
+.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;
+}
+
+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; }
+
+ .autocomplete-list {
+ background-color: #2e2f36;
+ border: 1px solid #2e2f36;
+ }
+
+ .autocomplete-list li:hover {
+ background-color: #4c4e58;
+ }
+
+ .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
new file mode 100644
index 00000000..a8db111d
--- /dev/null
+++ b/pages/summarize/mzta-summarize.html
@@ -0,0 +1,123 @@
+
+
+
+
+ ThunderAI - __MSG_Summarize_PageTitle__
+
+
+
+
+
+
+
__MSG_Summarize_PageTitle__
+
__MSG_Summarize_info_default__
+
+ __MSG_Summarize_prompt_prefs_title__
+
+
+
+
+
+
+ __MSG_Summarize_prompt_text_title__
+
+
+
+
+
+ __MSG_prefs_OptionText_btnManagePrompts_infoline__
+
+ __MSG_more_info_string__
+
+
+
+
+ __MSG_prefs_OptionText_Summarize_infoline2__
+
+
+
+
+
+
+ __MSG_prefs_OptionText_Summarize_main_prompt__
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ __MSG_prefs_OptionText_Summarize_email_template__
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ __MSG_prefs_OptionText_Summarize_email_separator__
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/summarize/mzta-summarize.js b/pages/summarize/mzta-summarize.js
new file mode 100644
index 00000000..ea8d8364
--- /dev/null
+++ b/pages/summarize/mzta-summarize.js
@@ -0,0 +1,284 @@
+/*
+ * 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, 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,
+ 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 () => {
+
+ 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 = {};
+ 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");
+ 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);
+ });
+
+ 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_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;
+ 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_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);
+ 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;
+ 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);
+ }
+ 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'){
+ 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);
+
+});
+
+// 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);
+}