Compare commits

...

1 commit

Author SHA1 Message Date
mic
25b06e490b Summary translation first try. See #725 2026-03-30 23:37:20 +02:00
6 changed files with 99 additions and 13 deletions

View file

@ -2149,6 +2149,14 @@
"message": "Comma-separated list of language codes (e.g., en, fr, it) to skip for automatic translation. If the email is in one of these languages, it won't be translated automatically or the manual button won't be shown.",
"description": ""
},
"prefs_OptionText_translate_summary_translation": {
"message": "Summary Translation",
"description": ""
},
"prefs_OptionText_translate_summary_translation_Info": {
"message": "When translating, also translate the existing summary. When summarizing, use the existing translation as input.",
"description": ""
},
"prefs_OptionText_Translate_main_prompt": {
"message": "The prompt describing the translation task:",
"description": ""

View file

@ -103,6 +103,7 @@ These are generated programmatically at the bottom of `mzta-options-default.js`
| `translate_display_mode` | `'inline'` | Where to display translations: `'inline'` = message pane banner, `'webchat'` = AI chat window. Note: `translate_auto = 2` and `translate_auto = 3` always use inline regardless of this setting. |
| `translate_max_display_length` | `0` | Maximum characters shown in inline translation before truncation. `0` = no limit (show full text). When set, text is truncated at a word boundary and a "See more"/"See less" toggle link is shown. |
| `translate_lang` | `''` | Target language for translation. Falls back to `default_chatgpt_lang` if empty. |
| `translate_summary_translation` | `false` | Links translation and summary features. When translating, also translates the existing cached summary. When summarizing, uses the existing cached translation as input instead of the original email body. |
### Summarize Settings Page (`pages/summarize/`)
@ -141,6 +142,7 @@ The translate settings page provides:
4. **Max display length** (`translate_max_display_length`) — number input, limits inline translation text to N characters. `0` = no limit. When truncated, a "See more"/"See less" toggle link is appended.
5. **Target language** (`translate_lang`) — text input for the destination language. If empty, falls back to `default_chatgpt_lang`.
6. **One editable prompt** — the translation instruction prompt (`prompt_translate_this`) with Save/Reset buttons and placeholder autocomplete. Default text comes from i18n string `prompt_translate_this_full_text`.
7. **Summary Translation checkbox** (`translate_summary_translation`) — links translation and summary features. When enabled: translating an email also translates the cached summary (if present), and summarizing an email uses the cached translation (if present) as input instead of the original email body.
## Adding a New Preference

View file

@ -132,7 +132,7 @@ export const taPromptUtils = {
},
async buildSummaryPrompt(messageDataArray) {
async buildSummaryPrompt(messageDataArray, overrideBodyText = null) {
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');
@ -151,10 +151,17 @@ export const taPromptUtils = {
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 || '';
let bodyText;
let bodyHtml;
if (overrideBodyText && messageDataArray.length === 1) {
bodyText = overrideBodyText;
bodyHtml = { html: '', text: overrideBodyText };
} else {
bodyHtml = getMailBody(entry.fullMessage);
bodyText = htmlBodyToPlainText(bodyHtml.html);
if (bodyText.length === 0) {
bodyText = bodyHtml.text || '';
}
}
messages_list.push(await taPromptUtils.preparePrompt({
@ -173,7 +180,7 @@ export const taPromptUtils = {
return { promptText, promptInfo: prompt };
},
async buildTranslationPrompt(fullMessage, lang) {
async buildTranslationPrompt(fullMessage, lang, overrideBodyText = null) {
const specialPrompts = await getSpecialPrompts();
const prompt = specialPrompts.find(p => p.id === 'prompt_translate_this');
@ -182,10 +189,15 @@ export const taPromptUtils = {
promptText = browser.i18n.getMessage('prompt_translate_this_full_text');
}
const bodyHtml = getMailBody(fullMessage);
let bodyText = htmlBodyToPlainText(bodyHtml.html);
if (bodyText.length === 0) {
bodyText = bodyHtml.text || '';
let bodyText;
if (overrideBodyText) {
bodyText = overrideBodyText;
} else {
const bodyHtml = getMailBody(fullMessage);
bodyText = htmlBodyToPlainText(bodyHtml.html);
if (bodyText.length === 0) {
bodyText = bodyHtml.text || '';
}
}
const fullPrompt = promptText + " " + lang + ". \"" + bodyText + "\"";

View file

@ -643,6 +643,7 @@ async function _generateSummaryForMessage(headerMessageId, tabId = null, options
do_debug: prefs_default.do_debug,
default_chatgpt_lang: prefs_default.default_chatgpt_lang,
summarize_max_display_length: prefs_default.summarize_max_display_length,
translate_summary_translation: prefs_default.translate_summary_translation,
...getDynamicSettingsDefaults(['use_specific_integration', 'connection_type'])
});
@ -687,7 +688,16 @@ async function _generateSummaryForMessage(headerMessageId, tabId = null, options
return;
}
const { promptText } = await taPromptUtils.buildSummaryPrompt([{ message, fullMessage }]);
// Summary Translation: if enabled and a cached translation exists, use it as input
let overrideBodyText = null;
if (prefs.translate_summary_translation) {
let cachedTranslation = await translationStore.loadTranslation(headerMessageId);
if (cachedTranslation && !cachedTranslation.error && cachedTranslation.translated_text) {
overrideBodyText = cachedTranslation.translated_text;
}
}
const { promptText } = await taPromptUtils.buildSummaryPrompt([{ message, fullMessage }], overrideBodyText);
const cmd = new mzta_specialCommand({
prompt: promptText,
@ -730,6 +740,8 @@ async function _generateTranslationForMessage(headerMessageId, tabId = null, opt
default_chatgpt_lang: prefs_default.default_chatgpt_lang,
translate_lang: prefs_default.translate_lang,
translate_max_display_length: prefs_default.translate_max_display_length,
translate_summary_translation: prefs_default.translate_summary_translation,
summarize_max_display_length: prefs_default.summarize_max_display_length,
...getDynamicSettingsDefaults(['use_specific_integration', 'connection_type'])
});
@ -796,6 +808,38 @@ async function _generateTranslationForMessage(headerMessageId, tabId = null, opt
};
await translationStore.saveTranslation(translationData, headerMessageId);
if (tabId) browser.tabs.sendMessage(tabId, { command: "showTranslation", data: { ...translationData, maxDisplayLength: prefs.translate_max_display_length } });
// Summary Translation: if enabled and a cached summary exists, also translate the summary
if (prefs.translate_summary_translation) {
let cachedSummary = await summaryStore.loadSummary(headerMessageId);
if (cachedSummary && !cachedSummary.error && cachedSummary.summary) {
try {
const { promptText: summaryTranslationPrompt } = await taPromptUtils.buildTranslationPrompt(null, lang, cachedSummary.summary);
const cmdSummary = new mzta_specialCommand({
prompt: summaryTranslationPrompt,
llm: connectionType,
do_debug: prefs.do_debug,
config: {}
});
await cmdSummary.initWorker();
const translatedSummary = await cmdSummary.sendPrompt();
let cleanedSummary = cleanSummaryText(translatedSummary);
const md = window.markdownit();
let summaryHtml = md.render(translatedSummary);
const summaryData = {
summary: cleanedSummary,
summary_html: summaryHtml,
summary_date: new Date(),
headerMessageId: headerMessageId
};
await summaryStore.saveSummary(summaryData, headerMessageId);
if (tabId) browser.tabs.sendMessage(tabId, { command: "showSummary", data: { ...summaryData, maxDisplayLength: prefs.summarize_max_display_length } });
} catch (summaryError) {
console.error("[ThunderAI] Error translating summary:", summaryError);
}
}
}
taWorkingStatus.stopWorking();
} catch (error) {
@ -935,7 +979,8 @@ async function _openSummaryWebchat(headerMessageId, tabId) {
const curr_message = messageResult.messages[0];
const curr_message_full = await browser.messages.getFull(curr_message.id);
const connectionType = getConnectionType(await browser.storage.sync.get(prefs_default), {}, 'summarize');
const prefs = await browser.storage.sync.get(prefs_default);
const connectionType = getConnectionType(prefs, {}, 'summarize');
if (connectionType === 'chatgpt_web') {
const errorMsg = browser.i18n.getMessage('summarize_chatgpt_web_not_supported');
await summaryStore.saveError(headerMessageId, errorMsg);
@ -943,7 +988,16 @@ async function _openSummaryWebchat(headerMessageId, tabId) {
return;
}
const { promptText, promptInfo } = await taPromptUtils.buildSummaryPrompt([{ message: curr_message, fullMessage: curr_message_full }]);
// Summary Translation: if enabled and a cached translation exists, use it as input
let overrideBodyText = null;
if (prefs.translate_summary_translation) {
let cachedTranslation = await translationStore.loadTranslation(headerMessageId);
if (cachedTranslation && !cachedTranslation.error && cachedTranslation.translated_text) {
overrideBodyText = cachedTranslation.translated_text;
}
}
const { promptText, promptInfo } = await taPromptUtils.buildSummaryPrompt([{ message: curr_message, fullMessage: curr_message_full }], overrideBodyText);
promptInfo.headerMessageId = headerMessageId;
promptInfo.summaryTabId = tabId;

View file

@ -147,6 +147,7 @@ export const prefs_default = {
translate_max_display_length: 0, // 0 = no limit, otherwise max chars shown inline
translate_lang: '', // target language, fallback on default_chatgpt_lang
translate_exclude_lang: '', // languages to do not translate
translate_summary_translation: false, // link translation and summary features
spamfilter_show_msg_panel: true,
...generated_prefs
}

View file

@ -81,6 +81,15 @@
</label>
</td>
</tr>
<tr class="translate_tr">
<td><span class="opt_title">__MSG_prefs_OptionText_translate_summary_translation__</span></td>
<td>
<label>
<input type="checkbox" id="translate_summary_translation" name="translate_summary_translation" class="option-input" />
__MSG_prefs_OptionText_translate_summary_translation_Info__
</label>
</td>
</tr>
</table>
<!-- PROMPTS -->