diff --git a/_locales/bg/messages.json b/_locales/bg/messages.json index 6adaff52..9c3c2735 100644 --- a/_locales/bg/messages.json +++ b/_locales/bg/messages.json @@ -104,6 +104,9 @@ "customPrompts_form_label_use_diff_viewer_title": { "message": "Инструментът за сравнение на текст може да бъде избран, когато действието е зададено на „Замести текст“." }, + "customPrompts_form_label_clean_embedded_images": { + "message": "Премахване на вградените изображения от HTML на промпта" + }, "customPrompts_form_required_fields": { "message": "Задължително поле" }, diff --git a/_locales/cs/messages.json b/_locales/cs/messages.json index 6592022a..cca7af1a 100644 --- a/_locales/cs/messages.json +++ b/_locales/cs/messages.json @@ -65,6 +65,9 @@ "customPrompts_form_label_Name": { "message": "Název" }, + "customPrompts_form_label_clean_embedded_images": { + "message": "Odebrat vložené obrázky z HTML výzvy" + }, "customPrompts_form_required_fields": { "message": "Požadovaná pole" }, diff --git a/_locales/de/messages.json b/_locales/de/messages.json index df5afd0b..09901cec 100644 --- a/_locales/de/messages.json +++ b/_locales/de/messages.json @@ -96,6 +96,9 @@ "customPrompts_form_label_enabled": { "message": "Aktiviert" }, + "customPrompts_form_label_clean_embedded_images": { + "message": "Eingebettete Bilder aus dem Prompt-HTML entfernen" + }, "customPrompts_form_required_fields": { "message": "Erforderliche Felder" }, diff --git a/_locales/el/messages.json b/_locales/el/messages.json index 13cdded3..2e603980 100644 --- a/_locales/el/messages.json +++ b/_locales/el/messages.json @@ -104,6 +104,9 @@ "customPrompts_form_label_use_diff_viewer_title": { "message": "Η προβολή σύγκρισης κειμένου μπορεί να επιλεγεί όταν η ενέργεια έχει οριστεί σε \"Αντικατάσταση κειμένου\"." }, + "customPrompts_form_label_clean_embedded_images": { + "message": "Αφαίρεση ενσωματωμένων εικόνων από το HTML του prompt" + }, "customPrompts_form_required_fields": { "message": "Απαιτούμενα πεδία" }, diff --git a/_locales/en/messages.json b/_locales/en/messages.json index f0e6e9b8..e7eb95a0 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -143,6 +143,10 @@ "message": "The text comparison viewer can be selected when the action is set to \"Substitute text\".", "description": "" }, + "customPrompts_form_label_clean_embedded_images": { + "message": "Remove embedded images from prompt HTML", + "description": "" + }, "customPrompts_form_required_fields": { "message": "Required fields", "description": "" @@ -1080,6 +1084,10 @@ "message": "Typed text before the quoted mail body", "description": "" }, + "placeholder_mail_typed_html": { + "message": "Typed HTML formatted text before the quoted mail body. Recommended system instruction: 'Return directly in HTML code when provided with HTML email text'", + "description": "" + }, "placeholder_mail_quoted_text": { "message": "Quoted text in the mail body", "description": "" diff --git a/_locales/eo/messages.json b/_locales/eo/messages.json index 1194a5aa..0c250630 100644 --- a/_locales/eo/messages.json +++ b/_locales/eo/messages.json @@ -17,6 +17,9 @@ "customPrompts_btnCancel": { "message": "Nuligi" }, + "customPrompts_form_label_clean_embedded_images": { + "message": "Forigi enigitajn bildojn el la prompta HTML-kodo" + }, "customPrompts_form_required_fields": { "message": "Postulataj kampoj" }, diff --git a/_locales/es/messages.json b/_locales/es/messages.json index 759f02a0..43120169 100644 --- a/_locales/es/messages.json +++ b/_locales/es/messages.json @@ -104,6 +104,9 @@ "customPrompts_form_label_use_diff_viewer_title": { "message": "El visor de comparación de texto se puede seleccionar cuando la acción se establece en \"sustituir texto\"." }, + "customPrompts_form_label_clean_embedded_images": { + "message": "Eliminar imágenes incrustadas del HTML del prompt" + }, "customPrompts_form_required_fields": { "message": "Campos obligatorios" }, diff --git a/_locales/fr/messages.json b/_locales/fr/messages.json index e5290bbd..8e79da5b 100644 --- a/_locales/fr/messages.json +++ b/_locales/fr/messages.json @@ -96,6 +96,9 @@ "customPrompts_form_label_enabled": { "message": "Activé" }, + "customPrompts_form_label_clean_embedded_images": { + "message": "Supprimer les images intégrées du HTML du prompt" + }, "customPrompts_form_required_fields": { "message": "Champs obligatoires" }, diff --git a/_locales/hr/messages.json b/_locales/hr/messages.json index 7e55e8a5..03a487f1 100644 --- a/_locales/hr/messages.json +++ b/_locales/hr/messages.json @@ -96,6 +96,9 @@ "customPrompts_form_label_enabled": { "message": "Omogućeno" }, + "customPrompts_form_label_clean_embedded_images": { + "message": "Ukloni ugrađene slike iz HTML-a odziva" + }, "customPrompts_form_required_fields": { "message": "Obavezna polja" }, diff --git a/_locales/hu/messages.json b/_locales/hu/messages.json index f2ed346a..4f28db6c 100644 --- a/_locales/hu/messages.json +++ b/_locales/hu/messages.json @@ -107,6 +107,9 @@ "customPrompts_form_label_use_diff_viewer_title": { "message": "A szöveg-összehasonlító nézet akkor választható ki, ha a művelet \"Szöveg helyettesítése\"-re van állítva." }, + "customPrompts_form_label_clean_embedded_images": { + "message": "Beágyazott képek eltávolítása a prompt HTML-kódjából" + }, "customPrompts_form_required_fields": { "message": "Kötelező mezők" }, diff --git a/_locales/id/messages.json b/_locales/id/messages.json index 084d9bba..865d4be1 100644 --- a/_locales/id/messages.json +++ b/_locales/id/messages.json @@ -107,6 +107,9 @@ "customPrompts_form_label_use_diff_viewer_title": { "message": "Penampil perbandingan teks dapat dipilih ketika tindakan diatur ke \"Ganti teks\"." }, + "customPrompts_form_label_clean_embedded_images": { + "message": "Hapus gambar tersemat dari HTML prompt" + }, "customPrompts_form_required_fields": { "message": "Kolom wajib diisi" }, diff --git a/_locales/it/messages.json b/_locales/it/messages.json index 7c449957..7d9a96d1 100644 --- a/_locales/it/messages.json +++ b/_locales/it/messages.json @@ -93,6 +93,9 @@ "customPrompts_form_label_need_custom_text": { "message": "Chiedi un testo aggiuntivo" }, + "customPrompts_form_label_clean_embedded_images": { + "message": "Rimuovi immagini incorporate dall'HTML del prompt" + }, "customPrompts_form_label_enabled": { "message": "Attivo" }, diff --git a/_locales/ja/messages.json b/_locales/ja/messages.json index b687fb24..dcb4205c 100644 --- a/_locales/ja/messages.json +++ b/_locales/ja/messages.json @@ -107,6 +107,9 @@ "customPrompts_form_label_use_diff_viewer_title": { "message": "テキスト比較ビューアは、アクションが「テキストを置換」に設定されている場合に選択できます。" }, + "customPrompts_form_label_clean_embedded_images": { + "message": "プロンプトHTMLから埋め込み画像を削除" + }, "customPrompts_form_required_fields": { "message": "必須フィールド" }, diff --git a/_locales/nb_NO/messages.json b/_locales/nb_NO/messages.json index 0967ef42..2c48b3f1 100644 --- a/_locales/nb_NO/messages.json +++ b/_locales/nb_NO/messages.json @@ -1 +1,5 @@ -{} +{ + "customPrompts_form_label_clean_embedded_images": { + "message": "Fjern innebygde bilder fra prompt-HTML" + } +} \ No newline at end of file diff --git a/_locales/nl/messages.json b/_locales/nl/messages.json index 79cc2215..16c2e2ef 100644 --- a/_locales/nl/messages.json +++ b/_locales/nl/messages.json @@ -1,5 +1,8 @@ { "extensionDescription": { "message": "Gebruik ChatGPT, Google Gemini, Claude of Ollama om uw emails te verbeteren." + }, + "customPrompts_form_label_clean_embedded_images": { + "message": "Ingebedde afbeeldingen verwijderen uit prompt-HTML" } -} +} \ No newline at end of file diff --git a/_locales/pl/messages.json b/_locales/pl/messages.json index 89188a14..7bbbfe67 100644 --- a/_locales/pl/messages.json +++ b/_locales/pl/messages.json @@ -96,6 +96,9 @@ "customPrompts_form_label_enabled": { "message": "Włączone" }, + "customPrompts_form_label_clean_embedded_images": { + "message": "Usuń osadzone obrazy z HTML prompta" + }, "customPrompts_form_required_fields": { "message": "Pola wymagane" }, diff --git a/_locales/pt-br/messages.json b/_locales/pt-br/messages.json index 6e3d4cfc..05f4eef0 100644 --- a/_locales/pt-br/messages.json +++ b/_locales/pt-br/messages.json @@ -96,6 +96,9 @@ "customPrompts_form_label_enabled": { "message": "Ativado" }, + "customPrompts_form_label_clean_embedded_images": { + "message": "Remover imagens incorporadas do HTML do prompt" + }, "customPrompts_form_required_fields": { "message": "Campos obrigatórios" }, diff --git a/_locales/pt/messages.json b/_locales/pt/messages.json index fe127e3a..e81f3f4b 100644 --- a/_locales/pt/messages.json +++ b/_locales/pt/messages.json @@ -68,6 +68,9 @@ "customPrompts_form_label_enabled": { "message": "Ativo" }, + "customPrompts_form_label_clean_embedded_images": { + "message": "Remover imagens incorporadas do HTML do prompt" + }, "customPrompts_form_required_fields": { "message": "Campos obrigatórios" }, diff --git a/_locales/ro/messages.json b/_locales/ro/messages.json index e92c8ac0..6ff2f3e3 100644 --- a/_locales/ro/messages.json +++ b/_locales/ro/messages.json @@ -77,6 +77,9 @@ "customPrompts_form_label_need_custom_text": { "message": "Solicită text suplimentar" }, + "customPrompts_form_label_clean_embedded_images": { + "message": "Elimină imaginile încorporate din HTML-ul promptului" + }, "customPrompts_form_required_fields": { "message": "Câmpuri necesare" }, diff --git a/_locales/ru/messages.json b/_locales/ru/messages.json index 43e8f7d0..302e2415 100644 --- a/_locales/ru/messages.json +++ b/_locales/ru/messages.json @@ -98,6 +98,9 @@ "customPrompts_form_label_use_diff_viewer_title": { "message": "Средство просмотра для сравнения текста можно выбрать, если для действия установлено значение \"Заменить текст\"." }, + "customPrompts_form_label_clean_embedded_images": { + "message": "Удалять встроенные изображения из HTML промпта" + }, "customPrompts_form_required_fields": { "message": "Обязательные поля" }, diff --git a/_locales/sk/messages.json b/_locales/sk/messages.json index 0967ef42..5de3defa 100644 --- a/_locales/sk/messages.json +++ b/_locales/sk/messages.json @@ -1 +1,5 @@ -{} +{ + "customPrompts_form_label_clean_embedded_images": { + "message": "Odstrániť vložené obrázky z HTML výzvy" + } +} \ No newline at end of file diff --git a/_locales/sv/messages.json b/_locales/sv/messages.json index e5c41d4c..15ce544b 100644 --- a/_locales/sv/messages.json +++ b/_locales/sv/messages.json @@ -80,6 +80,9 @@ "customPrompts_reloading_menus": { "message": "Laddar om menyer..." }, + "customPrompts_form_label_clean_embedded_images": { + "message": "Ta bort inbäddade bilder från prompt-HTML" + }, "customPrompts_form_required_fields": { "message": "Obligatoriska fält" }, diff --git a/_locales/tr/messages.json b/_locales/tr/messages.json index 0127f024..28e69822 100644 --- a/_locales/tr/messages.json +++ b/_locales/tr/messages.json @@ -16,5 +16,8 @@ }, "customPrompts_start_saving": { "message": "Promptlar kaydediliyor…" + }, + "customPrompts_form_label_clean_embedded_images": { + "message": "Gömülü görselleri prompt HTML'sinden kaldır" } -} +} \ No newline at end of file diff --git a/_locales/zh_Hans/messages.json b/_locales/zh_Hans/messages.json index f04910b1..82dd170a 100644 --- a/_locales/zh_Hans/messages.json +++ b/_locales/zh_Hans/messages.json @@ -182,6 +182,9 @@ "customPrompts_form_label_need_custom_text": { "message": "要求补充文字" }, + "customPrompts_form_label_clean_embedded_images": { + "message": "从提示词 HTML 中移除嵌入的图片" + }, "customPrompts_form_required_fields": { "message": "必填字段" }, diff --git a/_locales/zh_Hant/messages.json b/_locales/zh_Hant/messages.json index 89d03bba..c20e3df6 100644 --- a/_locales/zh_Hant/messages.json +++ b/_locales/zh_Hant/messages.json @@ -92,6 +92,9 @@ "customPrompts_form_label_use_diff_viewer": { "message": "啟用文字比較檢視器" }, + "customPrompts_form_label_clean_embedded_images": { + "message": "從提示詞 HTML 中移除嵌入的圖片" + }, "customPrompts_form_required_fields": { "message": "必填欄位" }, diff --git a/api_webchat/streamingMessage.js b/api_webchat/streamingMessage.js index c4cad912..66bb391f 100644 --- a/api_webchat/streamingMessage.js +++ b/api_webchat/streamingMessage.js @@ -101,7 +101,7 @@ export class StreamingMessage { this._thinkingAccumulator = ''; // Convert Markdown to DOM nodes using the markdown-it library - const md = window.markdownit(); + const md = window.markdownit({ html: true }); const html = md.render(fullText); this._fullTextHTML += html; diff --git a/claude-spec/02-prompts.md b/claude-spec/02-prompts.md index 6dc78b5a..c3dc1d73 100644 --- a/claude-spec/02-prompts.md +++ b/claude-spec/02-prompts.md @@ -23,6 +23,7 @@ Prompts are the core user-facing feature of ThunderAI. Each prompt defines an AI | `need_custom_text` | string | `"0"` = no custom input, `"1"` = show custom text input field | | `define_response_lang` | string | `"0"` = no language hint, `"1"` = append response language instruction | | `use_diff_viewer` | string | `"0"` = normal output, `"1"` = show diff viewer (ChatGPT Web only) | +| `clean_embedded_images` | string | `"0"` = keep raw embedded images, `"1"` = tokenize embedded images in HTML placeholders and restore them in AI output | > **Note:** These numeric-looking properties are stored as **strings** (`"0"`/`"1"`/`"2"`) in the prompt objects in `js/mzta-prompts.js`, not as JS numbers. The prompt body lives in the `text` property (there is no `prompt` property). diff --git a/claude-spec/03-placeholders.md b/claude-spec/03-placeholders.md index fa18a31e..54d044e3 100644 --- a/claude-spec/03-placeholders.md +++ b/claude-spec/03-placeholders.md @@ -31,7 +31,8 @@ placeholders with `is_dynamic: "1"` (take a parameter after `:`). |----|-------------|------|-----| | `mail_text_body` | Full plain text of the email | 0 | | | `mail_html_body` | Full HTML of the email | 0 | | -| `mail_typed_text` | Text typed so far in compose window | 2 | | +| `mail_typed_text` | Text typed so far in compose window (plain text) | 2 | | +| `mail_typed_html` | HTML formatted text typed so far in compose window. *Tip: Recommended system prompt: "Return directly in HTML code when provided with HTML code as email text"* | 2 | | | `mail_quoted_text` | Quoted text in the compose window | 2 | | | `mail_subject` | Email subject line | 0 | | | `mail_folder_name` | Name of the folder containing the email | 1 | | diff --git a/js/mzta-compose-script.js b/js/mzta-compose-script.js index 42b2ff45..b3c60985 100644 --- a/js/mzta-compose-script.js +++ b/js/mzta-compose-script.js @@ -302,9 +302,43 @@ switch (message.command) { } const r = sel.getRangeAt(0); r.deleteContents(); + + let cleanText = message.text || ''; + // Strip markdown code fences if response is wrapped in ```html ... ``` or ``` ... ``` + const codeBlockMatch = cleanText.trim().match(/^```(?:html)?\s*\n?([\s\S]*?)\n?```$/i); + if (codeBlockMatch) { + cleanText = codeBlockMatch[1]; + } + + // Decode HTML entities if text contains escaped tags (e.g. <p> or <div>) + while (cleanText.includes('<') && (cleanText.includes('>') || cleanText.includes('"'))) { + const txt = document.createElement('textarea'); + txt.innerHTML = cleanText; + const decoded = txt.value; + if (decoded === cleanText) break; + cleanText = decoded; + } + const parser = new DOMParser(); - const doc = parser.parseFromString(message.text, 'text/html'); - r.insertNode(doc.body); + let doc = parser.parseFromString(cleanText, 'text/html'); + + // Remove UI elements (buttons, SVGs, forms, scripts, inputs, navigation, etc.) + doc.querySelectorAll('button, svg, script, style, form, input, textarea, header, footer, nav, use').forEach(el => el.remove()); + + // If ChatGPT/webchat wrapper container (.markdown or [data-message-author-role]) is present, extract content directly from it + let targetContainer = doc.querySelector('.markdown') || doc.querySelector('[data-message-author-role="assistant"]') || doc.body; + + // If targetContainer contains literal string HTML tags like "
", re-parse its textContent into real DOM elements
+ if (/^\s*<[a-z][^>]*>/i.test(targetContainer.textContent.trim())) {
+ const innerDoc = parser.parseFromString(targetContainer.textContent.trim(), 'text/html');
+ targetContainer = innerDoc.body;
+ }
+
+ const fragment = document.createDocumentFragment();
+ while (targetContainer.firstChild) {
+ fragment.appendChild(targetContainer.firstChild);
+ }
+ r.insertNode(fragment);
browser.runtime.sendMessage({command: "compose_reloadBody", tabId: message.tabId});
return Promise.resolve(true);
}
@@ -375,6 +409,57 @@ switch (message.command) {
return Promise.resolve(t);
}
+ case "getOnlyTypedHtml": {
+ const children = window.document.body.childNodes;
+ const selection = window.getSelection();
+
+ let firstNode = null;
+ let lastNode = null;
+ const container = document.createElement('div');
+
+ for (const node of children) {
+ if (node instanceof Element) {
+ if (node.classList.contains('moz-cite-prefix') || node.classList.contains('moz-forward-container')) {
+ break;
+ }
+ if (MZTA_INJECTED_SELECTORS.some(sel => node.matches && node.matches(sel))) {
+ continue;
+ }
+ }
+ container.appendChild(node.cloneNode(true));
+
+ if (!firstNode) {
+ firstNode = node;
+ }
+ if (node.textContent && node.textContent.trim() !== '') {
+ lastNode = node;
+ }
+ }
+
+ if (!lastNode) {
+ lastNode = firstNode;
+ }
+
+ if (message.do_autoselect && firstNode && lastNode) {
+ const range = document.createRange();
+ range.setStartBefore(firstNode);
+ range.setEndAfter(lastNode);
+ selection.removeAllRanges();
+ selection.addRange(range);
+ }
+
+ let resultHtml = container.innerHTML;
+ // Un-nest ProseMirror or editor wrapper elements if present
+ const tempDiv = document.createElement('div');
+ tempDiv.innerHTML = resultHtml;
+ const pmWrapper = tempDiv.querySelector('.ProseMirror');
+ if (pmWrapper) {
+ resultHtml = pmWrapper.innerHTML;
+ }
+
+ return Promise.resolve(resultHtml);
+ }
+
case "getOnlyQuotedText": {
let t = '';
const children = window.document.body.childNodes;
diff --git a/js/mzta-menus.js b/js/mzta-menus.js
index 07a779c1..465f9318 100644
--- a/js/mzta-menus.js
+++ b/js/mzta-menus.js
@@ -103,6 +103,7 @@ export class mzta_Menus {
text: cleanupNewlines(await browser.tabs.sendMessage(tabs[0].id, { command: "getTextOnly" })),
html: convertNewlinesToBr(await browser.tabs.sendMessage(tabs[0].id, { command: "getFullHtml" })),
only_typed_text: cleanupNewlines(await browser.tabs.sendMessage(tabs[0].id, { command: "getOnlyTypedText", do_autoselect: do_autoselect })),
+ only_typed_html: await browser.tabs.sendMessage(tabs[0].id, { command: "getOnlyTypedHtml", do_autoselect: do_autoselect }),
only_quoted_text: cleanupNewlines(await browser.tabs.sendMessage(tabs[0].id, { command: "getOnlyQuotedText" }))
};
};
@@ -110,7 +111,7 @@ export class mzta_Menus {
curr_menu_entry.act = async () => {
taWorkingStatus.startWorking();
const tabs = await browser.tabs.query({ active: true, currentWindow: true });
- const msg_text = await getMailBody(tabs, placeholdersUtils.hasPlaceholder(curr_prompt.text,'mail_typed_text'));
+ const msg_text = await getMailBody(tabs, placeholdersUtils.hasPlaceholder(curr_prompt.text,'mail_typed_text') || placeholdersUtils.hasPlaceholder(curr_prompt.text,'mail_typed_html'));
if (curr_prompt.id === 'prompt_get_calendar_event_from_clipboard') {
try {
@@ -144,12 +145,17 @@ export class mzta_Menus {
let selection_text = '';
let selection_html = msg_text.selection_html;
let only_typed_text = '';
+ let only_typed_html = '';
let only_quoted_text = '';
only_typed_text = msg_text.only_typed_text.replace(/[ \t]+/g, ' ').trim();
+ only_typed_html = msg_text.only_typed_html || '';
selection_text = msg_text.selection.replace(/[ \t]+/g, ' ').trim();
if(selection_text === ''){
if(placeholdersUtils.hasPlaceholder(curr_prompt.text, "mail_typed_text")){
selection_text = only_typed_text;
+ } else if(placeholdersUtils.hasPlaceholder(curr_prompt.text, "mail_typed_html")){
+ selection_text = only_typed_text;
+ selection_html = only_typed_html;
}
}
only_quoted_text = msg_text.only_quoted_text.replace(/[ \t]+/g, ' ').trim();
@@ -190,6 +196,7 @@ export class mzta_Menus {
subject_text: await getMailSubject(tabs[0]),
msg_text: msg_text,
only_typed_text: only_typed_text,
+ only_typed_html: only_typed_html,
only_quoted_text: only_quoted_text,
tags_full_list: tags_full_list
});
diff --git a/js/mzta-placeholders.js b/js/mzta-placeholders.js
index f3faecf9..c1d5092c 100644
--- a/js/mzta-placeholders.js
+++ b/js/mzta-placeholders.js
@@ -19,7 +19,8 @@
import { prefs_default } from '../options/mzta-options-default.js';
import {
getMailHeader,
- sanitizeMailHeaders
+ sanitizeMailHeaders,
+ tokenizeHtmlImages
} from './mzta-utils.js';
/* ================= PLACEHOLDERS PROPERTIES ========================================
@@ -82,6 +83,15 @@ const defaultPlaceholders = [
is_dynamic: "0",
enabled: 1,
},
+ {
+ id: 'mail_typed_html',
+ name: "__MSG_placeholder_mail_typed_html__",
+ default_value: "",
+ type: 2,
+ is_default: "1",
+ is_dynamic: "0",
+ enabled: 1,
+ },
{
id: 'mail_quoted_text',
name: "__MSG_placeholder_mail_quoted_text__",
@@ -536,20 +546,47 @@ export const placeholdersUtils = {
async getPlaceholdersValues(args) {
const {
prompt_text = "",
+ curr_prompt = {},
curr_message = {},
mail_subject = "",
body_text = "",
msg_text = {},
only_typed_text = "",
+ only_typed_html = "",
only_quoted_text = "",
selection_text = "",
selection_html = "",
tags_full_list = ["", []]
} = args || {};
+
+ let activeImageMap = {};
+ const shouldCleanImages = String(curr_prompt?.clean_embedded_images) !== "0";
+
+ let htmlBody = msg_text?.html || "";
+ let typedHtml = only_typed_html || "";
+ let selHtml = selection_html || "";
+
+ if (shouldCleanImages) {
+ if (htmlBody) {
+ const res = tokenizeHtmlImages(htmlBody, activeImageMap);
+ htmlBody = res.cleanHtml;
+ }
+ if (typedHtml) {
+ const res = tokenizeHtmlImages(typedHtml, activeImageMap);
+ typedHtml = res.cleanHtml;
+ }
+ if (selHtml) {
+ const res = tokenizeHtmlImages(selHtml, activeImageMap);
+ selHtml = res.cleanHtml;
+ }
+ }
+
let currPHs = await placeholdersUtils.extractPlaceholders(prompt_text);
// console.log(">>>>>>>>>> currPHs: " + JSON.stringify(currPHs));
// console.log(">>>>>>>>>> curr_message: " + JSON.stringify(curr_message));
let finalSubs = {};
+ finalSubs._imageMap = activeImageMap;
+
for(let currPH of currPHs){
// console.log(">>>>>>>>>> currPH: " + JSON.stringify(currPH));
switch(currPH.id){
@@ -557,11 +594,14 @@ export const placeholdersUtils = {
finalSubs['mail_text_body'] = placeholdersUtils.failSafePlaceholders(body_text);
break;
case 'mail_html_body':
- finalSubs['mail_html_body'] = placeholdersUtils.failSafePlaceholders(msg_text?.html);
+ finalSubs['mail_html_body'] = placeholdersUtils.failSafePlaceholders(htmlBody);
break;
case 'mail_typed_text':
finalSubs['mail_typed_text'] = placeholdersUtils.failSafePlaceholders(only_typed_text);
break;
+ case 'mail_typed_html':
+ finalSubs['mail_typed_html'] = placeholdersUtils.failSafePlaceholders(typedHtml);
+ break;
case 'mail_quoted_text':
finalSubs['mail_quoted_text'] = placeholdersUtils.failSafePlaceholders(only_quoted_text);
break;
@@ -584,13 +624,13 @@ export const placeholdersUtils = {
finalSubs['selected_text'] = placeholdersUtils.failSafePlaceholders(selection_text);
break;
case 'selected_html':
- finalSubs['selected_html'] = placeholdersUtils.failSafePlaceholders(selection_html);
+ finalSubs['selected_html'] = placeholdersUtils.failSafePlaceholders(selHtml);
break;
case 'mail_text_body_or_selected':
finalSubs['mail_text_body_or_selected'] = placeholdersUtils.failSafePlaceholders(selection_text || body_text);
break;
case 'mail_html_body_or_selected':
- finalSubs['mail_html_body_or_selected'] = placeholdersUtils.failSafePlaceholders(selection_html || msg_text?.html);
+ finalSubs['mail_html_body_or_selected'] = placeholdersUtils.failSafePlaceholders(selHtml || htmlBody);
break;
case 'author':
finalSubs['author'] = placeholdersUtils.failSafePlaceholders(sanitizeMailHeaders(curr_message.author));
diff --git a/js/mzta-prompts.js b/js/mzta-prompts.js
index 1e83b545..f6c6c026 100644
--- a/js/mzta-prompts.js
+++ b/js/mzta-prompts.js
@@ -52,6 +52,11 @@
0: Do not use the diff viewer
1: Use the diff viewer
+ Clean embedded images (clean_embedded_images attribute):
+ 0: Keep raw embedded images in HTML placeholders
+ 1: Remove base64 embedded images from HTML placeholders and restore them in response
+
+
================ DYNAMIC PROPERTIES (set at runtime via prompt_info)
headerMessageId (set by _openSummaryWebchat in mzta-background.js):
@@ -108,6 +113,7 @@ const defaultPrompts = [
need_custom_text: "0",
define_response_lang: "1",
use_diff_viewer: "0",
+ clean_embedded_images: "1",
chatgpt_web_model: '',
chatgpt_web_project: '',
chatgpt_web_custom_gpt: '',
@@ -127,6 +133,7 @@ const defaultPrompts = [
need_custom_text: "0",
define_response_lang: "1",
use_diff_viewer: "0",
+ clean_embedded_images: "1",
chatgpt_web_model: '',
chatgpt_web_project: '',
chatgpt_web_custom_gpt: '',
@@ -146,6 +153,7 @@ const defaultPrompts = [
need_custom_text: "1",
define_response_lang: "1",
use_diff_viewer: "0",
+ clean_embedded_images: "1",
chatgpt_web_model: '',
chatgpt_web_project: '',
chatgpt_web_custom_gpt: '',
@@ -165,6 +173,7 @@ const defaultPrompts = [
need_custom_text: "0",
define_response_lang: "1",
use_diff_viewer: "1",
+ clean_embedded_images: "1",
chatgpt_web_model: '',
chatgpt_web_project: '',
chatgpt_web_custom_gpt: '',
@@ -184,6 +193,7 @@ const defaultPrompts = [
need_custom_text: "0",
define_response_lang: "1",
use_diff_viewer: "1",
+ clean_embedded_images: "1",
chatgpt_web_model: '',
chatgpt_web_project: '',
chatgpt_web_custom_gpt: '',
@@ -203,6 +213,7 @@ const defaultPrompts = [
need_custom_text: "0",
define_response_lang: "1",
use_diff_viewer: "0",
+ clean_embedded_images: "1",
chatgpt_web_model: '',
chatgpt_web_project: '',
chatgpt_web_custom_gpt: '',
@@ -222,6 +233,7 @@ const defaultPrompts = [
need_custom_text: "0",
define_response_lang: "1",
use_diff_viewer: "1",
+ clean_embedded_images: "1",
chatgpt_web_model: '',
chatgpt_web_project: '',
chatgpt_web_custom_gpt: '',
@@ -241,6 +253,7 @@ const defaultPrompts = [
need_custom_text: "0",
define_response_lang: "0",
use_diff_viewer: "0",
+ clean_embedded_images: "1",
chatgpt_web_model: '',
chatgpt_web_project: '',
chatgpt_web_custom_gpt: '',
@@ -469,7 +482,7 @@ export function preparePromptsForExport(prompts, include_api_settings = false){
}
if(prompt.is_default == 1){
- let allowedKeys = ['id', 'enabled', 'position_compose', 'position_display', 'position_context', 'need_custom_text', 'show_in', 'custom_icon'];
+ let allowedKeys = ['id', 'enabled', 'position_compose', 'position_display', 'position_context', 'need_custom_text', 'clean_embedded_images', 'show_in', 'custom_icon'];
if(include_api_settings){
allowedKeys.push('api_type');
for (const [integration, options] of Object.entries(integration_options_config)) {
@@ -537,6 +550,7 @@ async function getDefaultPrompts_withProps() {
prompt.position_context = prefs._default_prompts_properties[prompt.id]?.position_context || prompt.position_display;
prompt.enabled = prefs._default_prompts_properties[prompt.id].enabled;
prompt.need_custom_text = prefs._default_prompts_properties[prompt.id].need_custom_text;
+ prompt.clean_embedded_images = prefs._default_prompts_properties[prompt.id]?.clean_embedded_images ?? prompt.clean_embedded_images ?? "1";
prompt.chatgpt_web_model = prefs._default_prompts_properties[prompt.id].chatgpt_web_model;
prompt.chatgpt_web_project = prefs._default_prompts_properties[prompt.id].chatgpt_web_project;
prompt.chatgpt_web_custom_gpt = (prefs._default_prompts_properties[prompt.id]?.chatgpt_web_custom_gpt || '').trim();
@@ -567,6 +581,9 @@ async function getCustomPrompts() {
if (prompt.use_diff_viewer === undefined) {
prompt.use_diff_viewer = "0";
}
+ if (prompt.clean_embedded_images === undefined) {
+ prompt.clean_embedded_images = "1";
+ }
if(prompt.chatgpt_web_model === undefined){
prompt.chatgpt_web_model = "";
}
@@ -599,6 +616,7 @@ export async function setDefaultPromptsProperties(prompts) {
position_context: (prompt.position_context === undefined || prompt.position_context === "undefined") ? "" : prompt.position_context,
enabled: (prompt.enabled === undefined || prompt.enabled === "undefined") ? "" : prompt.enabled,
need_custom_text: (prompt.need_custom_text === undefined || prompt.need_custom_text === "undefined") ? "" : prompt.need_custom_text,
+ clean_embedded_images: (prompt.clean_embedded_images === undefined || prompt.clean_embedded_images === "undefined") ? "1" : prompt.clean_embedded_images,
chatgpt_web_model: (prompt.chatgpt_web_model === undefined || prompt.chatgpt_web_model === "undefined") ? "" : prompt.chatgpt_web_model,
chatgpt_web_project: (prompt.chatgpt_web_project === undefined || prompt.chatgpt_web_project === "undefined") ? "" : prompt.chatgpt_web_project,
chatgpt_web_custom_gpt: (prompt.chatgpt_web_custom_gpt === undefined || prompt.chatgpt_web_custom_gpt === "undefined") ? "" : prompt.chatgpt_web_custom_gpt,
diff --git a/js/mzta-utils-prompt.js b/js/mzta-utils-prompt.js
index 8d0ca1be..a19cb397 100644
--- a/js/mzta-utils-prompt.js
+++ b/js/mzta-utils-prompt.js
@@ -26,6 +26,7 @@ import { getSpecialPrompts } from './mzta-prompts.js';
import { prefs_default } from '../options/mzta-options-default.js';
export const taPromptUtils = {
+ lastImageMap: {},
async getDefaultSignature(){
let prefs = await browser.storage.sync.get({ default_sign_name: prefs_default.default_sign_name });
@@ -47,6 +48,7 @@ export const taPromptUtils = {
subject_text = '',
msg_text = {},
only_typed_text = '',
+ only_typed_html = '',
only_quoted_text = '',
tags_full_list = ["", []]
} = args || {};
@@ -73,16 +75,19 @@ export const taPromptUtils = {
let finalSubs = await placeholdersUtils.getPlaceholdersValues({
prompt_text: curr_prompt.text,
+ curr_prompt: curr_prompt,
curr_message: curr_message,
mail_subject: subject_text,
body_text: body_text,
msg_text: msg_text,
only_typed_text: only_typed_text,
+ only_typed_html: only_typed_html,
only_quoted_text: only_quoted_text,
selection_text: selection_text,
selection_html: selection_html,
tags_full_list: tags_full_list
});
+ taPromptUtils.lastImageMap = finalSubs._imageMap || {};
let prefs_ph = await browser.storage.sync.get({ placeholders_use_default_value: prefs_default.placeholders_use_default_value });
fullPrompt = (placeholdersUtils.replacePlaceholders({
text: curr_prompt.text,
@@ -187,9 +192,11 @@ export const taPromptUtils = {
const finalSubs = await placeholdersUtils.getPlaceholdersValues({
prompt_text: promptText,
+ curr_prompt: prompt,
msg_text: { html: bodyHtml.html, text: bodyHtml.text },
mail_subject: mailSubject,
});
+ taPromptUtils.lastImageMap = finalSubs._imageMap || {};
const fullPrompt = placeholdersUtils.replacePlaceholders({
text: promptText,
diff --git a/js/mzta-utils.js b/js/mzta-utils.js
index 6668cfde..cbdc5afc 100644
--- a/js/mzta-utils.js
+++ b/js/mzta-utils.js
@@ -959,3 +959,78 @@ export async function* getMessages(list) {
}
}
}
+
+/**
+ * Scans HTML for elements with base64/data URIs or very long src attributes,
+ * replacing them with compact token placeholders [[THUNDERAI_IMG_N]] and returning
+ * a map to restore them later.
+ *
+ * @param {string} html
+ * @returns {{ cleanHtml: string, imageMap: Object
{
+ const src = img.getAttribute('src') || '';
+ if (src.startsWith('data:image/') || src.length > 200) {
+ const token = `[[THUNDERAI_IMG_${counter++}]]`;
+ imageMap[token] = img.outerHTML;
+ const textNode = doc.createTextNode(token);
+ img.parentNode.replaceChild(textNode, img);
+ }
+ });
+
+ return { cleanHtml: doc.body.innerHTML, imageMap };
+ } catch (e) {
+ // Fallback to regex if DOMParser fails
+ }
+ }
+
+ // Regex fallback: match full
tags that contain a data:image/ URI
+ let cleanHtml = html.replace(/
]*src=["'](data:image\/[^"']+)["'][^>]*\s*\/?>/gi, (fullMatch) => {
+ const token = `[[THUNDERAI_IMG_${counter++}]]`;
+ imageMap[token] = fullMatch;
+ return token;
+ });
+
+ return { cleanHtml, imageMap };
+}
+
+/**
+ * Restores original
tags in response text by replacing token placeholders
+ * [[THUNDERAI_IMG_N]] with original image HTML from imageMap.
+ *
+ * @param {string} text
+ * @param {Object
__MSG_customPrompts_managePrompts__
+
+
diff --git a/pages/customprompts/mzta-custom-prompts.js b/pages/customprompts/mzta-custom-prompts.js
index e713e5cc..70b55cd3 100644
--- a/pages/customprompts/mzta-custom-prompts.js
+++ b/pages/customprompts/mzta-custom-prompts.js
@@ -263,6 +263,7 @@ document.addEventListener('DOMContentLoaded', async () => {
need_custom_text: (checkboxNeedCustomTextNew.checked) ? 1 : 0,
define_response_lang: (checkboxDefineResponseLangNew.checked) ? 1 : 0,
use_diff_viewer: (checkboxUseDiffViewerNew.checked) ? 1 : 0,
+ clean_embedded_images: (checkboxCleanEmbeddedImagesNew.checked) ? 1 : 0,
enabled: 1,
position_compose: positionMax_compose + 1,
position_display: positionMax_display + 1,
@@ -675,6 +676,7 @@ function showItemRowEditor(tr) {
tr.querySelector('input.need_custom_text').disabled = false;
tr.querySelector('input.define_response_lang').disabled = false;
tr.querySelector('input.use_diff_viewer').disabled = false;
+ tr.querySelector('input.clean_embedded_images').disabled = false;
}
function hideItemRowEditor(tr) {
@@ -702,6 +704,7 @@ function hideItemRowEditor(tr) {
tr.querySelector('input.need_custom_text').disabled = true;
tr.querySelector('input.define_response_lang').disabled = true;
tr.querySelector('input.use_diff_viewer').disabled = true;
+ tr.querySelector('input.clean_embedded_images').disabled = true;
}
function toggleAdditionalPropertiesShow(tr) {
@@ -891,6 +894,7 @@ function handleConfirmClick(e) {
newValues.need_custom_text = tr.querySelector('.need_custom_text').checked ? 1 : 0;
newValues.define_response_lang = tr.querySelector('.define_response_lang').checked ? 1 : 0;
newValues.use_diff_viewer = tr.querySelector('.use_diff_viewer').checked ? 1 : 0;
+ newValues.clean_embedded_images = tr.querySelector('.clean_embedded_images').checked ? 1 : 0;
newValues.enabled = tr.querySelector('.enabled').checked ? 1 : 0;
newValues.chatgpt_web_model = tr.querySelector('.chatgpt_web_model_output').value.trim();
newValues.chatgpt_web_project = tr.querySelector('.chatgpt_web_project_output').value.trim();
@@ -975,6 +979,7 @@ function handleCopyClick(e) {
let need_custom_text = tr.querySelector('.need_custom_text').checked;
let define_response_lang = tr.querySelector('.define_response_lang').checked;
let use_diff_viewer = tr.querySelector('.use_diff_viewer').checked;
+ let clean_embedded_images = tr.querySelector('.clean_embedded_images').checked;
let chatgpt_web_model = tr.querySelector('.chatgpt_web_model_output').value;
let chatgpt_web_project = tr.querySelector('.chatgpt_web_project_output').value;
@@ -999,6 +1004,7 @@ function handleCopyClick(e) {
let checkboxUseDiffViewerNew = document.getElementById('checkboxUseDiffViewerNew');
checkboxUseDiffViewerNew.checked = use_diff_viewer;
checkboxUseDiffViewerNew.disabled = (action !== "2");
+ document.getElementById('checkboxCleanEmbeddedImagesNew').checked = clean_embedded_images;
document.getElementById('chatGPTWebModelNew').value = chatgpt_web_model;
document.getElementById('chatGPTWebProjectNew').value = chatgpt_web_project;
@@ -1057,7 +1063,7 @@ function loadPromptsList(values){
}
let options = {
- valueNames: [ { data: ['idnum'] }, 'is_default', 'id', 'name', 'text', 'type', 'action', 'position_compose', 'position_display', 'show_in', { name: 'need_selected', attr: 'checked_val'}, { name: 'need_signature', attr: 'checked_val'}, { name: 'need_custom_text', attr: 'checked_val'}, { name: 'define_response_lang', attr: 'checked_val'}, { name: 'use_diff_viewer', attr: 'checked_val'}, { name: 'enabled', attr: 'checked_val'}, 'api_type', ...api_fields ],
+ valueNames: [ { data: ['idnum'] }, 'is_default', 'id', 'name', 'text', 'type', 'action', 'position_compose', 'position_display', 'show_in', { name: 'need_selected', attr: 'checked_val'}, { name: 'need_signature', attr: 'checked_val'}, { name: 'need_custom_text', attr: 'checked_val'}, { name: 'define_response_lang', attr: 'checked_val'}, { name: 'use_diff_viewer', attr: 'checked_val'}, { name: 'clean_embedded_images', attr: 'checked_val'}, { name: 'enabled', attr: 'checked_val'}, 'api_type', ...api_fields ],
item: function(values) {
let type_output = '';
switch(String(values.type)){
@@ -1174,6 +1180,8 @@ function loadPromptsList(values){
+
+
@@ -1369,6 +1377,7 @@ function checkSelectedBoxes(checkboxes = null) {
...document.querySelectorAll('.need_custom_text[type="checkbox"]'),
...document.querySelectorAll('.define_response_lang[type="checkbox"]'),
...document.querySelectorAll('.use_diff_viewer[type="checkbox"]'),
+ ...document.querySelectorAll('.clean_embedded_images[type="checkbox"]'),
...document.querySelectorAll('.enabled[type="checkbox"]'),
];
}