From b08b8563d0d3c3a4c72195eab0cceefcc90ba0d0 Mon Sep 17 00:00:00 2001 From: Mic Date: Tue, 24 Mar 2026 00:01:00 +0100 Subject: [PATCH] code refactored to have one method to calculate the final prompt. see #580 --- _locales/en/messages.json | 3 - claude-spec/01-architecture.md | 2 +- claude-spec/02-prompts.md | 8 ++- js/mzta-utils-prompt.js | 49 ++++++++++++++- mzta-background.js | 109 ++++----------------------------- 5 files changed, 67 insertions(+), 104 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 093c95c7..2e219924 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1900,9 +1900,6 @@ "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": "" diff --git a/claude-spec/01-architecture.md b/claude-spec/01-architecture.md index 7426a4c2..085affed 100644 --- a/claude-spec/01-architecture.md +++ b/claude-spec/01-architecture.md @@ -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-placeholders.js` | Placeholder definitions and resolution logic | | `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-chatgpt.js` | ChatGPT Web integration (opens browser window, reads DOM) | | `js/mzta-special-commands.js` | Handles special prompt actions (add_tags, calendar, task) | diff --git a/claude-spec/02-prompts.md b/claude-spec/02-prompts.md index ad368cb3..aa2b3864 100644 --- a/claude-spec/02-prompts.md +++ b/claude-spec/02-prompts.md @@ -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` **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 -- Does **not** use the 3 special prompts above +- Uses the same 3 special prompts as webchat mode, via `taPromptUtils.buildSummaryPrompt()` in `js/mzta-utils-prompt.js` - 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` - Banner includes a refresh button (↻) to regenerate the summary - 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 ``` diff --git a/js/mzta-utils-prompt.js b/js/mzta-utils-prompt.js index 28978662..c81588c2 100644 --- a/js/mzta-utils-prompt.js +++ b/js/mzta-utils-prompt.js @@ -17,7 +17,12 @@ */ 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'; export const taPromptUtils = { @@ -126,6 +131,48 @@ export const taPromptUtils = { 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. * @param {string} response_text - The response text from which to extract tags. diff --git a/mzta-background.js b/mzta-background.js index 06c6d50b..0138c34b 100644 --- a/mzta-background.js +++ b/mzta-background.js @@ -57,9 +57,8 @@ import { } from './js/mzta-utils.js'; import { taPromptUtils } from './js/mzta-utils-prompt.js'; import { mzta_specialCommand } from './js/mzta-special-commands.js'; -import { - getSpamFilterPrompt, - getSpecialPrompts +import { + getSpamFilterPrompt } from './js/mzta-prompts.js'; import { taSpamReport } from './js/mzta-spamreport.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 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'); @@ -501,6 +493,8 @@ async function _generateSummaryForMessage(headerMessageId, tabId) { return; } + const { promptText } = await taPromptUtils.buildSummaryPrompt([{ message: messageResult.messages[0], fullMessage }]); + const cmd = new mzta_specialCommand({ prompt: promptText, llm: connectionType, @@ -532,22 +526,6 @@ async function _generateSummaryForMessage(headerMessageId, tabId) { async function _openSummaryWebchat(headerMessageId, tabId) { 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 }); if (!messageResult || messageResult.messages.length === 0) { 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_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({ - 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 { promptText, promptInfo } = await taPromptUtils.buildSummaryPrompt([{ message: curr_message, fullMessage: curr_message_full }]); - const full_prompt = prompt_string + prompt_email_separator_string + email_text; - - openChatGPT(full_prompt, prompt.action, tabId, prompt.name, prompt.need_custom_text, prompt); + openChatGPT(promptText, promptInfo.action, tabId, promptInfo.name, promptInfo.need_custom_text, promptInfo); } catch (error) { console.error("[ThunderAI] Error opening summary webchat:", error); } @@ -1464,63 +1428,14 @@ async function processEmails(args) { await _generateSummaryForMessage(messageArray[0].headerMessageId, tabId); } else { // Webchat mode, or inline with multiple messages (fallback to webchat) - // we have three prompts, the actual assignment for the LLM, the email - // 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 = []; + const messageDataArray = []; for (let curr_message of messageArray) { + const fullMessage = await browser.messages.getFull(curr_message.id); + messageDataArray.push({ message: curr_message, fullMessage }); + } + const { promptText, promptInfo } = await taPromptUtils.buildSummaryPrompt(messageDataArray); - // extract body of current message as text - 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; - } - - messages_list.push(await taPromptUtils.preparePrompt({ - 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 - ); + openChatGPT(promptText, promptInfo.action, tabId, promptInfo.name, promptInfo.need_custom_text, promptInfo); } }