diff --git a/_locales/en/messages.json b/_locales/en/messages.json
index 2b112549..0396b7b5 100644
--- a/_locales/en/messages.json
+++ b/_locales/en/messages.json
@@ -1910,5 +1910,49 @@
"spam_check_in_progress": {
"message": "Spam check in progress...",
"description": ""
- }
-}
+ },
+ "prefs_OptionText_summarize_auto": {
+ "message": "Auto-summarize messages",
+ "description": ""
+ },
+ "prefs_OptionText_summarize_auto_disabled": {
+ "message": "Disabled",
+ "description": ""
+ },
+ "prefs_OptionText_summarize_auto_manual": {
+ "message": "Show summary button",
+ "description": ""
+ },
+ "prefs_OptionText_summarize_auto_automatic": {
+ "message": "Generate automatically",
+ "description": ""
+ },
+ "prefs_OptionText_summarize_auto_Info": {
+ "message": "Choose whether to automatically generate summaries when viewing messages. Requires an API-based connection (not ChatGPT Web).",
+ "description": ""
+ },
+ "summarize_title": {
+ "message": "Summary",
+ "description": ""
+ },
+ "summarize_generating": {
+ "message": "Generating summary...",
+ "description": ""
+ },
+ "summarize_error": {
+ "message": "Failed to generate summary",
+ "description": ""
+ },
+ "summarize_click_to_generate": {
+ "message": "Click here to generate a summary",
+ "description": ""
+ },
+ "summarize_chatgpt_web_not_supported": {
+ "message": "Auto-summary requires an API-based connection. Please configure an API connection in ThunderAI settings.",
+ "description": ""
+ },
+ "summarize_refresh": {
+ "message": "Refresh summary",
+ "description": ""
+ }
+ }
diff --git a/js/mzta-compose-script.js b/js/mzta-compose-script.js
index e30298fd..1303866d 100644
--- a/js/mzta-compose-script.js
+++ b/js/mzta-compose-script.js
@@ -709,6 +709,141 @@ switch (message.command) {
document.body.insertBefore(container, document.body.firstChild);
return Promise.resolve(true);
+ case "showSummary":
+ const generatingBanner = document.getElementById('mzta-summary-generating');
+ if(generatingBanner) generatingBanner.remove();
+
+ const triggerBtn = document.getElementById('mzta-summary-trigger');
+ if(triggerBtn) triggerBtn.remove();
+
+ const summaryBanner = document.getElementById('mzta-summary-banner');
+ if(summaryBanner) summaryBanner.remove();
+
+ const summaryData = message.data;
+ const summaryContainer = document.createElement('div');
+ summaryContainer.id = 'mzta-summary-banner';
+
+ const isDarkSummary = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
+
+ let bgColorSummary = isDarkSummary ? '#2a2a2a' : '#f0f0f0';
+ let textColorSummary = isDarkSummary ? '#e0e0e0' : '#333';
+ let borderColorSummary = isDarkSummary ? '#444' : '#ddd';
+ let titleColor = isDarkSummary ? '#ff6b6b' : '#d70022';
+
+ if (summaryData.error) {
+ bgColorSummary = isDarkSummary ? '#3a1a1a' : '#f7e6e6';
+ textColorSummary = isDarkSummary ? '#ffcccc' : '#660000';
+ borderColorSummary = '#660000';
+ titleColor = isDarkSummary ? '#ffcccc' : '#660000';
+ }
+
+ summaryContainer.className = 'thunderai-summary-pane';
+ summaryContainer.style.cssText = `background-color: ${bgColorSummary}; color: ${textColorSummary}; padding: 0.5rem; margin-bottom: 1rem; border-radius: 4px; border: 1px solid ${borderColorSummary}; font-family: system-ui, -apple-system, sans-serif; font-size: 14px;`;
+
+ const summaryHeader = document.createElement('div');
+ summaryHeader.style.cssText = `display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.5rem;`;
+
+ const summaryTitle = document.createElement('div');
+ summaryTitle.className = 'thunderai-summary-title';
+ summaryTitle.textContent = browser.i18n.getMessage("summarize_title");
+ summaryTitle.style.cssText = `font-weight: bold; font-size: 14px; color: ${titleColor};`;
+
+ const refreshBtn = document.createElement('span');
+ refreshBtn.textContent = '↻';
+ refreshBtn.title = browser.i18n.getMessage("summarize_refresh") || 'Refresh summary';
+ refreshBtn.style.cssText = `cursor: pointer; opacity: 0.6; font-size: 16px; transition: opacity 0.2s;`;
+ refreshBtn.onmouseover = () => refreshBtn.style.opacity = '1';
+ refreshBtn.onmouseout = () => refreshBtn.style.opacity = '0.6';
+ refreshBtn.onclick = async () => {
+ refreshBtn.onclick = null;
+ refreshBtn.style.opacity = '0.6';
+ browser.runtime.sendMessage({
+ command: "refreshSummary",
+ headerMessageId: summaryData.headerMessageId
+ });
+ };
+
+ summaryHeader.appendChild(summaryTitle);
+ summaryHeader.appendChild(refreshBtn);
+ summaryContainer.appendChild(summaryHeader);
+
+ const summaryText = document.createElement('div');
+ summaryText.className = 'thunderai-summary-content';
+ if (summaryData.error) {
+ summaryText.textContent = summaryData.message || browser.i18n.getMessage("summarize_error");
+ } else {
+ summaryText.textContent = summaryData.summary;
+ }
+ summaryText.style.cssText = `font-size: 14px; line-height: 1.4;`;
+
+ summaryContainer.appendChild(summaryText);
+
+ document.body.insertBefore(summaryContainer, document.body.firstChild);
+ return Promise.resolve(true);
+
+ case "showSummaryGenerating":
+ const existingGenerating = document.getElementById('mzta-summary-generating');
+ if(existingGenerating) return Promise.resolve(true);
+
+ const existingSummary = document.getElementById('mzta-summary-banner');
+ if(existingSummary) existingSummary.remove();
+
+ const isDarkGen = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
+
+ let bgColorGen = isDarkGen ? '#2a2a2a' : '#f0f0f0';
+ let textColorGen = isDarkGen ? '#e0e0e0' : '#333';
+ let borderColorGen = isDarkGen ? '#444' : '#ddd';
+ let titleColorGen = isDarkGen ? '#ff6b6b' : '#d70022';
+
+ const generatingContainer = document.createElement('div');
+ generatingContainer.id = 'mzta-summary-generating';
+ generatingContainer.className = 'thunderai-summary-pane';
+ generatingContainer.style.cssText = `background-color: ${bgColorGen}; color: ${textColorGen}; padding: 0.5rem; margin-bottom: 1rem; border-radius: 4px; border: 1px solid ${borderColorGen}; font-family: system-ui, -apple-system, sans-serif; font-size: 14px;`;
+
+ const generatingTitle = document.createElement('div');
+ generatingTitle.className = 'thunderai-summary-title';
+ generatingTitle.textContent = browser.i18n.getMessage("summarize_generating");
+ generatingTitle.style.cssText = `font-weight: bold; font-size: 14px; margin-bottom: 0.5rem; color: ${titleColorGen};`;
+
+ generatingContainer.appendChild(generatingTitle);
+
+ document.body.insertBefore(generatingContainer, document.body.firstChild);
+ return Promise.resolve(true);
+
+ case "showSummaryButton":
+ const existingButton = document.getElementById('mzta-summary-trigger');
+ if(existingButton) return Promise.resolve(true);
+
+ const isDarkBtn = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
+
+ let bgColorBtn = isDarkBtn ? '#2a2a2a' : '#f0f0f0';
+ let textColorBtn = isDarkBtn ? '#e0e0e0' : '#333';
+ let borderColorBtn = isDarkBtn ? '#444' : '#ddd';
+ let titleColorBtn = isDarkBtn ? '#ff6b6b' : '#d70022';
+
+ const triggerContainer = document.createElement('div');
+ triggerContainer.id = 'mzta-summary-trigger';
+ triggerContainer.className = 'thunderai-summary-pane';
+ triggerContainer.style.cssText = `background-color: ${bgColorBtn}; color: ${textColorBtn}; padding: 0.5rem; margin-bottom: 1rem; border-radius: 4px; border: 1px solid ${borderColorBtn}; cursor: pointer; font-family: system-ui, -apple-system, sans-serif; font-size: 14px;`;
+
+ const triggerText = document.createElement('div');
+ triggerText.className = 'thunderai-summary-title';
+ triggerText.textContent = browser.i18n.getMessage("summarize_click_to_generate");
+ triggerText.style.cssText = `font-weight: bold; font-size: 14px; margin-bottom: 0; color: ${titleColorBtn};`;
+
+ triggerContainer.appendChild(triggerText);
+ triggerContainer.onclick = async () => {
+ triggerContainer.onclick = null;
+ triggerText.textContent = browser.i18n.getMessage("summarize_generating");
+ browser.runtime.sendMessage({
+ command: "triggerSummaryGeneration",
+ headerMessageId: message.headerMessageId
+ });
+ };
+
+ document.body.insertBefore(triggerContainer, document.body.firstChild);
+ return Promise.resolve(true);
+
default:
// do nothing
return Promise.resolve(false);
@@ -716,4 +851,5 @@ switch (message.command) {
}
});
-browser.runtime.sendMessage({ command: "checkSpamReport" });
\ No newline at end of file
+browser.runtime.sendMessage({ command: "checkSpamReport" });
+browser.runtime.sendMessage({ command: "initSummary" });
\ No newline at end of file
diff --git a/js/mzta-summary-cache.js b/js/mzta-summary-cache.js
new file mode 100644
index 00000000..cacc2f02
--- /dev/null
+++ b/js/mzta-summary-cache.js
@@ -0,0 +1,115 @@
+/*
+ * 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 .
+ */
+
+export const taSummaryCache = {
+ logger: console,
+ _data_prefix: 'mzta-summary-',
+ _processing_prefix: 'mzta-summary-processing-',
+ _max_summaries: 100,
+
+ async setProcessing(data_id) {
+ const key = this._processing_prefix + data_id;
+ await browser.storage.session.set({ [key]: true });
+ },
+
+ async isProcessing(data_id) {
+ const key = this._processing_prefix + data_id;
+ let output = await browser.storage.session.get(key);
+ return output[key] || false;
+ },
+
+ async saveSummary(data, data_id) {
+ const key = this._data_prefix + data_id;
+ await browser.storage.session.set({ [key]: data });
+ await browser.storage.session.remove(this._processing_prefix + data_id);
+ },
+
+ async saveError(data_id, error_message) {
+ let data = {
+ error: true,
+ message: error_message,
+ summary_date: new Date(),
+ headerMessageId: data_id
+ };
+ await this.saveSummary(data, data_id);
+ return data;
+ },
+
+ async loadSummary(data_id) {
+ const key = this._data_prefix + data_id;
+ let output = await browser.storage.session.get(key);
+ return output[key] || null;
+ },
+
+ async removeSummary(data_id) {
+ const key = this._data_prefix + data_id;
+ await browser.storage.session.remove(key);
+ await browser.storage.session.remove(this._processing_prefix + data_id);
+ },
+
+ async getAllSummaries() {
+ let allData = await browser.storage.session.get(null);
+ let summaryData = {};
+
+ for (const [key, value] of Object.entries(allData)) {
+ if (key.startsWith(this._data_prefix)) {
+ summaryData[key.replace(this._data_prefix, '')] = value;
+ }
+ }
+
+ return summaryData;
+ },
+
+ async clearSummaries() {
+ let allData = await browser.storage.session.get(null);
+ let keysToDelete = Object.keys(allData).filter(key => key.startsWith(this._data_prefix) || key.startsWith(this._processing_prefix));
+
+ for (let key of keysToDelete) {
+ await browser.storage.session.remove(key);
+ }
+ },
+
+ async truncSummaries() {
+ let data = await this.getAllSummaries();
+ let sortedData = this.sortSummariesByDate(data);
+ let keys = Object.keys(sortedData);
+
+ if (keys.length > this._max_summaries) {
+ for (let i = this._max_summaries; i < keys.length; i++) {
+ await browser.storage.session.remove(this._data_prefix + keys[i]);
+ }
+ }
+ },
+
+ sortSummariesByDate(data) {
+ if (!data) return {};
+ const summaryKeys = Object.keys(data);
+ summaryKeys.sort((a, b) => {
+ const dateA = new Date(data[a].summary_date);
+ const dateB = new Date(data[b].summary_date);
+ return dateB - dateA;
+ });
+
+ let sortedSummaries = {};
+ summaryKeys.forEach((key) => {
+ sortedSummaries[key] = data[key];
+ });
+
+ return sortedSummaries;
+ }
+};
\ No newline at end of file
diff --git a/messageDisplay/message-content-script.js b/messageDisplay/message-content-script.js
deleted file mode 100644
index 7bb92c3d..00000000
--- a/messageDisplay/message-content-script.js
+++ /dev/null
@@ -1,107 +0,0 @@
-async function showSummaryPane() {
- // Check if auto-summary is enabled in user preferences
- const result = await browser.storage.sync.get('auto_summary_enabled');
-
- // If auto-summary is disabled or not set, don't show anything
- if (!result.auto_summary_enabled) {
- return;
- }
-
- // Create the summary pane element
- const summaryPane = document.createElement("div");
- summaryPane.className = "thunderai-summary-pane";
-
- // Create the title element
- const summaryTitle = document.createElement("div");
- summaryTitle.className = "thunderai-summary-title";
- summaryTitle.innerText = browser.i18n.getMessage("auto_summary_title");
-
- // Create a loading indicator
- const loadingIndicator = document.createElement("div");
- loadingIndicator.className = "thunderai-summary-content";
- loadingIndicator.innerText = browser.i18n.getMessage("auto_summary_generating");
-
- // Create the content element (initially hidden)
- const summaryContent = document.createElement("div");
- summaryContent.className = "thunderai-summary-content";
- summaryContent.style.display = 'none';
-
- // Add title and loading indicator to the pane
- summaryPane.appendChild(summaryTitle);
- summaryPane.appendChild(loadingIndicator);
- summaryPane.appendChild(summaryContent);
-
- // Insert it as the very first element in the message
- document.body.insertBefore(summaryPane, document.body.firstChild);
-
- // Get the message content and generate summary
- try {
- const messageContent = getMessageContent();
- const aiSummary = await generateAISummary(messageContent);
-
- // Update the UI with the AI summary
- loadingIndicator.style.display = 'none';
- summaryContent.innerText = aiSummary;
- summaryContent.style.display = 'block';
- } catch (error) {
- console.error("Error generating AI summary:", error);
- loadingIndicator.innerText = browser.i18n.getMessage("auto_summary_failed");
- loadingIndicator.style.color = '#d70022';
-
- }
-}
-
-function getMessageContent() {
- // Get the main message content from the page
- // This selects the main message body content
- const messageBody = document.querySelector('.moz-text-flowed, .moz-text-plain, body');
- if (messageBody) {
- return messageBody.textContent || messageBody.innerText || '';
- }
-
- // Fallback: get the entire body content
- return document.body.textContent || document.body.innerText || '';
-}
-
-function truncateMessageContent(content) {
- // Clean up the content by removing excessive whitespace and newlines
- const cleanedContent = content.replace(/\s+/g, ' ').trim();
-
- // Truncate to a reasonable length for preview
- const maxLength = 500;
- if (cleanedContent.length <= maxLength) {
- return cleanedContent;
- }
-
- return cleanedContent.substring(0, maxLength) + '...';
-}
-
-async function generateAISummary(messageContent) {
- // Clean up the message content
- const cleanedContent = messageContent.replace(/\s+/g, ' ').trim();
-
- // Create a simple summary prompt using localized string
- const summaryPrompt = browser.i18n.getMessage("auto_summary_prompt") + cleanedContent;
-
- // Request AI summary from the background script
- return new Promise((resolve, reject) => {
- // Send message to background script to get AI summary
- browser.runtime.sendMessage({
- command: "generate_summary",
- content: cleanedContent,
- prompt: summaryPrompt
- }, (response) => {
- if (response && response.summary) {
- resolve(response.summary);
- } else if (response && response.error) {
- // Use the localized error message
- reject(new Error(browser.i18n.getMessage("auto_summary_failed")));
- } else {
- reject(new Error(browser.i18n.getMessage("auto_summary_failed")));
- }
- });
- });
-}
-
-// Call the function to show the pane
-showSummaryPane();
\ No newline at end of file
diff --git a/messageDisplay/message-content-styles.css b/messageDisplay/message-content-styles.css
deleted file mode 100644
index 6a4019c8..00000000
--- a/messageDisplay/message-content-styles.css
+++ /dev/null
@@ -1,20 +0,0 @@
-.thunderai-summary-pane {
- background-color: #f0f0f0;
- color: #333;
- font-weight: 400;
- padding: 0.5rem;
- margin-bottom: 1rem;
- border-radius: 4px;
- border: 1px solid #ddd;
-}
-
-.thunderai-summary-title {
- font-weight: bold;
- margin-bottom: 0.5rem;
- color: #d70022;
-}
-
-.thunderai-summary-content {
- font-size: 0.9rem;
- line-height: 1.4;
-}
\ No newline at end of file
diff --git a/mzta-background.js b/mzta-background.js
index f47e746a..fc12acfa 100644
--- a/mzta-background.js
+++ b/mzta-background.js
@@ -63,6 +63,7 @@ import {
getSpecialPrompts
} from './js/mzta-prompts.js';
import { taSpamReport } from './js/mzta-spamreport.js';
+import { taSummaryCache } from './js/mzta-summary-cache.js';
import { taWorkingStatus } from './js/mzta-working-status.js';
import {
addTags_getExclusionList,
@@ -110,17 +111,10 @@ browser.composeScripts.register({
// Register the message display script for all newly opened message tabs.
messenger.messageDisplayScripts.register({
- js: [{ file: "js/mzta-compose-script.js" }],
- css: [{ file: "messageDisplay/message-content-styles.css" }]
+ js: [{ file: "js/mzta-compose-script.js" }]
});
-// Register our new ThunderAI summary script
-messenger.messageDisplayScripts.register({
- js: [{ file: "messageDisplay/message-content-script.js" }],
- css: [{ file: "messageDisplay/message-content-styles.css" }]
-});
-
-// Inject script and CSS in all already open message tabs.
+// Inject script in all already open message tabs.
let openTabs = await messenger.tabs.query();
let messageTabs = openTabs.filter(
tab => ["mail", "messageDisplay"].includes(tab.type)
@@ -133,13 +127,6 @@ for (let messageTab of messageTabs) {
await browser.tabs.executeScript(messageTab.id, {
file: "js/mzta-compose-script.js"
})
- // Inject our ThunderAI summary script
- await browser.tabs.executeScript(messageTab.id, {
- file: "messageDisplay/message-content-script.js"
- })
- await browser.tabs.insertCSS(messageTab.id, {
- file: "messageDisplay/message-content-styles.css"
- });
} catch (error) {
console.error("[ThunderAI] Error injecting message display script:", error);
console.error("[ThunderAI] Message tab:", messageTab.url);
@@ -248,38 +235,60 @@ messenger.runtime.onMessage.addListener((message, sender, sendResponse) => {
// handler function.
if (message && message.hasOwnProperty("command")){
switch (message.command) {
- case 'generate_summary':
- async function _generate_summary(message) {
+ case 'initSummary':
+ async function _initSummary() {
try {
- // Get user preferences for AI connection
- let prefs = await browser.storage.sync.get({
- connection_type: prefs_default.connection_type,
- chatgpt_model: prefs_default.chatgpt_model,
- chatgpt_api_key: prefs_default.chatgpt_api_key,
- do_debug: prefs_default.do_debug
- });
+ let tabId = sender.tab.id;
+ let prefs = await browser.storage.sync.get({ summarize_auto: 0 });
+ if (prefs.summarize_auto === 0) return;
- // Use the existing ThunderAI infrastructure
- // We need to adapt to the new v3.8.0 settings structure
- const summary = await generateAISummaryUsingThunderAIInfrastructure(
- message.content,
- message.prompt,
- {
- ...prefs,
- // Add the dynamic settings that the new system expects
- connection_type: prefs.connection_type,
- // For summary, we don't have specific integration settings yet,
- // so we'll use the global connection type
- }
- );
+ let message = await browser.messageDisplay.getDisplayedMessage(tabId);
+ if (!message) return;
- return { summary: summary };
- } catch (error) {
- console.error("[ThunderAI] Error generating summary:", error);
- return { error: "Failed to generate AI summary. Please confirm your settings and try again." };
+ let cachedSummary = await taSummaryCache.loadSummary(message.headerMessageId);
+ if (cachedSummary && !cachedSummary.error) {
+ browser.tabs.sendMessage(tabId, { command: "showSummary", data: cachedSummary });
+ return;
+ }
+
+ if (await taSummaryCache.isProcessing(message.headerMessageId)) {
+ browser.tabs.sendMessage(tabId, { command: "showSummaryGenerating" });
+ return;
+ }
+
+ if (prefs.summarize_auto === 1) {
+ browser.tabs.sendMessage(tabId, { command: "showSummaryButton", headerMessageId: message.headerMessageId });
+ } else if (prefs.summarize_auto === 2) {
+ _generateSummaryForMessage(message.headerMessageId, tabId);
+ }
+ } catch (e) {
+ taLog.error("Error in initSummary: " + e);
}
}
- return _generate_summary(message);
+ _initSummary();
+ break;
+ case 'triggerSummaryGeneration':
+ async function _triggerSummaryGeneration(message) {
+ let tabId = sender.tab.id;
+ browser.tabs.sendMessage(tabId, { command: "showSummaryGenerating" });
+ await _generateSummaryForMessage(message.headerMessageId, tabId);
+ }
+ _triggerSummaryGeneration(message);
+ break;
+ case 'generate_summary':
+ async function _generate_summary(message) {
+ await _generateSummaryForMessage(message.headerMessageId, message.tabId);
+ }
+ _generate_summary(message);
+ break;
+ case 'refreshSummary':
+ async function _refreshSummary(message) {
+ let tabId = sender.tab.id;
+ await taSummaryCache.removeSummary(message.headerMessageId);
+ await _generateSummaryForMessage(message.headerMessageId, tabId);
+ }
+ _refreshSummary(message);
+ break;
// case 'chatgpt_open':
// openChatGPT(message.prompt,message.action,message.tabId);
// return true;
@@ -433,10 +442,83 @@ messenger.runtime.onMessage.addListener((message, sender, sendResponse) => {
break;
}
}
- // Return false if the message was not handled by this listener.
return false;
});
+async function _generateSummaryForMessage(headerMessageId, tabId) {
+ try {
+ let prefs = await browser.storage.sync.get({
+ connection_type: prefs_default.connection_type,
+ do_debug: prefs_default.do_debug,
+ default_chatgpt_lang: prefs_default.default_chatgpt_lang,
+ ...getDynamicSettingsDefaults(['use_specific_integration', 'connection_type'])
+ });
+
+ let cachedSummary = await taSummaryCache.loadSummary(headerMessageId);
+ if (cachedSummary && !cachedSummary.error) {
+ browser.tabs.sendMessage(tabId, { command: "showSummary", data: cachedSummary });
+ return;
+ }
+
+ if (await taSummaryCache.isProcessing(headerMessageId)) {
+ browser.tabs.sendMessage(tabId, { command: "showSummaryGenerating" });
+ return;
+ }
+
+ await taSummaryCache.setProcessing(headerMessageId);
+ browser.tabs.sendMessage(tabId, { command: "showSummaryGenerating" });
+
+ const messageResult = await browser.messages.query({ headerMessageId: headerMessageId });
+ if (!messageResult || messageResult.messages.length === 0) {
+ await taSummaryCache.saveError(headerMessageId, "Message not found");
+ browser.tabs.sendMessage(tabId, { command: "showSummary", data: { error: true, message: "Message not found" } });
+ return;
+ }
+
+ const fullMessage = await browser.messages.getFull(messageResult.messages[0].id);
+ const mailBody = getMailBody(fullMessage);
+ let bodyText = htmlBodyToPlainText(mailBody.html);
+ if (bodyText.length === 0) {
+ bodyText = mailBody.text.replace(/\s+/g, ' ').trim();
+ }
+
+ const promptText = browser.i18n.getMessage('auto_summary_prompt') + bodyText;
+
+ const connectionType = getConnectionType(prefs, {}, 'summarize');
+
+ if (connectionType === 'chatgpt_web') {
+ const errorMsg = browser.i18n.getMessage('summarize_chatgpt_web_not_supported');
+ await taSummaryCache.saveError(headerMessageId, errorMsg);
+ browser.tabs.sendMessage(tabId, { command: "showSummary", data: { error: true, message: errorMsg } });
+ return;
+ }
+
+ const cmd = new mzta_specialCommand({
+ prompt: promptText,
+ llm: connectionType,
+ do_debug: prefs.do_debug,
+ config: {}
+ });
+
+ await cmd.initWorker();
+ const aiResponse = await cmd.sendPrompt();
+ const cleanedSummary = aiResponse.replace(/\s+/g, ' ').trim();
+
+ const summaryData = {
+ summary: cleanedSummary,
+ summary_date: new Date(),
+ headerMessageId: headerMessageId
+ };
+ await taSummaryCache.saveSummary(summaryData, headerMessageId);
+ browser.tabs.sendMessage(tabId, { command: "showSummary", data: summaryData });
+
+ } catch (error) {
+ console.error("[ThunderAI] Error generating summary:", error);
+ await taSummaryCache.saveError(headerMessageId, error.message || String(error));
+ browser.tabs.sendMessage(tabId, { command: "showSummary", data: { error: true, message: error.message || "Failed to generate summary" } });
+ }
+}
+
// Listen for messages from ThunderAI-Sparks
browser.runtime.onMessageExternal.addListener((message, sender, sendResponse) => {
switch (message.action) {
@@ -1392,77 +1474,3 @@ async function processEmails(args) {
browser.messages.onNewMailReceived.addListener(newEmailListener, !prefs_init.add_tags_auto_only_inbox);
-
-/**
- * AI summary generation function using ThunderAI infrastructure
- */
-async function generateAISummaryUsingThunderAIInfrastructure(content, prompt, prefs) {
- // Import the special command class
- const { mzta_specialCommand } = await import('./js/mzta-special-commands.js');
-
- // Create a prompt config for summary (similar to how other features do it)
- // This adapts to the new v3.8.0 dynamic settings system
- const summaryPromptConfig = {
- id: 'auto_summary',
- name: 'Auto Summary',
- model: '', // Model will be determined by getConnectionType
- connection_type: prefs.connection_type
- };
-
- // Determine which LLM to use based on user preferences using the new v3.8.0 pattern
- const llmType = getConnectionType(prefs, summaryPromptConfig, 'auto_summary');
-
- // Get the appropriate model based on the connection type using dynamic settings
- let model = '';
- if (prefs.connection_type === 'chatgpt_api') {
- model = prefs.chatgpt_model;
- } else if (prefs.connection_type === 'ollama_api') {
- model = prefs.ollama_model;
- } else if (prefs.connection_type === 'openai_comp_api') {
- model = prefs.openai_comp_model;
- } else if (prefs.connection_type === 'google_gemini_api') {
- model = prefs.google_gemini_model;
- } else if (prefs.connection_type === 'anthropic_api') {
- model = prefs.anthropic_model;
- }
-
- // Create a special command instance with the correct v3.8.0 pattern
- const summaryCommand = new mzta_specialCommand({
- prompt: prompt,
- llm: llmType,
- custom_model: model,
- do_debug: prefs.do_debug,
- config: summaryPromptConfig
- });
-
- try {
- // Initialize the worker
- await summaryCommand.initWorker();
-
- // Send the prompt and get the AI response
- const aiResponse = await summaryCommand.sendPrompt();
-
- // Clean up the response - extract just the summary content
- const cleanedResponse = cleanAISummaryResponse(aiResponse);
-
- return cleanedResponse;
- } catch (error) {
- console.error("[ThunderAI] Error in AI summary generation:", error);
- throw error; // Re-throw to be handled by the caller
- }
-}
-
-/**
- * Helper function to clean AI response
- */
-function cleanAISummaryResponse(response) {
- // Remove any markdown formatting or code blocks
- let cleaned = response.replace(/```[\s\S]*?```/g, '');
- cleaned = cleaned.replace(/[\*#_~`]/g, '');
- cleaned = cleaned.replace(/\s+/g, ' ').trim();
-
- // Remove any "Summary:" prefixes that the AI might add
- cleaned = cleaned.replace(/^Summary:\s*/i, '');
-
- return cleaned;
-}
diff --git a/options/mzta-options-default.js b/options/mzta-options-default.js
index 23957310..a961db22 100644
--- a/options/mzta-options-default.js
+++ b/options/mzta-options-default.js
@@ -134,7 +134,7 @@ export const prefs_default = {
spamfilter: false,
spamfilter_threshold: 70,
spamfilter_enabled_accounts: [],
- auto_summary_enabled: false, // Enable automatic AI summarization for message previews
+ summarize_auto: 0, // 0: disabled, 1: manual button, 2: automatic
spamfilter_show_msg_panel: true,
summarize: false,
...generated_prefs
diff --git a/options/mzta-options.html b/options/mzta-options.html
index 08a143fa..c809c1e3 100644
--- a/options/mzta-options.html
+++ b/options/mzta-options.html
@@ -125,17 +125,6 @@
-
- |
-
-
- |
-
__MSG_prefs_OptionText_add_tags__
|
diff --git a/pages/summarize/mzta-summarize.html b/pages/summarize/mzta-summarize.html
index a8db111d..ddb5cf41 100644
--- a/pages/summarize/mzta-summarize.html
+++ b/pages/summarize/mzta-summarize.html
@@ -26,6 +26,19 @@
|
+
+ | __MSG_prefs_OptionText_summarize_auto__ |
+
+
+ |
+
diff --git a/pages/summarize/mzta-summarize.js b/pages/summarize/mzta-summarize.js
index b41d9084..5ecf11dd 100644
--- a/pages/summarize/mzta-summarize.js
+++ b/pages/summarize/mzta-summarize.js
@@ -194,8 +194,11 @@ function saveOptions(e) {
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;
+ if (element.id === 'summarize_auto') {
+ options[element.id] = parseInt(element.value, 10);
+ } else {
+ options[element.id] = element.value;
+ }
break;
case 'textarea':
options[element.id] = normalizeStringList(element.value);
@@ -231,10 +234,13 @@ async function restoreOptions() {
break;
default:
if (element.tagName === 'SELECT') {
- let default_select_value = '';
- const restoreValue = result[element.id] || default_select_value;
+ let default_select_value = 0;
+ if (element.id === 'summarize_auto') {
+ default_select_value = prefs_default.summarize_auto;
+ }
+ const restoreValue = result[element.id] ?? default_select_value;
// Check if option exists
- let optionExists = Array.from(element.options).some(opt => opt.value === restoreValue);
+ let optionExists = Array.from(element.options).some(opt => opt.value === String(restoreValue));
// If it doesn't exist and restoreValue is not empty, create it
if (!optionExists && restoreValue !== '') {
let newOption = new Option(restoreValue, restoreValue);
@@ -243,7 +249,7 @@ async function restoreOptions() {
// Set value
element.value = restoreValue;
if (element.value === '') {
- element.selectedIndex = -1;
+ element.selectedIndex = 0;
}
if (element.tomselect) {
element.tomselect.setValue(element.value, true);