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:
Ronald Klarenbeek 2026-02-24 22:17:47 +01:00
parent 7e758a51e6
commit 2df835d46e
10 changed files with 450 additions and 266 deletions

View file

@ -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": ""
}
}

View file

@ -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
View 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;
}
};

View file

@ -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();

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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

View file

@ -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" />
&nbsp;<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>

View file

@ -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 -->

View file

@ -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);