From 774b60d7b8d10a67f6f89ee2990b9f9ed0793d5f Mon Sep 17 00:00:00 2001 From: mic Date: Mon, 19 Aug 2024 17:21:09 +0200 Subject: [PATCH 01/61] version set to 2.1.0 --- CHANGELOG.md | 4 ++++ manifest.json | 2 +- options/mzta-release-notes.html | 4 ++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ac57748..d60d9887 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ +

Version 2.1.0 - ??/??/2024

+

Version 2.0.5 - 19/08/2024

Version 2.0.5 - 19/08/2024

From 8d381455b679e276eceaeaf928c881ab0258e5ac Mon Sep 17 00:00:00 2001 From: Mic Date: Thu, 22 Aug 2024 21:17:30 +0200 Subject: [PATCH 31/61] Update README.md --- README.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index aedf5d52..c335f23f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # ![ThunderAI icon](images/icon-32px.png "ThunderAI") ThunderAI -ThunderAI is a Thunderbird Addon that uses the capabilities of ChatGPT to enhance email management. +ThunderAI is a Thunderbird Addon that uses the capabilities of ChatGPT or Ollama to enhance email management. It enables users to analyse, write, correct, and optimize their emails, facilitating more effective and professional communication. @@ -11,10 +11,20 @@ Find out how [here](https://micz.it/thunderbird-addon-thunderai/custom-prompts/) > [!TIP] -> There is no need for an API key and is possibile to use this extension even with a free account! +> **Using ChatGPT** +> +> There is no need for an API key and is possibile to use this extension even with a free account, using the web interface! > > Starting from version 2.0.0, if you want to connect with the OpenAI API integration, now you can use an API Key! +
+ +> [!TIP] +> **Using Ollama** +> +> From version 2.1.0 is possible to use a local Ollama server! +> +> Just remember to add `OLLAMA_ORIGINS = moz-extension://*` to the Ollama server environment variables.
From 43c6c68238729037d4c13415dbf2f2ae2d7e5ecb Mon Sep 17 00:00:00 2001 From: mic Date: Thu, 22 Aug 2024 22:09:44 +0200 Subject: [PATCH 32/61] implemented a loop on exception to send a message to the tab --- mzta-background.js | 173 +++++++++++++++++++++++++++------------------ 1 file changed, 103 insertions(+), 70 deletions(-) diff --git a/mzta-background.js b/mzta-background.js index 733b0083..0a706398 100644 --- a/mzta-background.js +++ b/mzta-background.js @@ -167,23 +167,23 @@ async function openChatGPT(promptText, action, curr_tabId, prompt_name = '', do_ let createdTab = newWindow.tabs[0]; // Wait for tab loaded. - await new Promise(resolve => { - const tabIsLoaded = tab => { - return tab.status == "complete" && tab.url != "about:blank"; - }; - const listener = (tabId, changeInfo, updatedTab) => { - if (tabIsLoaded(updatedTab)) { - browser.tabs.onUpdated.removeListener(listener); - resolve(); - } - } - // Early exit if loaded already - if (tabIsLoaded(createdTab)) { - resolve(); - } else { - browser.tabs.onUpdated.addListener(listener); - } - }); + // await new Promise(resolve => { + // const tabIsLoaded = tab => { + // return tab.status == "complete" && tab.url != "about:blank"; + // }; + // const listener = (tabId, changeInfo, updatedTab) => { + // if (tabIsLoaded(updatedTab)) { + // browser.tabs.onUpdated.removeListener(listener); + // resolve(); + // } + // } + // // Early exit if loaded already + // if (tabIsLoaded(createdTab)) { + // resolve(); + // } else { + // browser.tabs.onUpdated.addListener(listener); + // } + // }); let pre_script = `let mztaStatusPageDesc="`+ browser.i18n.getMessage("prefs_status_page") +`"; let mztaForceCompletionDesc="`+ browser.i18n.getMessage("chatgpt_force_completion") +`"; @@ -192,17 +192,28 @@ async function openChatGPT(promptText, action, curr_tabId, prompt_name = '', do_ let mztaPromptName="[`+ i18nConditionalGet(prompt_name) +`]"; `; - browser.tabs.executeScript(createdTab.id, { code: pre_script + mzta_script, matchAboutBlank: false }) - .then(async () => { - taLog.log("[ThunderAI] ChatGPT web interface script injected successfully"); - let mailMessage = await browser.messageDisplay.getDisplayedMessage(curr_tabId); - let mailMessageId = -1; - if(mailMessage) mailMessageId = mailMessage.id; - browser.tabs.sendMessage(createdTab.id, { command: "chatgpt_send", prompt: promptText, action: action, tabId: curr_tabId, mailMessageId: mailMessageId}); - }) - .catch(err => { - console.error("[ThunderAI] ChatGPT web interface error injecting the script: ", err); - }); + let done1 = false; + + while(!done1){ + try { + browser.tabs.executeScript(createdTab.id, { code: pre_script + mzta_script, matchAboutBlank: false }) + .then(async () => { + taLog.log("[ThunderAI] ChatGPT web interface script injected successfully"); + let mailMessage = await browser.messageDisplay.getDisplayedMessage(curr_tabId); + let mailMessageId = -1; + if(mailMessage) mailMessageId = mailMessage.id; + browser.tabs.sendMessage(createdTab.id, { command: "chatgpt_send", prompt: promptText, action: action, tabId: curr_tabId, mailMessageId: mailMessageId}); + }) + .catch(err => { + console.error("[ThunderAI] ChatGPT web interface error injecting the script: ", err); + }); + done1 = true; + } catch (error) { + console.error('[ThunderAI] Error sending message to ChatGPT Web Interface: ', error); + done1 = false; + } + await new Promise(resolve => setTimeout(resolve, 500)); + } break; // chatgpt_web case 'chatgpt_api': @@ -217,26 +228,26 @@ async function openChatGPT(promptText, action, curr_tabId, prompt_name = '', do_ createdWindowID = newWindow2.id; let createdTab2 = newWindow2.tabs[0]; - if(await isThunderbird128OrGreater()){ - // Wait for tab loaded. - await new Promise(resolve => { - const tabIsLoaded2 = tab => { - return tab.status == "complete" && tab.url != "about:blank"; - }; - const listener2 = (tabId, changeInfo, updatedTab) => { - if (tabIsLoaded2(updatedTab)) { - browser.tabs.onUpdated.removeListener(listener2); - resolve(); - } - } - // Early exit if loaded already - if (tabIsLoaded2(createdTab2)) { - resolve(); - } else { - browser.tabs.onUpdated.addListener(listener2); - } - }); - } + // if(await isThunderbird128OrGreater()){ + // // Wait for tab loaded. + // await new Promise(resolve => { + // const tabIsLoaded2 = tab => { + // return tab.status == "complete" && tab.url != "about:blank"; + // }; + // const listener2 = (tabId, changeInfo, updatedTab) => { + // if (tabIsLoaded2(updatedTab)) { + // browser.tabs.onUpdated.removeListener(listener2); + // resolve(); + // } + // } + // // Early exit if loaded already + // if (tabIsLoaded2(createdTab2)) { + // resolve(); + // } else { + // browser.tabs.onUpdated.addListener(listener2); + // } + // }); + // } let mailMessage = await browser.messageDisplay.getDisplayedMessage(curr_tabId); let mailMessageId2 = -1; @@ -252,7 +263,18 @@ async function openChatGPT(promptText, action, curr_tabId, prompt_name = '', do_ return; } - browser.tabs.sendMessage(createdTab2.id, { command: "api_send", prompt: promptText, action: action, tabId: curr_tabId, mailMessageId: mailMessageId2, do_custom_text: do_custom_text}); + let done2 = false; + + while(!done2){ + try { + browser.tabs.sendMessage(createdTab2.id, { command: "api_send", prompt: promptText, action: action, tabId: curr_tabId, mailMessageId: mailMessageId2, do_custom_text: do_custom_text}); + done2 = true; + } catch (error) { + console.error('[ThunderAI] Error sending message to OpenAI ChatGPI API: ', error); + done2 = false; + } + await new Promise(resolve => setTimeout(resolve, 500)); + } break; // chatgpt_api case 'ollama_api': @@ -267,26 +289,26 @@ async function openChatGPT(promptText, action, curr_tabId, prompt_name = '', do_ createdWindowID = newWindow3.id; let createdTab3 = newWindow3.tabs[0]; - if(await isThunderbird128OrGreater()){ + // if(await isThunderbird128OrGreater()){ // Wait for tab loaded. - await new Promise(resolve => { - const tabIsLoaded3 = tab => { - return tab.status == "complete" && tab.url != "about:blank"; - }; - const listener3 = (tabId, changeInfo, updatedTab) => { - if (tabIsLoaded3(updatedTab)) { - browser.tabs.onUpdated.removeListener(listener3); - resolve(); - } - } - // Early exit if loaded already - if (tabIsLoaded3(createdTab3)) { - resolve(); - } else { - browser.tabs.onUpdated.addListener(listener3); - } - }); - } + // await new Promise(resolve => { + // const tabIsLoaded3 = tab => { + // return tab.status == "complete" && tab.url != "about:blank"; + // }; + // const listener3 = (tabId, changeInfo, updatedTab) => { + // if (tabIsLoaded3(updatedTab)) { + // browser.tabs.onUpdated.removeListener(listener3); + // resolve(); + // } + // } + // // Early exit if loaded already + // if (tabIsLoaded3(createdTab3)) { + // resolve(); + // } else { + // browser.tabs.onUpdated.addListener(listener3); + // } + // }); + //} let mailMessage3 = await browser.messageDisplay.getDisplayedMessage(curr_tabId); let mailMessageId3 = -1; @@ -301,8 +323,19 @@ async function openChatGPT(promptText, action, curr_tabId, prompt_name = '', do_ browser.tabs.sendMessage(createdTab3.id, { command: "api_error", error: browser.i18n.getMessage('ollama_empty_model')}); return; } - - browser.tabs.sendMessage(createdTab3.id, { command: "api_send", prompt: promptText, action: action, tabId: curr_tabId, mailMessageId: mailMessageId3, do_custom_text: do_custom_text}); + + let done3 = false; + + while(!done3){ + try { + await browser.tabs.sendMessage(createdTab3.id, { command: "api_send", prompt: promptText, action: action, tabId: curr_tabId, mailMessageId: mailMessageId3, do_custom_text: do_custom_text}); + done3 = true; + } catch (error) { + console.error('[ThunderAI] Error sending message to Ollama API: ', error); + done3 = false; + } + await new Promise(resolve => setTimeout(resolve, 500)); + } break; // ollama_api } } From 1ab1da1b688cfa78afa222968053422e2c0bcab3 Mon Sep 17 00:00:00 2001 From: mic Date: Thu, 22 Aug 2024 22:17:02 +0200 Subject: [PATCH 33/61] fixed chatgpt web loop. see #117 --- mzta-background.js | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/mzta-background.js b/mzta-background.js index 0a706398..1e1a8af2 100644 --- a/mzta-background.js +++ b/mzta-background.js @@ -196,20 +196,15 @@ async function openChatGPT(promptText, action, curr_tabId, prompt_name = '', do_ while(!done1){ try { - browser.tabs.executeScript(createdTab.id, { code: pre_script + mzta_script, matchAboutBlank: false }) - .then(async () => { - taLog.log("[ThunderAI] ChatGPT web interface script injected successfully"); - let mailMessage = await browser.messageDisplay.getDisplayedMessage(curr_tabId); - let mailMessageId = -1; - if(mailMessage) mailMessageId = mailMessage.id; - browser.tabs.sendMessage(createdTab.id, { command: "chatgpt_send", prompt: promptText, action: action, tabId: curr_tabId, mailMessageId: mailMessageId}); - }) - .catch(err => { - console.error("[ThunderAI] ChatGPT web interface error injecting the script: ", err); - }); + await browser.tabs.executeScript(createdTab.id, { code: pre_script + mzta_script, matchAboutBlank: false }); + let mailMessage = await browser.messageDisplay.getDisplayedMessage(curr_tabId); + let mailMessageId = -1; + if(mailMessage) mailMessageId = mailMessage.id; + browser.tabs.sendMessage(createdTab.id, { command: "chatgpt_send", prompt: promptText, action: action, tabId: curr_tabId, mailMessageId: mailMessageId}); + taLog.log("[ThunderAI] ChatGPT Web script injected successfully"); done1 = true; } catch (error) { - console.error('[ThunderAI] Error sending message to ChatGPT Web Interface: ', error); + console.error('[ThunderAI] ChatGPT Web error injecting the script: ', error); done1 = false; } await new Promise(resolve => setTimeout(resolve, 500)); From 8071fe4f1612856049b34f8f68f3c71842448630 Mon Sep 17 00:00:00 2001 From: mic Date: Thu, 22 Aug 2024 22:20:32 +0200 Subject: [PATCH 34/61] error messages improved. see #117 --- mzta-background.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/mzta-background.js b/mzta-background.js index 1e1a8af2..8510a5f2 100644 --- a/mzta-background.js +++ b/mzta-background.js @@ -204,7 +204,8 @@ async function openChatGPT(promptText, action, curr_tabId, prompt_name = '', do_ taLog.log("[ThunderAI] ChatGPT Web script injected successfully"); done1 = true; } catch (error) { - console.error('[ThunderAI] ChatGPT Web error injecting the script: ', error); + console.error('[ThunderAI] [ChatGPT Web] Error connecting to the window chat: ', error); + console.error('[ThunderAI] [ChatGPT Web] trying again...'); done1 = false; } await new Promise(resolve => setTimeout(resolve, 500)); @@ -265,7 +266,8 @@ async function openChatGPT(promptText, action, curr_tabId, prompt_name = '', do_ browser.tabs.sendMessage(createdTab2.id, { command: "api_send", prompt: promptText, action: action, tabId: curr_tabId, mailMessageId: mailMessageId2, do_custom_text: do_custom_text}); done2 = true; } catch (error) { - console.error('[ThunderAI] Error sending message to OpenAI ChatGPI API: ', error); + console.error('[ThunderAI] [OpenAI ChatGPI API] Error connecting to the window chat: ', error); + console.error('[ThunderAI] [OpenAI ChatGPI API] trying again...'); done2 = false; } await new Promise(resolve => setTimeout(resolve, 500)); @@ -326,7 +328,8 @@ async function openChatGPT(promptText, action, curr_tabId, prompt_name = '', do_ await browser.tabs.sendMessage(createdTab3.id, { command: "api_send", prompt: promptText, action: action, tabId: curr_tabId, mailMessageId: mailMessageId3, do_custom_text: do_custom_text}); done3 = true; } catch (error) { - console.error('[ThunderAI] Error sending message to Ollama API: ', error); + console.error('[ThunderAI] [Ollama API] Error connecting to the window chat: ', error); + console.error('[ThunderAI] [Ollama API] trying again...'); done3 = false; } await new Promise(resolve => setTimeout(resolve, 500)); From afa7815ebe8024ba592d7252923e85cb10dd345b Mon Sep 17 00:00:00 2001 From: mic Date: Thu, 22 Aug 2024 22:26:12 +0200 Subject: [PATCH 35/61] log messages improved. see #117 --- mzta-background.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/mzta-background.js b/mzta-background.js index 8510a5f2..aa039d1e 100644 --- a/mzta-background.js +++ b/mzta-background.js @@ -201,11 +201,12 @@ async function openChatGPT(promptText, action, curr_tabId, prompt_name = '', do_ let mailMessageId = -1; if(mailMessage) mailMessageId = mailMessage.id; browser.tabs.sendMessage(createdTab.id, { command: "chatgpt_send", prompt: promptText, action: action, tabId: curr_tabId, mailMessageId: mailMessageId}); + taLog.log('[ChatGPT Web] Connection succeded!'); taLog.log("[ThunderAI] ChatGPT Web script injected successfully"); done1 = true; } catch (error) { - console.error('[ThunderAI] [ChatGPT Web] Error connecting to the window chat: ', error); - console.error('[ThunderAI] [ChatGPT Web] trying again...'); + taLog.warn('[ChatGPT Web] Error connecting to the window chat: ', error); + taLog.warn('[ChatGPT Web] Trying again...'); done1 = false; } await new Promise(resolve => setTimeout(resolve, 500)); @@ -264,10 +265,11 @@ async function openChatGPT(promptText, action, curr_tabId, prompt_name = '', do_ while(!done2){ try { browser.tabs.sendMessage(createdTab2.id, { command: "api_send", prompt: promptText, action: action, tabId: curr_tabId, mailMessageId: mailMessageId2, do_custom_text: do_custom_text}); + taLog.log('[OpenAI ChatGPI] Connection succeded!'); done2 = true; } catch (error) { - console.error('[ThunderAI] [OpenAI ChatGPI API] Error connecting to the window chat: ', error); - console.error('[ThunderAI] [OpenAI ChatGPI API] trying again...'); + taLog.warn('[OpenAI ChatGPI API] Error connecting to the window chat: ', error); + taLog.warn('[OpenAI ChatGPI API] Trying again...'); done2 = false; } await new Promise(resolve => setTimeout(resolve, 500)); @@ -326,10 +328,11 @@ async function openChatGPT(promptText, action, curr_tabId, prompt_name = '', do_ while(!done3){ try { await browser.tabs.sendMessage(createdTab3.id, { command: "api_send", prompt: promptText, action: action, tabId: curr_tabId, mailMessageId: mailMessageId3, do_custom_text: do_custom_text}); + taLog.log('[Ollama API] Connection succeded!'); done3 = true; } catch (error) { - console.error('[ThunderAI] [Ollama API] Error connecting to the window chat: ', error); - console.error('[ThunderAI] [Ollama API] trying again...'); + taLog.warn('[Ollama API] Error connecting to the window chat: ', error); + taLog.warn('[Ollama API] Trying again...'); done3 = false; } await new Promise(resolve => setTimeout(resolve, 500)); From 5dfd5925b00a5970bda9e3f460b66bb391124417 Mon Sep 17 00:00:00 2001 From: mic Date: Thu, 22 Aug 2024 22:28:43 +0200 Subject: [PATCH 36/61] no more quirks warning on api web chat --- api_webchat/index.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/api_webchat/index.html b/api_webchat/index.html index cdad673c..154e7957 100644 --- a/api_webchat/index.html +++ b/api_webchat/index.html @@ -1,3 +1,5 @@ + + @@ -18,3 +20,4 @@ + \ No newline at end of file From 9fa8bc50c48e728bd717bcef38bead1f1992087c Mon Sep 17 00:00:00 2001 From: mic Date: Thu, 22 Aug 2024 22:29:58 +0200 Subject: [PATCH 37/61] changeDebug method added. see #115 --- js/mzta-logger.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/js/mzta-logger.js b/js/mzta-logger.js index 9a050fdf..d8fa874a 100644 --- a/js/mzta-logger.js +++ b/js/mzta-logger.js @@ -26,6 +26,10 @@ export class taLogger { this.prefix = "[ThunderAI Logger | " + prefix + "] "; } + changeDebug(do_debug) { + this.do_debug = do_debug; + } + log(msg, do_debug = -1) { if(do_debug !== -1) this.do_debug = do_debug; if(this.do_debug === true) console.log(this.prefix + msg); @@ -37,5 +41,5 @@ export class taLogger { warn(msg) { console.warn(this.prefix + msg); - } + } }; \ No newline at end of file From d7cef5dceb5c618d8f8ded902df853f507ea5d4a Mon Sep 17 00:00:00 2001 From: mic Date: Thu, 22 Aug 2024 22:32:00 +0200 Subject: [PATCH 38/61] usgin the new logger changeDebug method. fixes #115 --- mzta-background.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mzta-background.js b/mzta-background.js index 733b0083..11e661dc 100644 --- a/mzta-background.js +++ b/mzta-background.js @@ -142,7 +142,7 @@ messenger.runtime.onMessage.addListener(async (message, sender, sendResponse) => async function openChatGPT(promptText, action, curr_tabId, prompt_name = '', do_custom_text = 0) { let prefs = await browser.storage.sync.get(prefs_default); - taLog = new taLogger("mzta-background",prefs.do_debug); + taLog.changeDebug(prefs.do_debug); prefs = checkScreenDimensions(prefs); //console.log(">>>>>>>>>>>>>>>> prefs: " + JSON.stringify(prefs)); taLog.log("[ThunderAI] Prompt length: " + promptText.length); From c66927f472d14c5b9917b12ceb003e3bca66b916 Mon Sep 17 00:00:00 2001 From: mic Date: Thu, 22 Aug 2024 22:42:02 +0200 Subject: [PATCH 39/61] really closing the connection to the server when stopped by the user. --- api_webchat/model-worker-ollama.js | 2 ++ api_webchat/model-worker-openai.js | 1 + 2 files changed, 3 insertions(+) diff --git a/api_webchat/model-worker-ollama.js b/api_webchat/model-worker-ollama.js index f8c0c0fb..9d66b757 100644 --- a/api_webchat/model-worker-ollama.js +++ b/api_webchat/model-worker-ollama.js @@ -65,9 +65,11 @@ self.onmessage = async function(event) { while (true) { if (stopStreaming) { stopStreaming = false; + reader.cancel(); conversationHistory.push({ role: 'assistant', content: assistantResponseAccumulator }); assistantResponseAccumulator = ''; postMessage({ type: 'tokensDone' }); + break; } const { done, value } = await reader.read(); diff --git a/api_webchat/model-worker-openai.js b/api_webchat/model-worker-openai.js index a8f041d5..de9ba27c 100644 --- a/api_webchat/model-worker-openai.js +++ b/api_webchat/model-worker-openai.js @@ -110,6 +110,7 @@ self.onmessage = async function(event) { while (true) { if (stopStreaming) { stopStreaming = false; + reader.cancel(); conversationHistory.push({ role: 'assistant', content: assistantResponseAccumulator }); assistantResponseAccumulator = ''; postMessage({ type: 'tokensDone' }); From a514e41137245ef8f75f88eff764a443edf8156b Mon Sep 17 00:00:00 2001 From: mic Date: Thu, 22 Aug 2024 23:07:32 +0200 Subject: [PATCH 40/61] ollama api: showing an error when connection is interrupted. fixes #114 --- _locales/en/messages.json | 8 +++ api_webchat/controller.js | 5 +- api_webchat/model-worker-ollama.js | 80 +++++++++++++++++------------- 3 files changed, 58 insertions(+), 35 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 5e3add3b..b56452d8 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -482,5 +482,13 @@ "andModel": { "message": "and model", "description": "" + }, + "error_connection_interrupted": { + "message": "The connection to the server was unexpectedly interrupted", + "description": "" + }, + "ollama_api_request_failed": { + "message": "Ollama API request failed", + "description": "" } } \ No newline at end of file diff --git a/api_webchat/controller.js b/api_webchat/controller.js index d9b51b7d..5493143a 100644 --- a/api_webchat/controller.js +++ b/api_webchat/controller.js @@ -79,10 +79,13 @@ switch (llm) { break; case "ollama_api": { let prefs_api = await browser.storage.sync.get({ollama_host: '', ollama_model: ''}); + let i18nStrings = {}; + i18nStrings["ollama_api_request_failed"] = browser.i18n.getMessage('ollama_api_request_failed'); + i18nStrings["error_connection_interrupted"] = browser.i18n.getMessage('error_connection_interrupted'); //console.log(">>>>>>>>>>> ollama_host: " + prefs_api_key.ollama_host); messageInput.setModel(prefs_api.ollama_model); messagesArea.setLLMName("Ollama Local"); - worker.postMessage({ type: 'init', ollama_host: prefs_api.ollama_host, ollama_model: prefs_api.ollama_model}); + worker.postMessage({ type: 'init', ollama_host: prefs_api.ollama_host, ollama_model: prefs_api.ollama_model, i18nStrings: i18nStrings}); messagesArea.appendUserMessage(browser.i18n.getMessage("ollama_api_connecting") + " " + prefs_api.ollama_host + " " +browser.i18n.getMessage("AndModel") + " " + prefs_api.ollama_model + " ...", "info"); break; } diff --git a/api_webchat/model-worker-ollama.js b/api_webchat/model-worker-ollama.js index 9d66b757..def38787 100644 --- a/api_webchat/model-worker-ollama.js +++ b/api_webchat/model-worker-ollama.js @@ -26,6 +26,7 @@ let ollama_host = null; let ollama_model = ''; let ollama = null; let stopStreaming = false; +let i18nStrings = null; let conversationHistory = []; let assistantResponseAccumulator = ''; @@ -37,6 +38,7 @@ self.onmessage = async function(event) { ollama_model = event.data.ollama_model; //console.log(">>>>>>>>>>> ollama_host: " + ollama_host); ollama = new Ollama(ollama_host, ollama_model, true); + i18nStrings = event.data.i18nStrings; break; // init case 'chatMessage': conversationHistory.push({ role: 'user', content: event.data.message }); @@ -55,47 +57,57 @@ self.onmessage = async function(event) { error_message = errorJSON.error; //console.log(">>>>>>>>>>>>> errorJSON.error.message: " + JSON.stringify(errorJSON.error.message)); } - postMessage({ type: 'error', payload: "Ollama API request failed: " + error_message }); + postMessage({ type: 'error', payload: i18nStrings["ollama_api_request_failed"] + ": " + error_message }); throw new Error("[ThunderAI] Ollama API request failed: " + response.status + " " + response.statusText + ", Detail: " + errorDetail); } const reader = response.body.getReader(); const decoder = new TextDecoder("utf-8"); - - while (true) { - if (stopStreaming) { - stopStreaming = false; - reader.cancel(); - conversationHistory.push({ role: 'assistant', content: assistantResponseAccumulator }); - assistantResponseAccumulator = ''; - postMessage({ type: 'tokensDone' }); + + try { + while (true) { + if (stopStreaming) { + stopStreaming = false; + reader.cancel(); + conversationHistory.push({ role: 'assistant', content: assistantResponseAccumulator }); + assistantResponseAccumulator = ''; + postMessage({ type: 'tokensDone' }); - break; - } - const { done, value } = await reader.read(); - if (done) { - conversationHistory.push({ role: 'assistant', content: assistantResponseAccumulator }); - assistantResponseAccumulator = ''; - postMessage({ type: 'tokensDone' }); - break; - } - // lots of low-level Ollama response parsing stuff - const chunk = decoder.decode(value); - //console.log(">>>>>>>>>>>>> chunk: " + chunk); - const lines = chunk.split("\n"); - const parsedLines = lines - .map((line) => line.replace(/^chunk: /, "").trim()) // Remove the "chunk: " prefix - .filter((line) => line !== "" && line !== "[DONE]") // Remove empty lines and "[DONE]" - .map((line) => JSON.parse(line)); // Parse the JSON string - - for (const parsedLine of parsedLines) { - const { message } = parsedLine; - const { content } = message; - // Update the UI with the new content - if (content) { - assistantResponseAccumulator += content; - postMessage({ type: 'newToken', payload: { token: content } }); + break; } + const { done, value } = await reader.read(); + if (done) { + conversationHistory.push({ role: 'assistant', content: assistantResponseAccumulator }); + assistantResponseAccumulator = ''; + postMessage({ type: 'tokensDone' }); + break; + } + // lots of low-level Ollama response parsing stuff + const chunk = decoder.decode(value); + //console.log(">>>>>>>>>>>>> chunk: " + chunk); + const lines = chunk.split("\n"); + const parsedLines = lines + .map((line) => line.replace(/^chunk: /, "").trim()) // Remove the "chunk: " prefix + .filter((line) => line !== "" && line !== "[DONE]") // Remove empty lines and "[DONE]" + .map((line) => JSON.parse(line)); // Parse the JSON string + + for (const parsedLine of parsedLines) { + const { message } = parsedLine; + const { content } = message; + // Update the UI with the new content + if (content) { + assistantResponseAccumulator += content; + postMessage({ type: 'newToken', payload: { token: content } }); + } + } + } + } catch (error) { + if (error instanceof TypeError && error.message.includes('Error in input stream')) { + console.error('[ThudenderAI] The connection to the server was unexpectedly interrupted:', error); + postMessage({ type: 'error', payload: i18nStrings['error_connection_interrupted'] + ": " + error.message }); + } else { + console.error('[ThudenderAI] Ollama API request failed:', error); + postMessage({ type: 'error', payload: i18nStrings["ollama_api_request_failed"] + ": " + error.message }); } } break; //chatMessage From 8483ea627b953087d357aeec1a5c71c61a8178ee Mon Sep 17 00:00:00 2001 From: mic Date: Thu, 22 Aug 2024 23:16:33 +0200 Subject: [PATCH 41/61] showing an error in fetching models, if needed. fixes #116 --- options/mzta-options.js | 64 ++++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 29 deletions(-) diff --git a/options/mzta-options.js b/options/mzta-options.js index 99150c98..56e0d821 100644 --- a/options/mzta-options.js +++ b/options/mzta-options.js @@ -220,36 +220,42 @@ select_ollama_model.appendChild(ollama_option); document.getElementById('btnUpdateOllamaModels').addEventListener('click', async () => { document.getElementById('ollama_model_fetch_loading').style.display = 'inline'; let ollama = new Ollama(document.getElementById("ollama_host").value, true); - let data = await ollama.fetchModels(); - if(!data){ - document.getElementById('ollama_model_fetch_loading').style.display = 'none'; - console.error("[ThunderAI] " + browser.i18n.getMessage("Ollama_Models_Error_fetching")); - alert(browser.i18n.getMessage("Ollama_Models_Error_fetching")); - return; - } - if(!data.ok){ - let errorDetail = JSON.parse(data.error); - document.getElementById('ollama_model_fetch_loading').style.display = 'none'; - console.error("[ThunderAI] " + browser.i18n.getMessage("Ollama_Models_Error_fetching")); - alert(browser.i18n.getMessage("Ollama_Models_Error_fetching")+": " + errorDetail.error.message); - return; - } - if(data.response.models.length == 0){ - document.getElementById('ollama_model_fetch_loading').style.display = 'none'; - console.error("[ThunderAI] " + browser.i18n.getMessage("Ollama_Models_Error_fetching")); - alert(browser.i18n.getMessage("Ollama_Models_Error_fetching")+": " + browser.i18n.getMessage("Ollama_Models_Error_NoModels")); - return; - } - taLog.log("Ollama models: " + JSON.stringify(data)); - data.response.models.forEach(model => { - if (!Array.from(select_ollama_model.options).some(option => option.value === model.model)) { - const option = document.createElement('option'); - option.value = model.model; - option.text = model.name + " (" + model.model + ")"; - select_ollama_model.appendChild(option); + try { + let data = await ollama.fetchModels(); + if(!data){ + document.getElementById('ollama_model_fetch_loading').style.display = 'none'; + console.error("[ThunderAI] " + browser.i18n.getMessage("Ollama_Models_Error_fetching")); + alert(browser.i18n.getMessage("Ollama_Models_Error_fetching")); + return; } - }); - document.getElementById('ollama_model_fetch_loading').style.display = 'none'; + if(!data.ok){ + let errorDetail = JSON.parse(data.error); + document.getElementById('ollama_model_fetch_loading').style.display = 'none'; + console.error("[ThunderAI] " + browser.i18n.getMessage("Ollama_Models_Error_fetching")); + alert(browser.i18n.getMessage("Ollama_Models_Error_fetching")+": " + errorDetail.error.message); + return; + } + if(data.response.models.length == 0){ + document.getElementById('ollama_model_fetch_loading').style.display = 'none'; + console.error("[ThunderAI] " + browser.i18n.getMessage("Ollama_Models_Error_fetching")); + alert(browser.i18n.getMessage("Ollama_Models_Error_fetching")+": " + browser.i18n.getMessage("Ollama_Models_Error_NoModels")); + return; + } + taLog.log("Ollama models: " + JSON.stringify(data)); + data.response.models.forEach(model => { + if (!Array.from(select_ollama_model.options).some(option => option.value === model.model)) { + const option = document.createElement('option'); + option.value = model.model; + option.text = model.name + " (" + model.model + ")"; + select_ollama_model.appendChild(option); + } + }); + document.getElementById('ollama_model_fetch_loading').style.display = 'none'; + } catch (error) { + document.getElementById('ollama_model_fetch_loading').style.display = 'none'; + taLog.error(browser.i18n.getMessage("Ollama_Models_Error_fetching")); + alert(browser.i18n.getMessage("Ollama_Models_Error_fetching")+": " + error.message); + } warnAPIKeyEmpty(); }); From 0d572a79efeaa7980f5d4dd0df7cf911fbd37867 Mon Sep 17 00:00:00 2001 From: mic Date: Thu, 22 Aug 2024 23:51:18 +0200 Subject: [PATCH 42/61] added a status message. fixes #119 --- _locales/en/messages.json | 4 ++++ api_webchat/controller.js | 1 + api_webchat/index.html | 2 +- api_webchat/messageInput.js | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 39 insertions(+), 1 deletion(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index b56452d8..9dae9335 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -490,5 +490,9 @@ "ollama_api_request_failed": { "message": "Ollama API request failed", "description": "" + }, + "WaitingServerReponse": { + "message": "Waiting for the server response", + "description": "" } } \ No newline at end of file diff --git a/api_webchat/controller.js b/api_webchat/controller.js index 5493143a..38993267 100644 --- a/api_webchat/controller.js +++ b/api_webchat/controller.js @@ -101,6 +101,7 @@ worker.onmessage = function(event) { break; case 'newToken': messagesArea.handleNewToken(payload.token); + messageInput.setStatusMessage('Receiving data...'); break; case 'tokensDone': messagesArea.handleTokensDone(promptData); diff --git a/api_webchat/index.html b/api_webchat/index.html index 154e7957..7989868d 100644 --- a/api_webchat/index.html +++ b/api_webchat/index.html @@ -9,7 +9,7 @@
- +
diff --git a/api_webchat/messageInput.js b/api_webchat/messageInput.js index c9cbcce9..3228c558 100644 --- a/api_webchat/messageInput.js +++ b/api_webchat/messageInput.js @@ -56,6 +56,16 @@ messagesInputStyle .textContent = ` cursor: pointer; border-radius: 10px; } + #statusLogger{ + font-size: 0.8rem; + position: absolute; + bottom: 1.5em; + right: 5em; + border: 1px solid lightgrey; + border-radius: 5px; + padding: 5px; + background: #212121; + } @media (prefers-color-scheme: dark) { #messageInputField { background-color: #303030; @@ -115,6 +125,12 @@ stopIcon.appendChild(stopRect); stopButton.appendChild(stopIcon); messageInputTemplate.content.appendChild(stopButton); +const statusLogger = document.createElement('div'); +statusLogger.id = 'statusLogger'; +statusLogger.textContent = ''; +statusLogger.style.display = 'none'; +messageInputTemplate.content.appendChild(statusLogger); + class MessageInput extends HTMLElement { model = ''; @@ -127,6 +143,7 @@ class MessageInput extends HTMLElement { this._messageInputField = shadowRoot.querySelector('#messageInputField'); this._sendButton = shadowRoot.querySelector('#sendButton'); this._stopButton = shadowRoot.querySelector('#stopButton'); + this._statusLogger = shadowRoot.querySelector('#statusLogger'); this._messageInputField.addEventListener('keydown', this._handleKeyDown.bind(this)); this._sendButton.addEventListener('click', this._handleClick.bind(this)); @@ -166,6 +183,20 @@ class MessageInput extends HTMLElement { this._stopButton.setAttribute('disabled', 'disabled'); this._stopButton.style.display = 'none'; this._stopButton.title = browser.i18n.getMessage("chagtp_api_send_button") + ": " + this.model; + this.hideStatusMessage(); + this.setStatusMessage(''); + } + + setStatusMessage(message) { + this._statusLogger.textContent = message; + } + + showStatusMessage() { + this._statusLogger.style.display = 'block'; + } + + hideStatusMessage() { + this._statusLogger.style.display = 'none'; } _handleKeyDown(event) { @@ -200,6 +231,8 @@ class MessageInput extends HTMLElement { if (this.messagesAreaComponent) { this.messagesAreaComponent.appendUserMessage(messageContent); } + this.setStatusMessage(browser.i18n.getMessage('WaitingServerReponse') + '...'); + this.showStatusMessage(); this.worker.postMessage({ type: 'chatMessage', message: messageContent }); } From 2dcdef11b668e246a96bcb76694c88127c1535f3 Mon Sep 17 00:00:00 2001 From: mic Date: Thu, 22 Aug 2024 23:59:25 +0200 Subject: [PATCH 43/61] correctly handling ollama options visual feedback --- options/mzta-options.js | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/options/mzta-options.js b/options/mzta-options.js index 56e0d821..db1938a3 100644 --- a/options/mzta-options.js +++ b/options/mzta-options.js @@ -124,7 +124,7 @@ function showConnectionOptions() { }); } -function warnAPIKeyEmpty() { +function warn_ChatGPT_APIKeyEmpty() { let apiKeyInput = document.getElementById('chatgpt_api_key'); let btnFetchChatGPTModels = document.getElementById('btnUpdateChatGPTModels'); let modelChatGPT = document.getElementById('chatgpt_model'); @@ -146,6 +146,28 @@ function warnAPIKeyEmpty() { } } +function warn_Ollama_HostEmpty() { + let hostInput = document.getElementById('ollama_host'); + let btnFetchOllamaModels = document.getElementById('btnUpdateOllamaModels'); + let modelOllama = document.getElementById('ollama_model'); + if(hostInput.value === ''){ + hostInput.style.border = '2px solid red'; + btnFetchOllamaModels.disabled = true; + modelOllama.disabled = true; + modelOllama.selectedIndex = -1; + modelOllama.style.border = 'none'; + }else{ + hostInput.style.border = 'none'; + btnFetchOllamaModels.disabled = false; + modelOllama.disabled = false; + if((modelOllama.selectedIndex === -1)||(modelOllama.value === '')){ + modelOllama.style.border = '2px solid red'; + }else{ + modelOllama.style.border = 'none'; + } + } +} + document.addEventListener('DOMContentLoaded', async () => { await restoreOptions(); i18n.updateDocument(); @@ -170,8 +192,10 @@ document.addEventListener('DOMContentLoaded', async () => { let conntype_select = document.getElementById("connection_type"); conntype_select.addEventListener("change", showConnectionOptions); - conntype_select.addEventListener("change", warnAPIKeyEmpty); - document.getElementById("chatgpt_api_key").addEventListener("change", warnAPIKeyEmpty); + conntype_select.addEventListener("change", warn_ChatGPT_APIKeyEmpty); + conntype_select.addEventListener("change", warn_Ollama_HostEmpty); + document.getElementById("chatgpt_api_key").addEventListener("change", warn_ChatGPT_APIKeyEmpty); + document.getElementById("ollama_host").addEventListener("change", warn_Ollama_HostEmpty); let prefs = await browser.storage.sync.get({chatgpt_model: '', ollama_model: ''}); @@ -181,7 +205,7 @@ document.addEventListener('DOMContentLoaded', async () => { chatgpt_option.value = prefs.chatgpt_model; chatgpt_option.text = prefs.chatgpt_model; select_chatgpt_model.appendChild(chatgpt_option); - select_chatgpt_model.addEventListener("change", warnAPIKeyEmpty); + select_chatgpt_model.addEventListener("change", warn_ChatGPT_APIKeyEmpty); document.getElementById('btnUpdateChatGPTModels').addEventListener('click', async () => { document.getElementById('chatgpt_model_fetch_loading').style.display = 'inline'; @@ -206,7 +230,7 @@ document.addEventListener('DOMContentLoaded', async () => { document.getElementById('chatgpt_model_fetch_loading').style.display = 'none'; }) - warnAPIKeyEmpty(); + warn_ChatGPT_APIKeyEmpty(); }); // Ollama API Model fetching @@ -215,7 +239,7 @@ const ollama_option = document.createElement('option'); ollama_option.value = prefs.ollama_model; ollama_option.text = prefs.ollama_model; select_ollama_model.appendChild(ollama_option); -// select_ollama_model.addEventListener("change", warnAPIKeyEmpty); +select_ollama_model.addEventListener("change", warn_Ollama_HostEmpty); document.getElementById('btnUpdateOllamaModels').addEventListener('click', async () => { document.getElementById('ollama_model_fetch_loading').style.display = 'inline'; @@ -257,6 +281,6 @@ select_ollama_model.appendChild(ollama_option); alert(browser.i18n.getMessage("Ollama_Models_Error_fetching")+": " + error.message); } - warnAPIKeyEmpty(); + warn_Ollama_HostEmpty(); }); }, { once: true }); From 3d3b61e95f2be1fb62e25d535ce33eb258640a31 Mon Sep 17 00:00:00 2001 From: mic Date: Fri, 23 Aug 2024 00:02:59 +0200 Subject: [PATCH 44/61] added ollama host example --- _locales/en/messages.json | 4 ++++ options/mzta-options.html | 1 + 2 files changed, 5 insertions(+) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 9dae9335..faf6b87e 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -494,5 +494,9 @@ "WaitingServerReponse": { "message": "Waiting for the server response", "description": "" + }, + "prefs_Ollama_Host_Info": { + "message": "Something like", + "description": "" } } \ No newline at end of file diff --git a/options/mzta-options.html b/options/mzta-options.html index fda5f5fe..f76382f9 100644 --- a/options/mzta-options.html +++ b/options/mzta-options.html @@ -115,6 +115,7 @@