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": {
|
||||
"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": ""
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
@ -717,3 +852,4 @@ switch (message.command) {
|
|||
});
|
||||
|
||||
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
|
||||
} 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 'initSummary':
|
||||
async function _initSummary() {
|
||||
try {
|
||||
let tabId = sender.tab.id;
|
||||
let prefs = await browser.storage.sync.get({ summarize_auto: 0 });
|
||||
if (prefs.summarize_auto === 0) return;
|
||||
|
||||
let message = await browser.messageDisplay.getDisplayedMessage(tabId);
|
||||
if (!message) return;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
_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) {
|
||||
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
|
||||
});
|
||||
|
||||
// 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
|
||||
await _generateSummaryForMessage(message.headerMessageId, message.tabId);
|
||||
}
|
||||
);
|
||||
|
||||
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." };
|
||||
_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);
|
||||
}
|
||||
}
|
||||
return _generate_summary(message);
|
||||
_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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -125,17 +125,6 @@
|
|||
</label>
|
||||
</td>
|
||||
</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">
|
||||
<td><span class="opt_title">__MSG_prefs_OptionText_add_tags__</span>
|
||||
<br><button id="btnManageTagsInfo" class="btn_small">__MSG_prefs_OptionText_btnManageTagsInfo__</button></td>
|
||||
|
|
|
|||
|
|
@ -26,6 +26,19 @@
|
|||
</tr>
|
||||
<tr id="connection_ui_anchor"></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>
|
||||
|
||||
<!-- PROMPTS -->
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue