code refactored to have one method to calculate the final prompt. see #580

This commit is contained in:
Mic 2026-03-24 00:01:00 +01:00
parent a505f22261
commit b08b8563d0
5 changed files with 67 additions and 104 deletions

View file

@ -1900,9 +1900,6 @@
"auto_summary_failed": { "auto_summary_failed": {
"message": "Failed to generate AI summary. Please confirm your settings and try again." "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": { "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!", "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": "" "description": ""

View file

@ -87,7 +87,7 @@ mzta-background.js (checks summarize_auto + summarize_display_mode prefs)
| `js/mzta-prompts.js` | Prompt definitions (built-in) and custom prompt loading | | `js/mzta-prompts.js` | Prompt definitions (built-in) and custom prompt loading |
| `js/mzta-placeholders.js` | Placeholder definitions and resolution logic | | `js/mzta-placeholders.js` | Placeholder definitions and resolution logic |
| `js/mzta-utils.js` | General utilities (email parsing, storage helpers, etc.) | | `js/mzta-utils.js` | General utilities (email parsing, storage helpers, etc.) |
| `js/mzta-utils-prompt.js` | Prompt-specific utilities (text truncation, lang injection) | | `js/mzta-utils-prompt.js` | Prompt-specific utilities (text truncation, lang injection, `buildSummaryPrompt()` for unified summary prompt assembly) |
| `js/mzta-compose-script.js` | Content script for compose and message display: injects AI response into compose window, renders summary/spam banners in message display | | `js/mzta-compose-script.js` | Content script for compose and message display: injects AI response into compose window, renders summary/spam banners in message display |
| `js/mzta-chatgpt.js` | ChatGPT Web integration (opens browser window, reads DOM) | | `js/mzta-chatgpt.js` | ChatGPT Web integration (opens browser window, reads DOM) |
| `js/mzta-special-commands.js` | Handles special prompt actions (add_tags, calendar, task) | | `js/mzta-special-commands.js` | Handles special prompt actions (add_tags, calendar, task) |

View file

@ -74,13 +74,17 @@ The summarize feature uses two distinct prompt pathways:
- Default prompt texts are stored as i18n keys: `prompt_summarize_full_text`, `prompt_summarize_email_template_full_text`, `prompt_summarize_email_separator_full_text` - Default prompt texts are stored as i18n keys: `prompt_summarize_full_text`, `prompt_summarize_email_template_full_text`, `prompt_summarize_email_separator_full_text`
**Inline Summary on Message Display** (automatic or manual per `summarize_auto` pref): **Inline Summary on Message Display** (automatic or manual per `summarize_auto` pref):
- Uses a single i18n string `auto_summary_prompt` concatenated with the message body text - Uses the same 3 special prompts as webchat mode, via `taPromptUtils.buildSummaryPrompt()` in `js/mzta-utils-prompt.js`
- Does **not** use the 3 special prompts above
- Does **not** support `chatgpt_web` connection type (shows error if configured) - Does **not** support `chatgpt_web` connection type (shows error if configured)
- Result is rendered as a styled banner at the top of the message body via `mzta-compose-script.js` - Result is rendered as a styled banner at the top of the message body via `mzta-compose-script.js`
- Banner includes a refresh button (↻) to regenerate the summary - Banner includes a refresh button (↻) to regenerate the summary
- Cached per-message via `taSummaryStore` / `taStorage` (max 100 entries) - Cached per-message via `taSummaryStore` / `taStorage` (max 100 entries)
**Unified Prompt Building** — `taPromptUtils.buildSummaryPrompt(messageDataArray)`:
- All summary paths (inline, webchat single, webchat multi) use this single method
- Accepts an array of `{ message, fullMessage }` entries
- Returns `{ promptText, promptInfo }` where `promptInfo` is the `prompt_summarize` prompt object
## Prompt Types Reference ## Prompt Types Reference
``` ```

View file

@ -17,7 +17,12 @@
*/ */
import { placeholdersUtils } from './mzta-placeholders.js'; import { placeholdersUtils } from './mzta-placeholders.js';
import { extractJsonObject } from './mzta-utils.js'; import {
extractJsonObject,
getMailBody,
htmlBodyToPlainText
} from './mzta-utils.js';
import { getSpecialPrompts } from './mzta-prompts.js';
import { prefs_default } from '../options/mzta-options-default.js'; import { prefs_default } from '../options/mzta-options-default.js';
export const taPromptUtils = { export const taPromptUtils = {
@ -126,6 +131,48 @@ export const taPromptUtils = {
return chatgpt_lang; return chatgpt_lang;
}, },
async buildSummaryPrompt(messageDataArray) {
const specialPrompts = await getSpecialPrompts();
const prompt = specialPrompts.find(p => p.id === 'prompt_summarize');
const prompt_email = specialPrompts.find(p => p.id === 'prompt_summarize_email_template');
const prompt_email_separator = specialPrompts.find(p => p.id === 'prompt_summarize_email_separator');
const chatgpt_lang = await taPromptUtils.getDefaultLang(prompt);
const prompt_string = await taPromptUtils.preparePrompt({
curr_prompt: prompt,
chatgpt_lang: chatgpt_lang,
});
const prompt_email_separator_string = await taPromptUtils.preparePrompt({
curr_prompt: prompt_email_separator,
chatgpt_lang: chatgpt_lang,
});
const messages_list = [];
for (let entry of messageDataArray) {
const bodyHtml = getMailBody(entry.fullMessage);
let bodyText = htmlBodyToPlainText(bodyHtml.html);
if (bodyText.length === 0) {
bodyText = bodyHtml.text || '';
}
messages_list.push(await taPromptUtils.preparePrompt({
curr_prompt: prompt_email,
curr_message: entry.message,
chatgpt_lang: chatgpt_lang,
body_text: bodyText,
subject_text: entry.fullMessage.headers.subject,
msg_text: bodyHtml,
}));
}
const messages_string = messages_list.join(prompt_email_separator_string);
const promptText = prompt_string + prompt_email_separator_string + messages_string;
return { promptText, promptInfo: prompt };
},
/** /**
* Extracts tags from the response text. * Extracts tags from the response text.
* @param {string} response_text - The response text from which to extract tags. * @param {string} response_text - The response text from which to extract tags.

View file

@ -58,8 +58,7 @@ import {
import { taPromptUtils } from './js/mzta-utils-prompt.js'; import { taPromptUtils } from './js/mzta-utils-prompt.js';
import { mzta_specialCommand } from './js/mzta-special-commands.js'; import { mzta_specialCommand } from './js/mzta-special-commands.js';
import { import {
getSpamFilterPrompt, getSpamFilterPrompt
getSpecialPrompts
} from './js/mzta-prompts.js'; } from './js/mzta-prompts.js';
import { taSpamReport } from './js/mzta-spamreport.js'; import { taSpamReport } from './js/mzta-spamreport.js';
import { taSummaryStore } from './js/mzta-summarystore.js'; import { taSummaryStore } from './js/mzta-summarystore.js';
@ -484,13 +483,6 @@ async function _generateSummaryForMessage(headerMessageId, tabId) {
} }
const fullMessage = await browser.messages.getFull(messageResult.messages[0].id); 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'); const connectionType = getConnectionType(prefs, {}, 'summarize');
@ -501,6 +493,8 @@ async function _generateSummaryForMessage(headerMessageId, tabId) {
return; return;
} }
const { promptText } = await taPromptUtils.buildSummaryPrompt([{ message: messageResult.messages[0], fullMessage }]);
const cmd = new mzta_specialCommand({ const cmd = new mzta_specialCommand({
prompt: promptText, prompt: promptText,
llm: connectionType, llm: connectionType,
@ -532,22 +526,6 @@ async function _generateSummaryForMessage(headerMessageId, tabId) {
async function _openSummaryWebchat(headerMessageId, tabId) { async function _openSummaryWebchat(headerMessageId, tabId) {
try { try {
const specialPrompts = await getSpecialPrompts();
const prompt = specialPrompts.find(p => p.id === 'prompt_summarize');
const prompt_email = specialPrompts.find(p => p.id === 'prompt_summarize_email_template');
const prompt_email_separator = specialPrompts.find(p => p.id === 'prompt_summarize_email_separator');
const chatgpt_lang = await taPromptUtils.getDefaultLang(prompt);
const prompt_string = await taPromptUtils.preparePrompt({
curr_prompt: prompt,
chatgpt_lang: chatgpt_lang,
});
const prompt_email_separator_string = await taPromptUtils.preparePrompt({
curr_prompt: prompt_email_separator,
chatgpt_lang: chatgpt_lang,
});
const messageResult = await browser.messages.query({ headerMessageId: headerMessageId }); const messageResult = await browser.messages.query({ headerMessageId: headerMessageId });
if (!messageResult || messageResult.messages.length === 0) { if (!messageResult || messageResult.messages.length === 0) {
console.error("[ThunderAI] _openSummaryWebchat: Message not found for headerMessageId:", headerMessageId); console.error("[ThunderAI] _openSummaryWebchat: Message not found for headerMessageId:", headerMessageId);
@ -556,24 +534,10 @@ async function _openSummaryWebchat(headerMessageId, tabId) {
const curr_message = messageResult.messages[0]; const curr_message = messageResult.messages[0];
const curr_message_full = await browser.messages.getFull(curr_message.id); const curr_message_full = await browser.messages.getFull(curr_message.id);
const curr_body_full_html = getMailBody(curr_message_full);
let curr_body_full_text = htmlBodyToPlainText(curr_body_full_html.html);
if (curr_body_full_text.length === 0) {
curr_body_full_text = curr_body_full_html.text;
}
const email_text = await taPromptUtils.preparePrompt({ const { promptText, promptInfo } = await taPromptUtils.buildSummaryPrompt([{ message: curr_message, fullMessage: curr_message_full }]);
curr_prompt: prompt_email,
curr_message: curr_message,
chatgpt_lang: chatgpt_lang,
body_text: curr_body_full_text,
subject_text: curr_message_full.headers.subject,
msg_text: curr_body_full_html,
});
const full_prompt = prompt_string + prompt_email_separator_string + email_text; openChatGPT(promptText, promptInfo.action, tabId, promptInfo.name, promptInfo.need_custom_text, promptInfo);
openChatGPT(full_prompt, prompt.action, tabId, prompt.name, prompt.need_custom_text, prompt);
} catch (error) { } catch (error) {
console.error("[ThunderAI] Error opening summary webchat:", error); console.error("[ThunderAI] Error opening summary webchat:", error);
} }
@ -1464,63 +1428,14 @@ async function processEmails(args) {
await _generateSummaryForMessage(messageArray[0].headerMessageId, tabId); await _generateSummaryForMessage(messageArray[0].headerMessageId, tabId);
} else { } else {
// Webchat mode, or inline with multiple messages (fallback to webchat) // Webchat mode, or inline with multiple messages (fallback to webchat)
// we have three prompts, the actual assignment for the LLM, the email const messageDataArray = [];
// template prompt, and the email separator prompt
const specialPrompts = await getSpecialPrompts();
const prompt = specialPrompts.find((prompt) => prompt.id === 'prompt_summarize');
const prompt_email = specialPrompts.find((prompt) => prompt.id === 'prompt_summarize_email_template');
const prompt_email_separator = specialPrompts.find((prompt) => prompt.id === 'prompt_summarize_email_separator');
const chatgpt_lang = await taPromptUtils.getDefaultLang(prompt);
// replace placeholders in the prompts the assignment prompt and email
// separator prompt do not have a message as context, so there is only
// limited things to replace
const prompt_string = await taPromptUtils.preparePrompt({
curr_prompt: prompt,
chatgpt_lang: chatgpt_lang,
});
const prompt_email_separator_string = await taPromptUtils.preparePrompt({
curr_prompt: prompt_email_separator,
chatgpt_lang: chatgpt_lang,
});
// assemble all email messages into one string and add the assignment prompt
const messages_list = [];
for (let curr_message of messageArray) { for (let curr_message of messageArray) {
const fullMessage = await browser.messages.getFull(curr_message.id);
// extract body of current message as text messageDataArray.push({ message: curr_message, fullMessage });
const curr_message_full = await browser.messages.getFull(curr_message.id);
const curr_body_full_html = getMailBody(curr_message_full);
let curr_body_full_text = htmlBodyToPlainText(curr_body_full_html.html);
if( curr_body_full_text.length === 0) {
taLog.log("No HTML found in the message body, using plain text...");
curr_body_full_text = curr_message_full.text;
} }
const { promptText, promptInfo } = await taPromptUtils.buildSummaryPrompt(messageDataArray);
messages_list.push(await taPromptUtils.preparePrompt({ openChatGPT(promptText, promptInfo.action, tabId, promptInfo.name, promptInfo.need_custom_text, promptInfo);
curr_prompt: prompt_email,
curr_message: curr_message,
chatgpt_lang: chatgpt_lang,
body_text: curr_body_full_text,
subject_text: curr_message_full.headers.subject,
msg_text: curr_body_full_html,
}));
};
const messages_string = messages_list.join(prompt_email_separator_string);
const full_prompt = prompt_string + prompt_email_separator_string + messages_string;
// send the prompt to the chat interface
openChatGPT(
full_prompt,
prompt.action,
tabId,
prompt.name,
prompt.need_custom_text,
prompt
);
} }
} }