trasnlate webchat removed. see #247

This commit is contained in:
mic 2026-04-01 00:34:26 +02:00
parent c0ebc646b8
commit 83f856d1ae
10 changed files with 21 additions and 211 deletions

View file

@ -239,10 +239,6 @@
"message": "Save as Summary", "message": "Save as Summary",
"description": "Button label in the webchat window to save the AI response as a message summary" "description": "Button label in the webchat window to save the AI response as a message summary"
}, },
"webchat_save_as_translation": {
"message": "Save as Translation",
"description": "Button label in the webchat window to save the AI response as a message translation"
},
"chatgpt_textarea_not_found_error": { "chatgpt_textarea_not_found_error": {
"message": "It seems that the ChatGPT page is taking too long to load. If it finishes loading, click the button on the right. If the problem persists, please check the service status.", "message": "It seems that the ChatGPT page is taking too long to load. If it finishes loading, click the button on the right. If the problem persists, please check the service status.",
"description": "" "description": ""
@ -2101,10 +2097,6 @@
"message": "Choose when to translate messages: disabled, only when clicking the button, or automatically when opening a message.", "message": "Choose when to translate messages: disabled, only when clicking the button, or automatically when opening a message.",
"description": "" "description": ""
}, },
"prefs_OptionText_translate_display_mode": {
"message": "Display mode for translations",
"description": ""
},
"prefs_OptionText_display_mode_inline": { "prefs_OptionText_display_mode_inline": {
"message": "Message pane (inline)", "message": "Message pane (inline)",
"description": "" "description": ""
@ -2113,10 +2105,6 @@
"message": "Chat window", "message": "Chat window",
"description": "" "description": ""
}, },
"prefs_OptionText_translate_display_mode_Info": {
"message": "Choose where to display translations. Note: automatic mode always uses inline display.",
"description": ""
},
"prefs_OptionText_translate_max_display_length": { "prefs_OptionText_translate_max_display_length": {
"message": "Maximum length of displayed translation", "message": "Maximum length of displayed translation",
"description": "" "description": ""

View file

@ -481,29 +481,6 @@ class MessagesArea extends HTMLElement {
selectionInfo.style.display = "block"; selectionInfo.style.display = "block";
} }
// Save as Translation button (only shown for translation webchat sessions)
if(promptData.prompt_info?.headerMessageId && promptData.prompt_info?.translationTabId) {
const saveTranslationButton = document.createElement('button');
saveTranslationButton.textContent = browser.i18n.getMessage("webchat_save_as_translation");
saveTranslationButton.classList.add('action_btn');
saveTranslationButton.addEventListener('click', async () => {
let finalText = removeAloneBRs(fullTextHTMLAtAssignment);
const selectedHTML = this.getCurrentSelectionHTML();
if(selectedHTML != "") {
finalText = removeAloneBRs(selectedHTML);
}
await browser.runtime.sendMessage({
command: "chatgpt_saveTranslation",
text: finalText,
headerMessageId: promptData.prompt_info.headerMessageId,
tabId: promptData.prompt_info.translationTabId || promptData.tabId,
});
browser.runtime.sendMessage({command: "chatgpt_close", window_id: (await browser.windows.getCurrent()).id});
});
actionButtons.appendChild(saveTranslationButton);
selectionInfo.style.display = "block";
}
// diff viewer button // diff viewer button
if(promptData.prompt_info?.use_diff_viewer == "1") { if(promptData.prompt_info?.use_diff_viewer == "1") {
const diffvButton = document.createElement('button'); const diffvButton = document.createElement('button');

View file

@ -82,13 +82,8 @@ mzta-background.js (checks summarize_auto + summarize_display_mode prefs)
### Data Flow: Inline Translation on Message Display ### Data Flow: Inline Translation on Message Display
The `translate_display_mode` preference (`'inline'` or `'webchat'`) controls where The `translate_auto` preference controls when translation is triggered.
the translation is displayed. The `translate_auto` preference controls when it is triggered. Translation always renders inline (webchat mode has been removed).
- `translate_auto = 2` (automatic) always generates inline, regardless of `translate_display_mode`.
- `translate_auto = 1` (manual button) respects `translate_display_mode`:
- `'inline'` → button click triggers inline generation
- `'webchat'` → button click opens the AI chat window via `_openTranslationWebchat()`
The target language is determined by `translate_lang` (fallback on `default_chatgpt_lang`). The target language is determined by `translate_lang` (fallback on `default_chatgpt_lang`).
@ -97,16 +92,14 @@ User opens/selects a message in Thunderbird
mzta-compose-script.js (sends "initTranslation" to background) mzta-compose-script.js (sends "initTranslation" to background)
mzta-background.js (checks translate + translate_auto + translate_display_mode prefs) mzta-background.js (checks translate + translate_auto prefs)
┌──────────────────────────────────────────────────────────┐ ┌──────────────────────────────────────────────────────────┐
│ translate_auto = 0 → do nothing │ │ translate_auto = 0 → do nothing │
│ translate_auto = 1 → show "click to translate" button │ │ translate_auto = 1 → show "click to translate" button │
│ display_mode = inline → click triggers inline gen │ │ translate_auto = 2 → generate immediately │
│ display_mode = webchat → click opens chat window │
│ translate_auto = 2 → generate immediately (always inline)│
└──────────────────────────────────────────────────────────┘ └──────────────────────────────────────────────────────────┘
(if generating inline)
taTranslationStore (check cache / set processing) taTranslationStore (check cache / set processing)
↓ (cache miss) ↓ (cache miss)
mzta-special-commands (via Web Worker, NOT chatgpt_web) mzta-special-commands (via Web Worker, NOT chatgpt_web)

View file

@ -88,24 +88,24 @@ The summarize feature uses two distinct prompt pathways:
### Translate: Inline-Only Prompt System ### Translate: Inline-Only Prompt System
The translate feature uses a single special prompt (`prompt_translate_this`) for translating emails. It supports both inline display and webchat mode, but has no context menu entry. The translate feature uses a single special prompt (`prompt_translate_this`) for translating emails. Translation always renders inline (no webchat mode).
**Inline Translation on Message Display** (controlled by `translate_auto` and `translate_display_mode` prefs): **Inline Translation on Message Display** (controlled by `translate_auto` pref):
- Uses a single special prompt: `prompt_translate_this` - Uses a single special prompt: `prompt_translate_this`
- The prompt text is appended with the target language and the email body: `prompt_text + " " + lang + ". \"" + body_text + "\""` - The prompt uses placeholders (`{%mail_subject%}`, `{%mail_html_body%}`, `{%thunderai_translate_lang%}`, `{%thunderai_translate_exclude_lang%}`) resolved via the standard placeholder system
- The AI response is a JSON object: `{ "subject": "...", "body": "...", "status": "1"|"-1" }`
- `status = "1"`: translation completed, subject and body are displayed
- `status = "-1"`: translation skipped (excluded/target language), a "skipped" message is shown
- Target language is determined by `translate_lang` pref, falling back to `default_chatgpt_lang` - Target language is determined by `translate_lang` pref, falling back to `default_chatgpt_lang`
- Does **not** support `chatgpt_web` connection type (shows error if configured) - Does **not** support `chatgpt_web` connection type (shows error if configured)
- `translate_display_mode = 'inline'`: result is rendered as a styled banner (green/teal theme) in the message body via `mzta-compose-script.js` - Result is rendered as a styled banner (green/teal theme) in the message body via `mzta-compose-script.js`
- `translate_display_mode = 'webchat'`: opens AI chat window; webchat shows a "Save as Translation" button to persist the result inline
- `translate_auto = 2` (automatic) always generates inline regardless of `translate_display_mode`
- Banner includes refresh (↻) and delete (×) buttons - Banner includes refresh (↻) and delete (×) buttons
- Cached per-message via `taTranslationStore` / `taStorage` (max 100 entries) - Cached per-message via `taTranslationStore` / `taStorage` (max 100 entries)
- The prompt was originally a regular prompt (`defaultPrompts`) and was moved to `specialPrompts` with `is_special: "1"` and `type: "1"` (reading email only) - The prompt was originally a regular prompt (`defaultPrompts`) and was moved to `specialPrompts` with `is_special: "1"` and `type: "1"` (reading email only)
**Prompt Building** — `taPromptUtils.buildTranslationPrompt(fullMessage, lang)`: **Prompt Building** — `taPromptUtils.buildTranslationPrompt(fullMessage)`:
- Retrieves the `prompt_translate_this` special prompt text - Retrieves the `prompt_translate_this` special prompt text
- Extracts the email body from the full message - Resolves placeholders via `placeholdersUtils.getPlaceholdersValues()` + `replacePlaceholders()`
- Combines prompt + language + body text
- Returns `{ promptText, promptInfo }` - Returns `{ promptText, promptInfo }`
## Prompt Types Reference ## Prompt Types Reference

View file

@ -100,7 +100,6 @@ These are generated programmatically at the bottom of `mzta-options-default.js`
| `summarize_max_display_length` | `0` | Maximum characters shown in inline summary 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. | | `summarize_max_display_length` | `0` | Maximum characters shown in inline summary 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` | `true` | Enable email translation | | `translate` | `true` | Enable email translation |
| `translate_auto` | `0` | Auto-translate mode: `0` = disabled, `1` = manual (show button), `2` = automatic (translate on message open), `3` = generate on email receive (background pre-cache via `onNewMailReceived`, no UI during generation) | | `translate_auto` | `0` | Auto-translate mode: `0` = disabled, `1` = manual (show button), `2` = automatic (translate on message open), `3` = generate on email receive (background pre-cache via `onNewMailReceived`, no UI during generation) |
| `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_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_lang` | `''` | Target language for translation. Falls back to `default_chatgpt_lang` if empty. |
@ -134,13 +133,9 @@ The translate settings page provides:
- `0` (Disabled) — no inline translations - `0` (Disabled) — no inline translations
- `1` (Manual) — shows a "Get AI Translation" button in message display - `1` (Manual) — shows a "Get AI Translation" button in message display
- `2` (Automatic) — generates translation immediately when message is opened - `2` (Automatic) — generates translation immediately when message is opened
3. **Display mode dropdown** (`translate_display_mode`) — controls where translations are shown: 3. **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.
- `'inline'` — translation banner in the message pane (default) 4. **Target language** (`translate_lang`) — text input for the destination language. If empty, falls back to `default_chatgpt_lang`.
- `'webchat'` — opens the AI chat window with a "Save as Translation" button 5. **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`.
- Note: `translate_auto = 2` always generates inline regardless of this setting.
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`.
## Adding a New Preference ## Adding a New Preference

View file

@ -1423,7 +1423,7 @@ switch (message.command) {
triggerBtn.onclick = () => { triggerBtn.onclick = () => {
_removeToolbarItem('mzta-toolbar-translation'); _removeToolbarItem('mzta-toolbar-translation');
browser.runtime.sendMessage({ browser.runtime.sendMessage({
command: message.webchat ? "triggerTranslationWebchat" : "triggerTranslationGeneration", command: "triggerTranslationGeneration",
headerMessageId: message.headerMessageId headerMessageId: message.headerMessageId
}); });
}; };

View file

@ -335,7 +335,7 @@ messenger.runtime.onMessage.addListener((message, sender, sendResponse) => {
async function _initTranslation() { async function _initTranslation() {
try { try {
let tabId = sender.tab.id; let tabId = sender.tab.id;
let prefs = await browser.storage.sync.get({ translate: prefs_default.translate, translate_auto: prefs_default.translate_auto, translate_display_mode: prefs_default.translate_display_mode, translate_max_display_length: prefs_default.translate_max_display_length }); let prefs = await browser.storage.sync.get({ translate: prefs_default.translate, translate_auto: prefs_default.translate_auto, translate_max_display_length: prefs_default.translate_max_display_length });
if (!prefs.translate) return; if (!prefs.translate) return;
@ -364,11 +364,7 @@ messenger.runtime.onMessage.addListener((message, sender, sendResponse) => {
} }
// Manual button mode (translate_auto === 1) // Manual button mode (translate_auto === 1)
if (prefs.translate_display_mode === 'inline') {
browser.tabs.sendMessage(tabId, { command: "showTranslationButton", headerMessageId: message.headerMessageId }); browser.tabs.sendMessage(tabId, { command: "showTranslationButton", headerMessageId: message.headerMessageId });
} else {
browser.tabs.sendMessage(tabId, { command: "showTranslationButton", headerMessageId: message.headerMessageId, webchat: true });
}
} catch (e) { } catch (e) {
taLog.error("Error in initTranslation: " + e); taLog.error("Error in initTranslation: " + e);
} }
@ -393,83 +389,17 @@ messenger.runtime.onMessage.addListener((message, sender, sendResponse) => {
} }
_triggerTranslationGeneration(message); _triggerTranslationGeneration(message);
break; break;
case 'triggerTranslationWebchat':
async function _triggerTranslationWebchat(message) {
let tabId = sender.tab.id;
let prefs_tw = await browser.storage.sync.get({
translate_lang: prefs_default.translate_lang,
default_chatgpt_lang: prefs_default.default_chatgpt_lang
});
const lang_tw = prefs_tw.translate_lang || prefs_tw.default_chatgpt_lang || '';
if (!lang_tw) {
let tabs = await browser.tabs.query({ active: true, currentWindow: true });
browser.tabs.sendMessage(tabId, { command: "sendAlert", curr_tab_type: tabs[0].type, message: browser.i18n.getMessage('translate_no_language_configured') });
browser.tabs.sendMessage(tabId, { command: "showTranslationButton", headerMessageId: message.headerMessageId, webchat: true });
return;
}
await _openTranslationWebchat(message.headerMessageId, tabId);
}
_triggerTranslationWebchat(message);
break;
case 'refreshTranslation': case 'refreshTranslation':
async function _refreshTranslation(message) { async function _refreshTranslation(message) {
let tabId = sender.tab.id; let tabId = sender.tab.id;
await translationStore.removeTranslation(message.headerMessageId); await translationStore.removeTranslation(message.headerMessageId);
let prefs_refresh_tr = await browser.storage.sync.get({ translate_display_mode: prefs_default.translate_display_mode });
if (prefs_refresh_tr.translate_display_mode === 'webchat') {
await _openTranslationWebchat(message.headerMessageId, tabId);
} else {
await _generateTranslationForMessage(message.headerMessageId, tabId); await _generateTranslationForMessage(message.headerMessageId, tabId);
} }
}
_refreshTranslation(message); _refreshTranslation(message);
break; break;
case 'removeTranslation': case 'removeTranslation':
translationStore.removeTranslation(message.headerMessageId); translationStore.removeTranslation(message.headerMessageId);
break; break;
case 'chatgpt_saveTranslation':
async function _saveTranslationFromWebchat(msg) {
try {
let rawText = msg.text.trim();
let translatedBody = '';
let translatedSubject = '';
let translationStatus = '';
try {
const parsed = JSON.parse(rawText);
translatedBody = parsed.body || '';
translatedSubject = parsed.subject || '';
translationStatus = String(parsed.status || '');
} catch (e) {
translatedBody = rawText;
}
let prefs_tr = await browser.storage.sync.get({
translate_lang: prefs_default.translate_lang,
default_chatgpt_lang: prefs_default.default_chatgpt_lang,
translate_max_display_length: prefs_default.translate_max_display_length
});
let lang = prefs_tr.translate_lang || prefs_tr.default_chatgpt_lang || '';
const translationData = {
translated_text: translatedBody,
translated_subject: translatedSubject,
translation_status: translationStatus,
lang: lang,
headerMessageId: msg.headerMessageId
};
await translationStore.saveTranslation(translationData, msg.headerMessageId);
try {
browser.tabs.sendMessage(msg.tabId, {
command: "showTranslation",
data: { ...translationData, maxDisplayLength: prefs_tr.translate_max_display_length }
});
} catch (e) {
taLog.error("Error sending showTranslation to tab: " + e);
}
} catch (error) {
console.error("[ThunderAI] Error saving translation from webchat:", error);
}
}
_saveTranslationFromWebchat(message);
break;
case 'chatgpt_close': case 'chatgpt_close':
async function _closeChatGptWindow(window_id) { async function _closeChatGptWindow(window_id) {
let prefs_close = await browser.storage.sync.get({chatgpt_win_save_position: prefs_default.chatgpt_win_save_position}); let prefs_close = await browser.storage.sync.get({chatgpt_win_save_position: prefs_default.chatgpt_win_save_position});
@ -980,45 +910,6 @@ async function _openSummaryWebchat(headerMessageId, tabId) {
} }
} }
async function _openTranslationWebchat(headerMessageId, tabId) {
try {
const messageResult = await browser.messages.query({ headerMessageId: headerMessageId });
if (!messageResult || messageResult.messages.length === 0) {
console.error("[ThunderAI] _openTranslationWebchat: 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 prefs = await browser.storage.sync.get({
...prefs_default,
translate_lang: prefs_default.translate_lang,
default_chatgpt_lang: prefs_default.default_chatgpt_lang
});
const connectionType = getConnectionType(prefs, {}, 'translate');
if (connectionType === 'chatgpt_web') {
const errorMsg = browser.i18n.getMessage('translate_chatgpt_web_not_supported');
await translationStore.saveError(headerMessageId, errorMsg);
browser.tabs.sendMessage(tabId, { command: "showTranslation", data: { error: true, message: errorMsg } });
return;
}
const lang = prefs.translate_lang || prefs.default_chatgpt_lang || '';
if (!lang) {
taLog.warn("Translation skipped: no language configured (translate_lang and default_chatgpt_lang are both empty).");
return;
}
const { promptText, promptInfo } = await taPromptUtils.buildTranslationPrompt(curr_message_full);
promptInfo.headerMessageId = headerMessageId;
promptInfo.translationTabId = tabId;
openChatGPT(promptText, promptInfo.action, tabId, promptInfo.name, promptInfo.need_custom_text, promptInfo);
} catch (error) {
console.error("[ThunderAI] Error opening translation webchat:", error);
}
}
// Listen for messages from ThunderAI-Sparks // Listen for messages from ThunderAI-Sparks
browser.runtime.onMessageExternal.addListener((message, sender, sendResponse) => { browser.runtime.onMessageExternal.addListener((message, sender, sendResponse) => {
switch (message.action) { switch (message.action) {

View file

@ -143,7 +143,6 @@ export const prefs_default = {
summarize_max_display_length: 0, // 0 = no limit, otherwise max chars shown inline summarize_max_display_length: 0, // 0 = no limit, otherwise max chars shown inline
translate: true, translate: true,
translate_auto: 0, // 0: disabled, 1: manual button, 2: automatic on message open, 3: generate on email receive translate_auto: 0, // 0: disabled, 1: manual button, 2: automatic on message open, 3: generate on email receive
translate_display_mode: 'inline', // 'inline' or 'webchat'
translate_max_display_length: 0, // 0 = no limit, otherwise max chars shown inline translate_max_display_length: 0, // 0 = no limit, otherwise max chars shown inline
translate_lang: '', // target language, fallback on default_chatgpt_lang translate_lang: '', // target language, fallback on default_chatgpt_lang
translate_exclude_lang: '', // languages to do not translate translate_exclude_lang: '', // languages to do not translate

View file

@ -40,18 +40,6 @@
</label> </label>
</td> </td>
</tr> </tr>
<tr class="translate_tr">
<td><span class="opt_title">__MSG_prefs_OptionText_translate_display_mode__</span></td>
<td>
<label>
<select id="translate_display_mode" name="translate_display_mode" class="option-input">
<option value="inline">__MSG_prefs_OptionText_display_mode_inline__</option>
<option value="webchat">__MSG_prefs_OptionText_display_mode_webchat__</option>
</select>
<br>__MSG_prefs_OptionText_translate_display_mode_Info__
</label>
</td>
</tr>
<tr class="translate_tr"> <tr class="translate_tr">
<td><span class="opt_title">__MSG_prefs_OptionText_translate_max_display_length__</span></td> <td><span class="opt_title">__MSG_prefs_OptionText_translate_max_display_length__</span></td>
<td> <td>

View file

@ -74,8 +74,6 @@ document.addEventListener("DOMContentLoaded", async () => {
document.querySelectorAll(".option-input").forEach(element => { document.querySelectorAll(".option-input").forEach(element => {
element.addEventListener("change", saveOptions); element.addEventListener("change", saveOptions);
}); });
document.getElementById('translate_auto').addEventListener('change', updateDisplayModeConstraint);
let translate_textarea = document.getElementById("translate_prompt_text"); let translate_textarea = document.getElementById("translate_prompt_text");
let translate_save_btn = document.getElementById("btn_save_prompt"); let translate_save_btn = document.getElementById("btn_save_prompt");
let translate_reset_btn = document.getElementById("btn_reset_prompt"); let translate_reset_btn = document.getElementById("btn_reset_prompt");
@ -119,21 +117,6 @@ document.addEventListener("DOMContentLoaded", async () => {
// Methods to manage options, derived from: /options/mzta-options.js // Methods to manage options, derived from: /options/mzta-options.js
function updateDisplayModeConstraint() {
const translate_auto_el = document.getElementById('translate_auto');
const display_mode_el = document.getElementById('translate_display_mode');
const autoVal = String(translate_auto_el.value);
if (autoVal === '2' || autoVal === '3') {
display_mode_el.value = 'inline';
display_mode_el.disabled = true;
browser.storage.sync.set({ translate_display_mode: 'inline' });
} else if (autoVal === '0') {
display_mode_el.disabled = true;
} else {
display_mode_el.disabled = false;
}
}
function saveOptions(e) { function saveOptions(e) {
e.preventDefault(); e.preventDefault();
let options = {}; let options = {};
@ -191,9 +174,6 @@ async function restoreOptions() {
if (element.id === 'translate_auto') { if (element.id === 'translate_auto') {
default_select_value = prefs_default.translate_auto; default_select_value = prefs_default.translate_auto;
} }
if (element.id === 'translate_display_mode') {
default_select_value = prefs_default.translate_display_mode;
}
const restoreValue = result[element.id] ?? default_select_value; const restoreValue = result[element.id] ?? default_select_value;
let optionExists = Array.from(element.options).some(opt => opt.value === String(restoreValue)); let optionExists = Array.from(element.options).some(opt => opt.value === String(restoreValue));
if (element.tomselect) { if (element.tomselect) {
@ -248,5 +228,4 @@ async function restoreOptions() {
} }
setCurrentChoice(getting); setCurrentChoice(getting);
updateDisplayModeConstraint();
} }