diff --git a/CHANGELOG.md b/CHANGELOG.md index 39e7aa10..ff39af4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,10 +15,25 @@
"/, '
').replace(/"<\/p>$/, '
'); // strip quotation marks //console.log(">>>>>>>>>>>> fullTextHTMLAtAssignment: " + fullTextHTMLAtAssignment); actionButton.addEventListener('click', async () => { + if(promptData.mailMessageId == -1) { // we are using the reply from the compose window! + promptData.action = "2"; // replace text + } switch(promptData.action) { case "1": // do reply // console.log("[ThunderAI] (do reply) fullTextHTMLAtAssignment: " + fullTextHTMLAtAssignment); @@ -239,7 +242,7 @@ class MessagesArea extends HTMLElement { browser.runtime.sendMessage({command: "chatgpt_close", window_id: (await browser.windows.getCurrent()).id}); break; case "2": // replace text - // console.log("[ThunderAI] (replace text) fullTextHTMLAtAssignment: " + fullTextHTMLAtAssignment); + // console.log("[ThunderAI] (replace text) fullTextHTMLAtAssignment: " + fullTextHTMLAtAssignment); await browser.runtime.sendMessage({command: "chatgpt_replaceSelectedText", text: fullTextHTMLAtAssignment, tabId: promptData.tabId, mailMessageId: promptData.mailMessageId}); browser.runtime.sendMessage({command: "chatgpt_close", window_id: (await browser.windows.getCurrent()).id}); break; diff --git a/js/api/ollama.js b/js/api/ollama.js index 0f300243..5cc41215 100644 --- a/js/api/ollama.js +++ b/js/api/ollama.js @@ -21,11 +21,13 @@ export class Ollama { host = ''; model = ''; stream = false; + num_ctx = 0; - constructor(host, model, stream = false) { + constructor(host, model, stream = false, num_ctx = 0) { this.host = host.trim().replace(/\/+$/, ""); this.model = model; this.stream = stream; + this.num_ctx = num_ctx; } fetchModels = async () => { @@ -78,6 +80,7 @@ export class Ollama { model: this.model, messages: messages, stream: this.stream, + ...(this.num_ctx > 0 ? { options: { num_ctx: parseInt(this.num_ctx) } } : {}) }), }); return response; diff --git a/js/mzta-chatgpt.js b/js/mzta-chatgpt.js index b538d855..3bc1e56a 100644 --- a/js/mzta-chatgpt.js +++ b/js/mzta-chatgpt.js @@ -652,6 +652,9 @@ browser.runtime.onMessage.addListener((message, sender, sendResponse) => { current_message = message; current_tabId = message.tabId; current_mailMessageId = message.mailMessageId; + if(current_mailMessageId == -1) { // we are using the reply from the compose window! + current_action = '2'; // replace text + } run(); break; case "chatgpt_alive": diff --git a/js/mzta-compose-script.js b/js/mzta-compose-script.js index f858b93b..05fc96a6 100644 --- a/js/mzta-compose-script.js +++ b/js/mzta-compose-script.js @@ -24,12 +24,19 @@ switch (message.command) { case "replaceSelectedText": { const selectedText = window.getSelection().toString(); + let force_insert = false; if (selectedText === '') { - return Promise.resolve(false); + if(!confirm(browser.i18n.getMessage("Replace_No_Selected_Text"))) { + return Promise.resolve(false); + }else{ + force_insert = true; + } } const sel = window.getSelection(); if (!sel || sel.type !== "Range" || !sel.rangeCount) { - return Promise.resolve(false); + if(!force_insert) { + return Promise.resolve(false); + } } const r = sel.getRangeAt(0); r.deleteContents(); diff --git a/js/mzta-placeholders.js b/js/mzta-placeholders.js index 4dfb6d0e..08bbd1d7 100644 --- a/js/mzta-placeholders.js +++ b/js/mzta-placeholders.js @@ -276,8 +276,12 @@ export const placeholdersUtils = { if (skip_additional_text && (p1 === 'additional_text')) { return match; } + const currPlaceholder = defaultPlaceholders.find(ph => ph.id === p1); + if (!currPlaceholder) { + return match; + } // Replace if found, otherwise keep the original or substitute with default value - return replacements[p1] || (use_default_value ? defaultPlaceholders.find(ph => ph.id === p1).default_value : match); + return replacements[p1] || (use_default_value ? currPlaceholder.default_value : match); }); }, diff --git a/js/mzta-utils.js b/js/mzta-utils.js index e62500ba..ba7e8b74 100644 --- a/js/mzta-utils.js +++ b/js/mzta-utils.js @@ -121,41 +121,44 @@ export async function getMailSubject(tab){ } } -export async function getMailBody(fullMessage){ - let text = ''; - let html = ''; +function extractTextParts(fullMessage) { + const textParts = []; - // console.log(">>>>>>>>>> fullMessage.contentType.trim().toLowerCase(): " + fullMessage.contentType.trim().toLowerCase()); - // console.log(">>>>>>>>>> fullMessage.body: " + fullMessage.body); - - if (fullMessage.contentType.trim().toLowerCase() === "text/plain") { - text = fullMessage.body; - } - if (fullMessage.contentType.trim().toLowerCase() === "text/html") { - html = fullMessage.body; - } - - if((text == undefined || text == null || text == '') && (html == undefined || html == null || html == '')) { - for (let part of fullMessage.parts) { - if (part.contentType.trim().toLowerCase() === "text/plain") { - text = part.body; - } - if (part.contentType.trim().toLowerCase() === "text/html") { - html = part.body; - } - if((text == undefined || text == null || text == '') && (html == undefined || html == null || html == '')) { - for (let subpart of part.parts) { - if (subpart.contentType.trim().toLowerCase() === "text/plain") { - text = subpart.body; - } - if (subpart.contentType.trim().toLowerCase() === "text/html") { - html = subpart.body; - } + function walkParts(parts) { + for (const part of parts) { + if (part.parts && part.parts.length > 0) { + // Recursively walk through sub-parts + walkParts(part.parts); + } else { + // Check if contentType starts with "text/" + if (part.contentType && part.contentType.startsWith("text/")) { + textParts.push(part); } } } } + + if (fullMessage.parts && fullMessage.parts.length > 0) { + walkParts(fullMessage.parts); + } + + return textParts; +} +export function getMailBody(fullMessage){ + const textParts = extractTextParts(fullMessage); + let text = ""; + let html = ""; + for (const part of textParts) { + if (part.contentType === "text/plain") { + text += part.body; + } else if (part.contentType === "text/html") { + html += part.body; + } + } + if(html === "") { + html = text.replace(/\n/g, "tags with a newline at the beginning + // and removes all other HTML tags + return htmlString + .replace(/
/gi, '') // removes
tags + .replace(/<\/p>/gi, '\n') // replaces
tags with newline + .replace(/<[^>]*>/g, '') // removes any other HTML tags + .trim(); // removes leading/trailing whitespace +} + +// This method is used to convert the model string id used in the URL +// to the model string used in the webpage export function getGPTWebModelString(model) { model = model.toLowerCase().trim(); switch (model) { @@ -196,14 +211,6 @@ export function getGPTWebModelString(model) { return '4o'; case 'gpt-4o-mini': return '4o mini'; - case 'gpt-4': - return 'gpt-4'; - case 'o1': - return 'o1'; - case 'o3-mini': - return 'o3-mini'; - case 'o3-mini-high': - return 'o3-mini-high'; default: return model; } diff --git a/js/workers/model-worker-ollama.js b/js/workers/model-worker-ollama.js index 21396f5d..f29bab62 100644 --- a/js/workers/model-worker-ollama.js +++ b/js/workers/model-worker-ollama.js @@ -25,6 +25,7 @@ import { taLogger } from '../mzta-logger.js'; let ollama_host = null; let ollama_model = ''; +let ollama_num_ctx = 0; let ollama = null; let stopStreaming = false; let i18nStrings = null; @@ -39,8 +40,9 @@ self.onmessage = async function(event) { case 'init': ollama_host = event.data.ollama_host; ollama_model = event.data.ollama_model; + ollama_num_ctx = event.data.ollama_num_ctx; //console.log(">>>>>>>>>>> ollama_host: " + ollama_host); - ollama = new Ollama(ollama_host, ollama_model, true); + ollama = new Ollama(ollama_host, ollama_model, true, ollama_num_ctx); do_debug = event.data.do_debug; i18nStrings = event.data.i18nStrings; taLog = new taLogger('model-worker-ollama', do_debug); diff --git a/mzta-background.js b/mzta-background.js index 1ddc21d6..52015219 100644 --- a/mzta-background.js +++ b/mzta-background.js @@ -20,7 +20,7 @@ import { mzta_script } from './js/mzta-chatgpt.js'; import { prefs_default } from './options/mzta-options-default.js'; import { mzta_Menus } from './js/mzta-menus.js'; import { taLogger } from './js/mzta-logger.js'; -import { getCurrentIdentity, getOriginalBody, replaceBody, setBody, i18nConditionalGet, generateCallID, migrateCustomPromptsStorage, migrateDefaultPromptsPropStorage, getGPTWebModelString, getTagsList, createTag, assignTagsToMessage, checkIfTagExists, getActiveSpecialPromptsIDs, checkSparksPresence, getMessages, getMailBody, extractJsonObject, contextMenuID_AddTags, contextMenuID_Spamfilter, sanitizeChatGPTModelData, sanitizeChatGPTWebCustomData } from './js/mzta-utils.js'; +import { getCurrentIdentity, getOriginalBody, replaceBody, setBody, i18nConditionalGet, generateCallID, migrateCustomPromptsStorage, migrateDefaultPromptsPropStorage, getGPTWebModelString, getTagsList, createTag, assignTagsToMessage, checkIfTagExists, getActiveSpecialPromptsIDs, checkSparksPresence, getMessages, getMailBody, extractJsonObject, contextMenuID_AddTags, contextMenuID_Spamfilter, sanitizeChatGPTModelData, sanitizeChatGPTWebCustomData, stripHtmlKeepLines } from './js/mzta-utils.js'; import { taPromptUtils } from './js/mzta-utils-prompt.js'; import { mzta_specialCommand } from './js/mzta-special-commands.js'; import { getSpamFilterPrompt } from './js/mzta-prompts.js'; @@ -205,15 +205,22 @@ messenger.runtime.onMessage.addListener((message, sender, sendResponse) => { async function _replaceSelectedText(tabId, text) { //console.log('chatgpt_replaceSelectedText: [' + tabId +'] ' + text) original_html = await getOriginalBody(tabId); + let prefs_repl = await browser.storage.sync.get({composing_plain_text: prefs_default.composing_plain_text}); + if(prefs_repl.composing_plain_text){ + text = stripHtmlKeepLines(text); + } await browser.tabs.sendMessage(tabId, { command: "replaceSelectedText", text: text, tabId: tabId }); return true; } return _replaceSelectedText(message.tabId, message.text); case 'chatgpt_replyMessage': async function _replyMessage(message) { - const paragraphsHtmlString = message.text; + let paragraphsHtmlString = message.text; //console.log(">>>>>>>>>>>> paragraphsHtmlString: " + paragraphsHtmlString); - let prefs = await browser.storage.sync.get({reply_type: prefs_default.reply_type}); + let prefs = await browser.storage.sync.get({reply_type: prefs_default.reply_type, composing_plain_text: prefs_default.composing_plain_text}); + if(prefs.composing_plain_text){ + paragraphsHtmlString = stripHtmlKeepLines(paragraphsHtmlString); + } //console.log('reply_type: ' + prefs.reply_type); let replyType = 'replyToAll'; if(prefs.reply_type === 'reply_sender'){ @@ -878,7 +885,7 @@ async function processEmails(messages, addTagsAuto, spamFilter) { if (addTagsAuto || spamFilter) { curr_fullMessage = await browser.messages.getFull(message.id); - msg_text = await getMailBody(curr_fullMessage); + msg_text = getMailBody(curr_fullMessage); body_text = msg_text.text.replace(/\s+/g, ' ').trim(); } diff --git a/options/mzta-options-default.js b/options/mzta-options-default.js index 3f9fe33b..a6d1ca66 100644 --- a/options/mzta-options-default.js +++ b/options/mzta-options-default.js @@ -22,6 +22,7 @@ export const prefs_default = { chatgpt_win_width: 700, default_chatgpt_lang: '', default_sign_name: '', + composing_plain_text: false, connection_type: 'chatgpt_web', //Other values: 'chatgpt_api', 'ollama_api', 'openai_comp_api', 'google_gemini_api' chatgpt_web_model: '', chatgpt_web_tempchat: false, @@ -32,6 +33,7 @@ export const prefs_default = { chatgpt_developer_messages: '', ollama_host: '', ollama_model: '', + ollama_num_ctx: 0, openai_comp_host: '', // For OpenAI Compatible API as LM-Studio openai_comp_model: '', openai_comp_api_key: '', diff --git a/options/mzta-options.html b/options/mzta-options.html index 7c56b274..4b538ee8 100644 --- a/options/mzta-options.html +++ b/options/mzta-options.html @@ -101,6 +101,17 @@ +