Merge pull request #579 from yonie/main
feat(summary): add AI-powered message summary feature
This commit is contained in:
commit
d2604518ea
7 changed files with 479 additions and 11 deletions
|
|
@ -1885,6 +1885,24 @@
|
|||
"message": "You have denied the optional permission needed to fetch models for this integration.",
|
||||
"description": ""
|
||||
},
|
||||
"prefs_OptionText_auto_summary": {
|
||||
"message": "Enable automatic AI summarization for message previews"
|
||||
},
|
||||
"prefs_OptionText_auto_summary_Info": {
|
||||
"message": "If checked, ThunderAI will automatically generate and display AI summaries above email messages when they are opened. Note that this means all messages you preview will immediately be sent to the configured AI service."
|
||||
},
|
||||
"auto_summary_title": {
|
||||
"message": "ThunderAI Summary"
|
||||
},
|
||||
"auto_summary_generating": {
|
||||
"message": "Generating AI summary..."
|
||||
},
|
||||
"auto_summary_failed": {
|
||||
"message": "Failed to generate AI summary. Please confirm your settings and try again."
|
||||
},
|
||||
"auto_summary_prompt": {
|
||||
"message": "Please provide a concise summary of the following email message. The summary should be 3-5 sentences maximum and capture the main points:\n\n"
|
||||
},
|
||||
"customPrompts_export_include_api_settings": {
|
||||
"message": "Do you want to include the API settings in the export? Be aware that also the API Key will be saved in the file!",
|
||||
"description": ""
|
||||
|
|
@ -1913,6 +1931,50 @@
|
|||
"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": ""
|
||||
},
|
||||
"antispam_by": {
|
||||
"message": "Antispam by",
|
||||
"description": ""
|
||||
|
|
@ -1945,4 +2007,4 @@
|
|||
"message": "Leave empty to use the default position.",
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -719,6 +719,146 @@ 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 existingTrigger = document.getElementById('mzta-summary-trigger');
|
||||
if(existingTrigger) existingTrigger.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;
|
||||
triggerContainer.id = 'mzta-summary-generating';
|
||||
triggerContainer.style.cursor = 'default';
|
||||
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);
|
||||
|
|
@ -726,4 +866,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;
|
||||
}
|
||||
};
|
||||
|
|
@ -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,
|
||||
|
|
@ -111,7 +112,7 @@ browser.composeScripts.register({
|
|||
|
||||
// Register the message display script for all newly opened message tabs.
|
||||
messenger.messageDisplayScripts.register({
|
||||
js: [{ file: "js/mzta-compose-script.js" }],
|
||||
js: [{ file: "js/mzta-compose-script.js" }]
|
||||
});
|
||||
|
||||
browser.contentScripts.register({
|
||||
|
|
@ -216,6 +217,59 @@ 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;
|
||||
await _generateSummaryForMessage(message.headerMessageId, tabId);
|
||||
}
|
||||
_triggerSummaryGeneration(message);
|
||||
break;
|
||||
case 'generate_summary':
|
||||
async function _generate_summary(message) {
|
||||
await _generateSummaryForMessage(message.headerMessageId, message.tabId);
|
||||
}
|
||||
_generate_summary(message);
|
||||
break;
|
||||
case 'refreshSummary':
|
||||
async function _refreshSummary(message) {
|
||||
let tabId = sender.tab.id;
|
||||
await taSummaryCache.removeSummary(message.headerMessageId);
|
||||
await _generateSummaryForMessage(message.headerMessageId, tabId);
|
||||
}
|
||||
_refreshSummary(message);
|
||||
break;
|
||||
// case 'chatgpt_open':
|
||||
// openChatGPT(message.prompt,message.action,message.tabId);
|
||||
// return true;
|
||||
|
|
@ -379,10 +433,86 @@ 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();
|
||||
let cleanedSummary = aiResponse.replace(/```[\s\S]*?```/g, '');
|
||||
cleanedSummary = cleanedSummary.replace(/[\*#_~`]/g, '');
|
||||
cleanedSummary = cleanedSummary.replace(/\s+/g, ' ').trim();
|
||||
cleanedSummary = cleanedSummary.replace(/^Summary:\s*/i, '');
|
||||
|
||||
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) {
|
||||
|
|
@ -1337,4 +1467,4 @@ for (let messageTab of messageTabs) {
|
|||
console.error("[ThunderAI] Error injecting message display script:", error);
|
||||
console.error("[ThunderAI] Message tab:", messageTab.url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,6 +137,7 @@ export const prefs_default = {
|
|||
spamfilter: false,
|
||||
spamfilter_threshold: 70,
|
||||
spamfilter_enabled_accounts: [],
|
||||
summarize_auto: 0, // 0: disabled, 1: manual button, 2: automatic
|
||||
spamfilter_show_msg_panel: true,
|
||||
summarize: false,
|
||||
...generated_prefs
|
||||
|
|
|
|||
|
|
@ -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 -->
|
||||
|
|
|
|||
|
|
@ -196,8 +196,11 @@ function saveOptions(e) {
|
|||
options[element.id] = element.value.trim();
|
||||
break;
|
||||
case 'select-one':
|
||||
// console.log(">>>>>>>>>> Saving option [select-one]: " + element.id + " = " + element.value);
|
||||
options[element.id] = element.value;
|
||||
if (element.id === 'summarize_auto') {
|
||||
options[element.id] = parseInt(element.value, 10);
|
||||
} else {
|
||||
options[element.id] = element.value;
|
||||
}
|
||||
break;
|
||||
case 'textarea':
|
||||
options[element.id] = normalizeStringList(element.value);
|
||||
|
|
@ -233,10 +236,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);
|
||||
|
|
@ -245,7 +251,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