summarize_display_mode added. see #580

This commit is contained in:
mic 2026-03-23 23:24:54 +01:00
parent 7bac86cc82
commit 58437ad168
8 changed files with 218 additions and 85 deletions

View file

@ -1951,6 +1951,22 @@
"message": "Choose whether to automatically generate summaries when viewing messages. Requires an API-based connection (not ChatGPT Web).",
"description": ""
},
"prefs_OptionText_summarize_display_mode": {
"message": "Display summary in",
"description": ""
},
"prefs_OptionText_summarize_display_mode_inline": {
"message": "Message pane (inline)",
"description": ""
},
"prefs_OptionText_summarize_display_mode_webchat": {
"message": "Chat window",
"description": ""
},
"prefs_OptionText_summarize_display_mode_Info": {
"message": "Choose where the summary result is displayed. Inline mode shows a summary banner directly in the message pane. Chat window mode opens the AI chat window.",
"description": ""
},
"summarize_title": {
"message": "Summary",
"description": ""

View file

@ -43,19 +43,32 @@ js/mzta-compose-script.js (inserts text into Thunderbird compose window)
### Data Flow: Inline Summary on Message Display
The `summarize_display_mode` preference (`'inline'` or `'webchat'`) controls where
the summary is displayed. The `summarize_auto` preference controls when it is triggered.
- `summarize_auto = 2` (automatic) always generates inline, regardless of `summarize_display_mode`.
- `summarize_auto = 1` (manual button) respects `summarize_display_mode`:
- `'inline'` → button click triggers inline generation
- `'webchat'` → button click opens the AI chat window via `_openSummaryWebchat()`
- Context menu summarize also respects `summarize_display_mode`:
- `'inline'` with a single message → generates inline via `_generateSummaryForMessage()`
- `'webchat'` or multiple messages → opens the AI chat window via `openChatGPT()`
```
User opens/selects a message in Thunderbird
mzta-compose-script.js (sends "initSummary" to background)
mzta-background.js (checks summarize_auto pref)
mzta-background.js (checks summarize_auto + summarize_display_mode prefs)
┌────────────────────────────────────────────────┐
┌──────────────────────────────────────────────────────────
│ summarize_auto = 0 → do nothing │
│ summarize_auto = 1 → show "click to generate" │
│ summarize_auto = 2 → generate immediately │
└────────────────────────────────────────────────┘
↓ (if generating)
│ summarize_auto = 1 → show "click to generate" button │
│ display_mode = inline → click triggers inline gen │
│ display_mode = webchat → click opens chat window │
│ summarize_auto = 2 → generate immediately (always inline)│
└──────────────────────────────────────────────────────────┘
↓ (if generating inline)
taSummaryStore (check cache / set processing)
↓ (cache miss)
mzta-special-commands (via Web Worker, NOT chatgpt_web)

View file

@ -96,6 +96,7 @@ These are generated programmatically at the bottom of `mzta-options-default.js`
| `spamfilter_show_msg_panel` | `true` | Show info panel on spam detection |
| `summarize` | `false` | Enable email summarization |
| `summarize_auto` | `0` | Auto-summarize mode: `0` = disabled, `1` = manual (show "click to generate" button), `2` = automatic (generate on message open) |
| `summarize_display_mode` | `'inline'` | Where to display summaries: `'inline'` = message pane banner, `'webchat'` = AI chat window. Note: `summarize_auto = 2` always uses inline regardless of this setting. |
### Summarize Settings Page (`pages/summarize/`)
@ -106,7 +107,11 @@ The summarize settings page provides:
- `0` (Disabled) — no inline summaries
- `1` (Manual) — shows a "Click to generate summary" button in message display
- `2` (Automatic) — generates summary immediately when message is opened
3. **Three editable prompts** (used by context menu summarize, not inline):
3. **Display mode dropdown** (`summarize_display_mode`) — controls where summaries are shown:
- `'inline'` — summary banner in the message pane (default)
- `'webchat'` — opens the AI chat window
- Note: `summarize_auto = 2` always generates inline regardless of this setting. Context menu summarize with multiple messages always falls back to webchat.
4. **Three editable prompts** (used by context menu summarize and webchat mode):
- Summarize instruction prompt (`prompt_summarize`)
- Email template prompt (`prompt_summarize_email_template`)
- Email separator prompt (`prompt_summarize_email_separator`)

View file

@ -851,7 +851,7 @@ switch (message.command) {
triggerContainer.style.cursor = 'default';
triggerText.textContent = browser.i18n.getMessage("summarize_generating");
browser.runtime.sendMessage({
command: "triggerSummaryGeneration",
command: message.webchat ? "triggerSummaryWebchat" : "triggerSummaryGeneration",
headerMessageId: message.headerMessageId
});
};

View file

@ -222,27 +222,41 @@ messenger.runtime.onMessage.addListener((message, sender, sendResponse) => {
async function _initSummary() {
try {
let tabId = sender.tab.id;
let prefs = await browser.storage.sync.get({ summarize_auto: 0 });
let prefs = await browser.storage.sync.get({ summarize_auto: 0, summarize_display_mode: prefs_default.summarize_display_mode });
if (prefs.summarize_auto === 0) return;
let message = await browser.messageDisplay.getDisplayedMessage(tabId);
if (!message) return;
// Auto mode (summarize_auto === 2) always generates inline
if (prefs.summarize_auto === 2) {
let cachedSummary = await summaryStore.loadSummary(message.headerMessageId);
if (cachedSummary && !cachedSummary.error) {
browser.tabs.sendMessage(tabId, { command: "showSummary", data: cachedSummary });
return;
}
if (await summaryStore.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);
return;
}
// Manual button mode (summarize_auto === 1)
if (prefs.summarize_display_mode === 'inline') {
let cachedSummary = await summaryStore.loadSummary(message.headerMessageId);
if (cachedSummary && !cachedSummary.error) {
browser.tabs.sendMessage(tabId, { command: "showSummary", data: cachedSummary });
return;
}
if (await summaryStore.isProcessing(message.headerMessageId)) {
browser.tabs.sendMessage(tabId, { command: "showSummaryGenerating" });
return;
}
browser.tabs.sendMessage(tabId, { command: "showSummaryButton", headerMessageId: message.headerMessageId });
} else {
browser.tabs.sendMessage(tabId, { command: "showSummaryButton", headerMessageId: message.headerMessageId, webchat: true });
}
} catch (e) {
taLog.error("Error in initSummary: " + e);
@ -257,6 +271,13 @@ messenger.runtime.onMessage.addListener((message, sender, sendResponse) => {
}
_triggerSummaryGeneration(message);
break;
case 'triggerSummaryWebchat':
async function _triggerSummaryWebchat(message) {
let tabId = sender.tab.id;
await _openSummaryWebchat(message.headerMessageId, tabId);
}
_triggerSummaryWebchat(message);
break;
case 'generate_summary':
async function _generate_summary(message) {
await _generateSummaryForMessage(message.headerMessageId, message.tabId);
@ -514,6 +535,55 @@ 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);
return;
}
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 full_prompt = prompt_string + prompt_email_separator_string + email_text;
openChatGPT(full_prompt, prompt.action, tabId, prompt.name, prompt.need_custom_text, prompt);
} catch (error) {
console.error("[ThunderAI] Error opening summary webchat:", error);
}
}
// Listen for messages from ThunderAI-Sparks
browser.runtime.onMessageExternal.addListener((message, sender, sendResponse) => {
switch (message.action) {
@ -1384,6 +1454,21 @@ async function processEmails(args) {
}
if (summarize) {
const tabs = await browser.tabs.query({ active: true, currentWindow: true });
const tabId = tabs[0].id;
let summarize_prefs = await browser.storage.sync.get({ summarize_display_mode: prefs_default.summarize_display_mode });
// Collect messages into array to check count
const messageArray = [];
for await (let msg of messages) {
messageArray.push(msg);
}
// Inline mode for single message: generate inline summary in the message pane
if (summarize_prefs.summarize_display_mode === 'inline' && messageArray.length === 1) {
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();
@ -1391,7 +1476,6 @@ async function processEmails(args) {
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 tabs = await browser.tabs.query({ active: true, currentWindow: true });
const chatgpt_lang = await taPromptUtils.getDefaultLang(prompt);
// replace placeholders in the prompts the assignment prompt and email
@ -1409,7 +1493,7 @@ async function processEmails(args) {
// assemble all email messages into one string and add the assignment prompt
const messages_list = [];
for await (let curr_message of messages) {
for (let curr_message of messageArray) {
// extract body of current message as text
const curr_message_full = await browser.messages.getFull(curr_message.id);
@ -1433,18 +1517,17 @@ async function processEmails(args) {
const full_prompt = prompt_string + prompt_email_separator_string + messages_string;
// console.log(full_prompt);
// send the prompt to the chat interface
openChatGPT(
full_prompt,
prompt.action,
tabs[0].id,
tabId,
prompt.name,
prompt.need_custom_text,
prompt
);
}
}
taWorkingStatus.stopWorking();
}

View file

@ -138,6 +138,7 @@ export const prefs_default = {
spamfilter_threshold: 70,
spamfilter_enabled_accounts: [],
summarize_auto: 0, // 0: disabled, 1: manual button, 2: automatic
summarize_display_mode: 'inline', // 'inline' or 'webchat'
spamfilter_show_msg_panel: true,
summarize: false,
...generated_prefs

View file

@ -39,6 +39,18 @@
</label>
</td>
</tr>
<tr class="summarize_tr">
<td><span class="opt_title">__MSG_prefs_OptionText_summarize_display_mode__</span></td>
<td>
<label>
<select id="summarize_display_mode" name="summarize_display_mode" class="option-input">
<option value="inline">__MSG_prefs_OptionText_summarize_display_mode_inline__</option>
<option value="webchat">__MSG_prefs_OptionText_summarize_display_mode_webchat__</option>
</select>
<br>__MSG_prefs_OptionText_summarize_display_mode_Info__
</label>
</td>
</tr>
</table>
<!-- PROMPTS -->

View file

@ -240,6 +240,9 @@ async function restoreOptions() {
if (element.id === 'summarize_auto') {
default_select_value = prefs_default.summarize_auto;
}
if (element.id === 'summarize_display_mode') {
default_select_value = prefs_default.summarize_display_mode;
}
const restoreValue = result[element.id] ?? default_select_value;
// Check if option exists
let optionExists = Array.from(element.options).some(opt => opt.value === String(restoreValue));