feat(summary): add AI-powered message summary feature
Add new ThunderAI summary functionality that generates concise summaries for email messages using the existing ThunderAI infrastructure. Includes a new content script that creates a summary pane in the message display, associated CSS styling, and backend integration for AI summary generation. The feature shows a loading indicator while generating the summary and falls back to showing truncated message content if the AI generation fails.
This commit is contained in:
parent
b5741c61bb
commit
dcbf9d3dcd
3 changed files with 209 additions and 0 deletions
105
messageDisplay/message-content-script.js
Normal file
105
messageDisplay/message-content-script.js
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
async function showSummaryPane() {
|
||||
// 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 = "ThunderAI Summary";
|
||||
|
||||
// Create a loading indicator
|
||||
const loadingIndicator = document.createElement("div");
|
||||
loadingIndicator.className = "thunderai-summary-content";
|
||||
loadingIndicator.innerText = "Generating AI summary...";
|
||||
|
||||
// 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 = "Failed to generate AI summary. Showing message preview instead.";
|
||||
loadingIndicator.style.color = '#d70022';
|
||||
|
||||
// Fallback to showing truncated message content
|
||||
const messageContent = getMessageContent();
|
||||
loadingIndicator.innerText += "\n\n" + truncateMessageContent(messageContent);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
const summaryPrompt = `Please provide a concise summary of the following email message. The summary should be 3-5 sentences maximum and capture the main points:
|
||||
|
||||
${cleanedContent}
|
||||
|
||||
Summary:`;
|
||||
|
||||
// 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) {
|
||||
reject(new Error(response.error));
|
||||
} else {
|
||||
reject(new Error("Failed to get AI summary"));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Call the function to show the pane
|
||||
showSummaryPane();
|
||||
20
messageDisplay/message-content-styles.css
Normal file
20
messageDisplay/message-content-styles.css
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
.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;
|
||||
}
|
||||
|
|
@ -99,6 +99,13 @@ 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" }]
|
||||
});
|
||||
|
||||
// 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.
|
||||
|
|
@ -114,6 +121,13 @@ 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);
|
||||
|
|
@ -220,6 +234,31 @@ messenger.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|||
// handler function.
|
||||
if (message && message.hasOwnProperty("command")){
|
||||
switch (message.command) {
|
||||
case 'generate_summary':
|
||||
async function _generate_summary(message) {
|
||||
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
|
||||
const summary = await generateAISummaryUsingThunderAIInfrastructure(
|
||||
message.content,
|
||||
message.prompt,
|
||||
prefs
|
||||
);
|
||||
|
||||
return { summary: summary };
|
||||
} catch (error) {
|
||||
console.error("[ThunderAI] Error generating summary:", error);
|
||||
return { error: "Failed to generate AI summary: " + error.message };
|
||||
}
|
||||
}
|
||||
return _generate_summary(message);
|
||||
// case 'chatgpt_open':
|
||||
// openChatGPT(message.prompt,message.action,message.tabId);
|
||||
// return true;
|
||||
|
|
@ -1177,3 +1216,48 @@ try {
|
|||
taLog.log("Using browser.messages.onNewMailReceived.addListener with one agrument for Thunderbird 115.");
|
||||
browser.messages.onNewMailReceived.addListener(newEmailListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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');
|
||||
|
||||
// Determine which LLM to use based on user preferences
|
||||
const llmType = getConnectionType(prefs.connection_type, {}, '');
|
||||
|
||||
// Create a special command instance
|
||||
const summaryCommand = new mzta_specialCommand({
|
||||
prompt: prompt,
|
||||
llm: llmType,
|
||||
custom_model: prefs.chatgpt_model,
|
||||
do_debug: prefs.do_debug
|
||||
});
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue