From 2df835d46e59672e7357818028bde753486fcfd8 Mon Sep 17 00:00:00 2001 From: Ronald Klarenbeek Date: Tue, 24 Feb 2026 22:17:47 +0100 Subject: [PATCH] Fix auto-summary: inline styles, refresh button, and proper headerMessageId passing - Move CSS inline as requested by maintainer (no external CSS file) - Add fixed font sizing (14px) to prevent inheritance from email content - Add refresh button to regenerate summaries - Fix refresh bug: use summaryData.headerMessageId instead of message.headerMessageId - Remove deleted message display script and CSS files --- _locales/en/messages.json | 48 ++++- js/mzta-compose-script.js | 138 +++++++++++- js/mzta-summary-cache.js | 115 ++++++++++ messageDisplay/message-content-script.js | 107 ---------- messageDisplay/message-content-styles.css | 20 -- mzta-background.js | 244 +++++++++++----------- options/mzta-options-default.js | 2 +- options/mzta-options.html | 11 - pages/summarize/mzta-summarize.html | 13 ++ pages/summarize/mzta-summarize.js | 18 +- 10 files changed, 450 insertions(+), 266 deletions(-) create mode 100644 js/mzta-summary-cache.js delete mode 100644 messageDisplay/message-content-script.js delete mode 100644 messageDisplay/message-content-styles.css 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);