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
This commit is contained in:
parent
7e758a51e6
commit
2df835d46e
10 changed files with 450 additions and 266 deletions
|
|
@ -1910,5 +1910,49 @@
|
||||||
"spam_check_in_progress": {
|
"spam_check_in_progress": {
|
||||||
"message": "Spam check in progress...",
|
"message": "Spam check in progress...",
|
||||||
"description": ""
|
"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": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -709,6 +709,141 @@ switch (message.command) {
|
||||||
document.body.insertBefore(container, document.body.firstChild);
|
document.body.insertBefore(container, document.body.firstChild);
|
||||||
return Promise.resolve(true);
|
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:
|
default:
|
||||||
// do nothing
|
// do nothing
|
||||||
return Promise.resolve(false);
|
return Promise.resolve(false);
|
||||||
|
|
@ -716,4 +851,5 @@ switch (message.command) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
browser.runtime.sendMessage({ command: "checkSpamReport" });
|
browser.runtime.sendMessage({ command: "checkSpamReport" });
|
||||||
|
browser.runtime.sendMessage({ command: "initSummary" });
|
||||||
115
js/mzta-summary-cache.js
Normal file
115
js/mzta-summary-cache.js
Normal file
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -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();
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -63,6 +63,7 @@ import {
|
||||||
getSpecialPrompts
|
getSpecialPrompts
|
||||||
} from './js/mzta-prompts.js';
|
} from './js/mzta-prompts.js';
|
||||||
import { taSpamReport } from './js/mzta-spamreport.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 { taWorkingStatus } from './js/mzta-working-status.js';
|
||||||
import {
|
import {
|
||||||
addTags_getExclusionList,
|
addTags_getExclusionList,
|
||||||
|
|
@ -110,17 +111,10 @@ browser.composeScripts.register({
|
||||||
|
|
||||||
// Register the message display script for all newly opened message tabs.
|
// Register the message display script for all newly opened message tabs.
|
||||||
messenger.messageDisplayScripts.register({
|
messenger.messageDisplayScripts.register({
|
||||||
js: [{ file: "js/mzta-compose-script.js" }],
|
js: [{ file: "js/mzta-compose-script.js" }]
|
||||||
css: [{ file: "messageDisplay/message-content-styles.css" }]
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Register our new ThunderAI summary script
|
// Inject script in all already open message tabs.
|
||||||
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.
|
|
||||||
let openTabs = await messenger.tabs.query();
|
let openTabs = await messenger.tabs.query();
|
||||||
let messageTabs = openTabs.filter(
|
let messageTabs = openTabs.filter(
|
||||||
tab => ["mail", "messageDisplay"].includes(tab.type)
|
tab => ["mail", "messageDisplay"].includes(tab.type)
|
||||||
|
|
@ -133,13 +127,6 @@ for (let messageTab of messageTabs) {
|
||||||
await browser.tabs.executeScript(messageTab.id, {
|
await browser.tabs.executeScript(messageTab.id, {
|
||||||
file: "js/mzta-compose-script.js"
|
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) {
|
} catch (error) {
|
||||||
console.error("[ThunderAI] Error injecting message display script:", error);
|
console.error("[ThunderAI] Error injecting message display script:", error);
|
||||||
console.error("[ThunderAI] Message tab:", messageTab.url);
|
console.error("[ThunderAI] Message tab:", messageTab.url);
|
||||||
|
|
@ -248,38 +235,60 @@ messenger.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||||
// handler function.
|
// handler function.
|
||||||
if (message && message.hasOwnProperty("command")){
|
if (message && message.hasOwnProperty("command")){
|
||||||
switch (message.command) {
|
switch (message.command) {
|
||||||
case 'generate_summary':
|
case 'initSummary':
|
||||||
async function _generate_summary(message) {
|
async function _initSummary() {
|
||||||
try {
|
try {
|
||||||
// Get user preferences for AI connection
|
let tabId = sender.tab.id;
|
||||||
let prefs = await browser.storage.sync.get({
|
let prefs = await browser.storage.sync.get({ summarize_auto: 0 });
|
||||||
connection_type: prefs_default.connection_type,
|
if (prefs.summarize_auto === 0) return;
|
||||||
chatgpt_model: prefs_default.chatgpt_model,
|
|
||||||
chatgpt_api_key: prefs_default.chatgpt_api_key,
|
|
||||||
do_debug: prefs_default.do_debug
|
|
||||||
});
|
|
||||||
|
|
||||||
// Use the existing ThunderAI infrastructure
|
let message = await browser.messageDisplay.getDisplayedMessage(tabId);
|
||||||
// We need to adapt to the new v3.8.0 settings structure
|
if (!message) return;
|
||||||
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
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return { summary: summary };
|
let cachedSummary = await taSummaryCache.loadSummary(message.headerMessageId);
|
||||||
} catch (error) {
|
if (cachedSummary && !cachedSummary.error) {
|
||||||
console.error("[ThunderAI] Error generating summary:", error);
|
browser.tabs.sendMessage(tabId, { command: "showSummary", data: cachedSummary });
|
||||||
return { error: "Failed to generate AI summary. Please confirm your settings and try again." };
|
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':
|
// case 'chatgpt_open':
|
||||||
// openChatGPT(message.prompt,message.action,message.tabId);
|
// openChatGPT(message.prompt,message.action,message.tabId);
|
||||||
// return true;
|
// return true;
|
||||||
|
|
@ -433,10 +442,83 @@ messenger.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Return false if the message was not handled by this listener.
|
|
||||||
return false;
|
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
|
// Listen for messages from ThunderAI-Sparks
|
||||||
browser.runtime.onMessageExternal.addListener((message, sender, sendResponse) => {
|
browser.runtime.onMessageExternal.addListener((message, sender, sendResponse) => {
|
||||||
switch (message.action) {
|
switch (message.action) {
|
||||||
|
|
@ -1392,77 +1474,3 @@ async function processEmails(args) {
|
||||||
|
|
||||||
|
|
||||||
browser.messages.onNewMailReceived.addListener(newEmailListener, !prefs_init.add_tags_auto_only_inbox);
|
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;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,7 @@ export const prefs_default = {
|
||||||
spamfilter: false,
|
spamfilter: false,
|
||||||
spamfilter_threshold: 70,
|
spamfilter_threshold: 70,
|
||||||
spamfilter_enabled_accounts: [],
|
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,
|
spamfilter_show_msg_panel: true,
|
||||||
summarize: false,
|
summarize: false,
|
||||||
...generated_prefs
|
...generated_prefs
|
||||||
|
|
|
||||||
|
|
@ -125,17 +125,6 @@
|
||||||
</label>
|
</label>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<td style="width:20em"><label>
|
|
||||||
<span class="opt_title">__MSG_prefs_OptionText_auto_summary__</span>
|
|
||||||
</label></td>
|
|
||||||
<td>
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" id="auto_summary_enabled" name="auto_summary_enabled" class="option-input" />
|
|
||||||
<span>__MSG_prefs_OptionText_auto_summary_Info__</span>
|
|
||||||
</label>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr class="add_tags_tr">
|
<tr class="add_tags_tr">
|
||||||
<td><span class="opt_title">__MSG_prefs_OptionText_add_tags__</span>
|
<td><span class="opt_title">__MSG_prefs_OptionText_add_tags__</span>
|
||||||
<br><button id="btnManageTagsInfo" class="btn_small">__MSG_prefs_OptionText_btnManageTagsInfo__</button></td>
|
<br><button id="btnManageTagsInfo" class="btn_small">__MSG_prefs_OptionText_btnManageTagsInfo__</button></td>
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,19 @@
|
||||||
</tr>
|
</tr>
|
||||||
<tr id="connection_ui_anchor"></tr>
|
<tr id="connection_ui_anchor"></tr>
|
||||||
<tr id="connection_ui_end"><td colspan="2"></td></tr>
|
<tr id="connection_ui_end"><td colspan="2"></td></tr>
|
||||||
|
<tr class="summarize_tr">
|
||||||
|
<td><span class="opt_title">__MSG_prefs_OptionText_summarize_auto__</span></td>
|
||||||
|
<td>
|
||||||
|
<label>
|
||||||
|
<select id="summarize_auto" name="summarize_auto" class="option-input">
|
||||||
|
<option value="0">__MSG_prefs_OptionText_summarize_auto_disabled__</option>
|
||||||
|
<option value="1">__MSG_prefs_OptionText_summarize_auto_manual__</option>
|
||||||
|
<option value="2">__MSG_prefs_OptionText_summarize_auto_automatic__</option>
|
||||||
|
</select>
|
||||||
|
<br>__MSG_prefs_OptionText_summarize_auto_Info__
|
||||||
|
</label>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<!-- PROMPTS -->
|
<!-- PROMPTS -->
|
||||||
|
|
|
||||||
|
|
@ -194,8 +194,11 @@ function saveOptions(e) {
|
||||||
options[element.id] = element.value.trim();
|
options[element.id] = element.value.trim();
|
||||||
break;
|
break;
|
||||||
case 'select-one':
|
case 'select-one':
|
||||||
// console.log(">>>>>>>>>> Saving option [select-one]: " + element.id + " = " + element.value);
|
if (element.id === 'summarize_auto') {
|
||||||
options[element.id] = element.value;
|
options[element.id] = parseInt(element.value, 10);
|
||||||
|
} else {
|
||||||
|
options[element.id] = element.value;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 'textarea':
|
case 'textarea':
|
||||||
options[element.id] = normalizeStringList(element.value);
|
options[element.id] = normalizeStringList(element.value);
|
||||||
|
|
@ -231,10 +234,13 @@ async function restoreOptions() {
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
if (element.tagName === 'SELECT') {
|
if (element.tagName === 'SELECT') {
|
||||||
let default_select_value = '';
|
let default_select_value = 0;
|
||||||
const restoreValue = result[element.id] || default_select_value;
|
if (element.id === 'summarize_auto') {
|
||||||
|
default_select_value = prefs_default.summarize_auto;
|
||||||
|
}
|
||||||
|
const restoreValue = result[element.id] ?? default_select_value;
|
||||||
// Check if option exists
|
// 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 it doesn't exist and restoreValue is not empty, create it
|
||||||
if (!optionExists && restoreValue !== '') {
|
if (!optionExists && restoreValue !== '') {
|
||||||
let newOption = new Option(restoreValue, restoreValue);
|
let newOption = new Option(restoreValue, restoreValue);
|
||||||
|
|
@ -243,7 +249,7 @@ async function restoreOptions() {
|
||||||
// Set value
|
// Set value
|
||||||
element.value = restoreValue;
|
element.value = restoreValue;
|
||||||
if (element.value === '') {
|
if (element.value === '') {
|
||||||
element.selectedIndex = -1;
|
element.selectedIndex = 0;
|
||||||
}
|
}
|
||||||
if (element.tomselect) {
|
if (element.tomselect) {
|
||||||
element.tomselect.setValue(element.value, true);
|
element.tomselect.setValue(element.value, true);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue