Merge pull request #149 from micz/2.2.0

Merging v2.2.0
This commit is contained in:
Mic 2024-10-07 21:26:41 +02:00 committed by GitHub
commit 550b3c3f1f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
35 changed files with 2400 additions and 432 deletions

View file

@ -4,6 +4,24 @@
<h2>Version 2.2.0 - ??/??/2024</h2>
<ul>
<li><i>[ChatGPT Web]</i> Removed the option to force the ChatGPT4 model over ChatGPT3.5, since it was useless now.</li>
<li><i>[ChatGPT Web]</i> Minor internal improvements.</li>
<li><i>[OpenAI Comp API]</i> Added a new integration method to use a local LLM via an API compatible with OpenAI's API specifications [<a href="https://github.com/micz/ThunderAI/issues/126">#126</a>].</li>
<li>Custom Prompts storage space incremented to 5MB, using <i>storage.local</i>. Added also the total occupied space at the bottom of the Custom Prompts page [<a href="https://github.com/micz/ThunderAI/issues/129">#129</a>].</li>
<li><i>[ChatGPT Web]</i> Updated the information in the option page.</li>
<li>A new dynamic menu for selecting prompts has been added. In addition to clicking the ThunderAI button, you can now use the CTRL+ALT+A keyboard shortcut [<a href="https://micz.it/thunderbird-addon-thunderai/dynamic-menu/">More info</a>] [<a href="https://github.com/micz/ThunderAI/issues/130">#130</a>].</li>
<li>Added an option to order alphabetically the prompts in the menu.</li>
<li>Allow 0 as the dimensions for the chat window in the options page to prevent issues, such as those encountered with NixOS/Hyprland [<a href="https://github.com/micz/ThunderAI/issues/137">#137</a>].</li>
<li>Is now possible to ask for additional text before sending the prompts also for default prompts [<a href="https://github.com/micz/ThunderAI/issues/135">#135</a>].</li>
<li>Fixed a bug that occurred when importing prompts after an export without closing the Custom Prompts page.</li>
<li>Brazilian Portuguese (pt-br) translation added, thanks to Bruno Pereira de Souza.</li>
<li><i>[Ollama API]</i> Added a link to the <a href="https://micz.it/thunderbird-addon-thunderai/ollama-cors-information/">CORS information page</a>.</li>
<li><i>[ChatGPT API][Ollama API][OpenAI Comp API]</i> Improved handling of streaming responses even in case of broken chunks [<a href="https://github.com/micz/ThunderAI/issues/147">#147</a>].</li>
<li>Fixed a race condition that occurred when opening the chat window under certain circumstances. Thanks to <a href="https://github.com/jobisoft" target="_blank">@jobisoft</a> for helping refine the code, and to <a href="https://github.com/Mikilio" target="_blank">@Mikilio</a> and <a href="https://github.com/mattcaron" target="_blank">@mattcaron</a> for the extensive testing [<a href="https://github.com/micz/ThunderAI/issues/143">#143</a>].</li>
</ul>
<h2>Version 2.1.5 - 23/09/2024</h2>
<ul>
<li><i>[ChatGPT Web]</i> Working again in Thunderbird 115. Implemented a workaround for <i>Intl.Segmenter</i> [<a href="https://github.com/micz/ThunderAI/issues/139">#139</a>].</li>

View file

@ -26,6 +26,13 @@ Find out how [here](https://micz.it/thunderbird-addon-thunderai/custom-prompts/)
>
> Just remember to add `OLLAMA_ORIGINS = moz-extension://*` to the Ollama server environment variables.
<br>
> [!TIP]
> **Using an OpenAI Compatible API**
>
> From version 2.2.0 is possible to use a local OpenAI Compatible API server, like LM Studio!
<br>
@ -52,9 +59,28 @@ Are you using this addon in your Thunderbird?
<br>
## Thanks to
<ul><li><a href="https://github.com/KudoAI/chatgpt.js">chatgpt.js</a> for providing methods to interact with the ChatGTP frontend.</li>
<li><a href="https://github.com/ali-raheem/Aify">Aify</a> for inspiration.</li>
<li><a href="https://github.com/boxabirds">Julian Harris</a> for his project <a href="https://github.com/boxabirds/chatgpt-frontend-nobuild">chatgpt-frontend-nobuild</a>, that has been used as a starting point for the API Web Interface.</li>
</ul>
<i>The specific references are described in the corresponding source files.</i>
## Attributions
### Translations
- English (en-US): [Mic](https://github.com/micz/)
- French (fr): Generated automatically
- German (de): Generated automatically
- Italian (it): [Mic](https://github.com/micz/)
- Português Brasileiro (pt-br): Bruno Pereira de Souza
<br>
### Miscellaneous
- <a href="https://github.com/KudoAI/chatgpt.js">chatgpt.js</a> for providing methods to interact with the ChatGTP frontend
- <a href="https://github.com/ali-raheem/Aify">Aify</a> for inspiration
- <a href="https://github.com/boxabirds">Julian Harris</a> for his project <a href="https://github.com/boxabirds/chatgpt-frontend-nobuild">chatgpt-frontend-nobuild</a>, that has been used as a starting point for the API Web Interface
<br>
### Graphics
- Addon icon thanks for the help to ChatGPT-4 ;-)
- <a href="https://loading.io">loading.io</a> for the dynamic menu loading SVG
- [Fluent Design System](https://www.iconfinder.com/fluent-designsystem) for the Custom Prompts table sorting icons
- [JessiGue](https://www.flaticon.com/authors/jessigue) for the show/hide icon for api key fields

View file

@ -1,7 +1,7 @@
{
"extensionDescription": {
"message": "Verwende ChatGPT oder Ollama, um deine E-Mails zu verbessern!",
"description": "Beschreibung der Erweiterung."
"description": "Description of the extension."
},
"menu_title": {
"message": "KI",
@ -303,14 +303,6 @@
"message": "Antworttyp",
"description": ""
},
"prefs_OptionText_chatpgt_use_gpt4": {
"message": "Verwenden Sie ChatGPT-4",
"description": ""
},
"prefs_OptionText_chatpgt_use_gpt4_info": {
"message": "Nachdem Sie diese Option geändert haben, müssen Sie beim ersten Gebrauch das richtige Modell auswählen.",
"description": ""
},
"prefs_OptionText_btnManagePrompts": {
"message": "Verwalten Sie Ihre Eingabeaufforderungen",
"description": ""
@ -320,7 +312,7 @@
"description": ""
},
"prefsInfoDesc_1": {
"message": "Wenn eine weiße Seite erscheint, wenn das ChatGPT-Fenster geöffnet wird, bedeutet dies, dass es ein Problem beim Laden der ChatGPT-Seite gibt, das nicht mit der Verwendung dieses Add-Ons zusammenhängt.",
"message": "Es könnte passieren, dass sich die Weboberfläche von ChatGPT ändert und dadurch die Funktionsweise des Add-ons beeinträchtigt wird. Überprüfen Sie die Seite \"Service Status\", die unten auf dieser Seite verlinkt ist. Denken Sie auch daran, dass Sie sich beim ersten Mal, wenn Sie ThunderAI verwenden, bei ChatGPT anmelden müssen.",
"description": ""
},
"prefsInfoDesc_2": {
@ -328,7 +320,19 @@
"description": ""
},
"prefsInfoDesc_3": {
"message": "Um die Integration mit Ollama zu nutzen, müssen Sie einen lokalen Ollama-Server einrichten. Sobald der Server läuft, geben Sie dessen Adresse in das dafür vorgesehene Feld in der Anwendung ein. Um eine ordnungsgemäße Kommunikation zwischen der App und dem Ollama-Server sicherzustellen, denken Sie daran, OLLAMA_ORIGINS=moz-extension://* festzulegen.",
"message": "Um die Integration mit Ollama zu nutzen, müssen Sie einen lokalen Ollama-Server einrichten. Sobald der Server läuft, geben Sie dessen Adresse in das dafür vorgesehene Feld in der Anwendung ein. Um eine ordnungsgemäße Kommunikation zwischen der ThunderAI und dem Ollama-Server sicherzustellen, denken Sie daran, OLLAMA_ORIGINS=moz-extension://* festzulegen.",
"description": ""
},
"prefsInfoDesc_4": {
"message": "Um diese Integration zu nutzen, müssen Sie einen lokalen Server einrichten, der mit der OpenAI-API kompatibel ist, wie z.B. LM Studio. Sobald der Server läuft, geben Sie dessen Adresse in das dafür vorgesehene Feld innerhalb der Anwendung ein. Um eine ordnungsgemäße Kommunikation zwischen ThunderAI und dem lokalen Server sicherzustellen, denken Sie daran, die CORS-Einstellungen korrekt zu konfigurieren.",
"description": ""
},
"prefsInfoDesc_5": {
"message": "Denken Sie daran, dass Sie das ThunderAI-Menü mit der Tastenkombination STRG+ALT+A öffnen können.",
"description": ""
},
"prefsInfoDesc_6": {
"message": "Sie können die Tastenkombination ändern, indem Sie auf das Zahnradsymbol oben rechts auf dieser Seite klicken und \"Erweiterungskürzel verwalten\" auswählen.",
"description": ""
},
"prefsDonation_1": {
@ -451,7 +455,11 @@
"message": "Ollama API (Lokales LLM)",
"description": ""
},
"prefs_Ollama_Host": {
"prefs_Connection_type_OpenAI_Comp_API": {
"message": "OpenAI-kompatible API",
"description": ""
},
"prefs_API_Host": {
"message": "Host-Adresse",
"description": ""
},
@ -467,7 +475,7 @@
"message": "Fehler beim Abrufen der Ollama-Modelle",
"description": ""
},
"Ollama_Models_Error_NoModels": {
"API_Models_Error_NoModels": {
"message": "Keine Modelle gefunden",
"description": ""
},
@ -499,8 +507,76 @@
"message": "Warten auf die Serverantwort.",
"description": ""
},
"prefs_Ollama_Host_Info": {
"prefs_API_Host_Info": {
"message": "Etwas wie",
"description": ""
},
"OpenAIComp_Models": {
"message": "OpenAI-kompatible API-Modelle",
"description": ""
},
"OpenAIComp_Models_Fetch": {
"message": "Aktualisiere die Liste der OpenAI-kompatiblen API-Modelle.",
"description": ""
},
"OpenAIComp_Models_Error_fetching": {
"message": "Fehler beim Abrufen der OpenAI-kompatiblen API-Modelle.",
"description": ""
},
"OpenAIComp_empty_host": {
"message": "Sie haben keine Host-Adresse für die OpenAI-kompatible API hinzugefügt. Bitte fügen Sie eine auf der Optionsseite ein.",
"description": ""
},
"OpenAIComp_empty_model": {
"message": "Sie haben kein Modell für die OpenAI-kompatible API ausgewählt. Bitte wählen Sie eines auf der Optionsseite aus.",
"description": ""
},
"OpenAIComp_api_connecting": {
"message": "Versuche, eine Verbindung zum OpenAI-kompatiblen lokalen API-Server über den Host herzustellen.",
"description": ""
},
"OpenAIComp_api_request_failed": {
"message": "OpenAI-kompatible API-Anfrage fehlgeschlagen",
"description": ""
},
"prefs_OpenAIComp_ChatName": {
"message": "Chat-Name",
"description": ""
},
"prefs_OpenAIComp_ChatName_Info": {
"message": "Dies ist der Name, der im AI-Chat verwendet wird.",
"description": ""
},
"StorageSpace": {
"message": "Gesamtspeicherplatz belegt",
"description": ""
},
"SearchPrompt": {
"message": "Suchanfragen",
"description": ""
},
"prefs_OptionText_dynamic_menu_force_enter": {
"message": "Menü: Sofortige Aufforderung senden",
"description": ""
},
"prefs_OptionText_dynamic_menu_force_enter_info": {
"message": "Wenn diese Option aktiviert ist, wird die Tastenkombination STRG+ALT+A den hervorgehobenen Prompt aus dem Menü automatisch senden. Andernfalls wird der Prompt-Name dem Benutzer angezeigt, und es ist ein weiterer Druck auf die Eingabetaste erforderlich, um ihn zu senden.",
"description": ""
},
"prefs_OptionText_dynamic_menu_order_alphabet": {
"message": "Menü: Alphabetisch sortieren",
"description": ""
},
"prefs_OptionText_dynamic_menu_order_alphabet_info": {
"message": "Wenn diese Option aktiviert ist, werden die Prompts im Menü alphabetisch sortiert.",
"description": ""
},
"prefs_OptionText_chatgpt_win_dims_info": {
"message": "Auf 0 setzen, wenn Sie die Fenstergröße nicht angeben möchten.",
"description": ""
},
"Optional": {
"message": "Optional",
"description": ""
}
}

View file

@ -303,14 +303,6 @@
"message": "Reply type",
"description": ""
},
"prefs_OptionText_chatpgt_use_gpt4": {
"message": "Use ChatGPT-4",
"description": ""
},
"prefs_OptionText_chatpgt_use_gpt4_info": {
"message": "After changing this option, you have to choose the right model the first time you use it.",
"description": ""
},
"prefs_OptionText_btnManagePrompts": {
"message": "Manage your prompts",
"description": ""
@ -320,7 +312,7 @@
"description": ""
},
"prefsInfoDesc_1": {
"message": "If a blank or white page appears when opening the ChatGPT web interface window, it means there is a problem loading the ChatGPT page, not related to the use of this add-on.",
"message": "It could happen that the ChatGPT web interface changes in a way that breaks how the addon works. Check the \"Service Status\" page linked at the bottom of this page. Also, remember that the first time you use ThunderAI, you need to log in to ChatGPT.",
"description": ""
},
"prefsInfoDesc_2": {
@ -328,7 +320,19 @@
"description": ""
},
"prefsInfoDesc_3": {
"message": "To use the integration with Ollama, you need to set up a local Ollama server. Once the server is running, enter its address in the designated field within the application. To ensure proper communication between the app and the Ollama server remember to set OLLAMA_ORIGINS=moz-extension://*.",
"message": "To use the integration with Ollama, you need to set up a local Ollama server. Once the server is running, enter its address in the designated field within the application. To ensure proper communication between ThunderAI and the Ollama server remember to set OLLAMA_ORIGINS=moz-extension://*.",
"description": ""
},
"prefsInfoDesc_4": {
"message": "To use this integration, you need to set up a local server compatible with the OpenAI API, like LM Studio. Once the server is running, enter its address in the designated field within the application. To ensure proper communication between ThunderAI and the local server remember to set the CORS settings correctly.",
"description": ""
},
"prefsInfoDesc_5": {
"message": "Remember that you can open the ThunderAI menu using the keyboard shortcut CTRL+ALT+A.",
"description": ""
},
"prefsInfoDesc_6": {
"message": "You can change the shortcut clicking on the cogwheel icon at the top right of this page and choosing \"Manage Extension Shortcut\".",
"description": ""
},
"prefsDonation_1": {
@ -451,7 +455,11 @@
"message": "Ollama API (Local LLM)",
"description": ""
},
"prefs_Ollama_Host": {
"prefs_Connection_type_OpenAI_Comp_API": {
"message": "OpenAI Compatible API",
"description": ""
},
"prefs_API_Host": {
"message": "Host Address",
"description": ""
},
@ -467,7 +475,7 @@
"message": "Error trying to fetch Ollama models",
"description": ""
},
"Ollama_Models_Error_NoModels": {
"API_Models_Error_NoModels": {
"message": "No models found",
"description": ""
},
@ -499,8 +507,80 @@
"message": "Waiting for the server response",
"description": ""
},
"prefs_Ollama_Host_Info": {
"prefs_API_Host_Info": {
"message": "Something like",
"description": ""
},
"OpenAIComp_Models": {
"message": "OpenAI Compatible API Models",
"description": ""
},
"OpenAIComp_Models_Fetch": {
"message": "Update OpenAI Compatible API Models list",
"description": ""
},
"OpenAIComp_Models_Error_fetching": {
"message": "Error trying to fetch OpenAI Compatible API models",
"description": ""
},
"OpenAIComp_empty_host": {
"message": "You've not added an Host Address for the OpenAI Compatible API. Please insert one in the options page.",
"description": ""
},
"OpenAIComp_empty_model": {
"message": "You've not choosen a model for the OpenAI Compatible API. Please choose one in the options page.",
"description": ""
},
"OpenAIComp_api_connecting": {
"message": "Attempting to connect to the OpenAI Compatible API Local Server using the host",
"description": ""
},
"OpenAIComp_api_request_failed": {
"message": "OpenAI Comp API request failed",
"description": ""
},
"prefs_OpenAIComp_ChatName": {
"message": "Chat Name",
"description": ""
},
"prefs_OpenAIComp_ChatName_Info": {
"message": "This is the name that will be used in the AI chat.",
"description": ""
},
"StorageSpace": {
"message": "Total storage space occupied",
"description": ""
},
"SearchPrompt": {
"message": "Search prompts",
"description": ""
},
"prefs_OptionText_dynamic_menu_force_enter": {
"message": "Menu: immediate prompt send",
"description": ""
},
"prefs_OptionText_dynamic_menu_force_enter_info": {
"message": "If checked, using the keyboard shortcut CTRL+ALT+A will automatically send the hightlighted prompt from the menu. Otherwise, the prompt name will be displayed to the user, requiring another press of the Enter key to send it.",
"description": ""
},
"prefs_OptionText_dynamic_menu_order_alphabet": {
"message": "Menu: order alphabetically",
"description": ""
},
"prefs_OptionText_dynamic_menu_order_alphabet_info": {
"message": "If checked, the prompts in the menu will be ordered alphabetically.",
"description": ""
},
"prefs_OptionText_chatgpt_win_dims_info": {
"message": "Set to 0 if you don't want to specify the window size.",
"description": ""
},
"prefs_OpenAIComp_API_Key": {
"message": "OpenAI Comp API Key",
"description": ""
},
"Optional": {
"message": "Optional",
"description": ""
}
}

View file

@ -1,7 +1,7 @@
{
"extensionDescription": {
"message": "Utilise ChatGPT ou Ollama pour améliorer tes e-mails !",
"description": "Description de l'extension."
"description": "Description of the extension."
},
"menu_title": {
"message": "IA",
@ -303,14 +303,6 @@
"message": "Type de réponse",
"description": ""
},
"prefs_OptionText_chatpgt_use_gpt4": {
"message": "Utiliser ChatGPT-4",
"description": ""
},
"prefs_OptionText_chatpgt_use_gpt4_info": {
"message": "Après avoir changé cette option, vous devez choisir le bon modèle la première fois que vous l'utilisez.",
"description": ""
},
"prefs_OptionText_btnManagePrompts": {
"message": "Gérer vos prompts",
"description": ""
@ -320,7 +312,7 @@
"description": ""
},
"prefsInfoDesc_1": {
"message": "Si une page blanche apparaît lors de l'ouverture de la fenêtre ChatGPT, cela signifie qu'il y a un problème de chargement de la page ChatGPT, non lié à l'utilisation de cet addon.",
"message": "Il est possible que l'interface web de ChatGPT change de manière à perturber le fonctionnement de l'addon. Consultez la page \"État du service\" située en bas de cette page. N'oubliez pas non plus que la première fois que vous utilisez ThunderAI, vous devez vous connecter à ChatGPT.",
"description": ""
},
"prefsInfoDesc_2": {
@ -328,7 +320,19 @@
"description": ""
},
"prefsInfoDesc_3": {
"message": "Pour utiliser l'intégration avec Ollama, vous devez configurer un serveur Ollama local. Une fois que le serveur est en marche, entrez son adresse dans le champ prévu à cet effet dans l'application. Pour garantir une communication adéquate entre l'application et le serveur Ollama, n'oubliez pas de définir OLLAMA_ORIGINS=moz-extension://*.",
"message": "Pour utiliser l'intégration avec Ollama, vous devez configurer un serveur Ollama local. Une fois que le serveur est en marche, entrez son adresse dans le champ prévu à cet effet dans l'application. Pour garantir une communication adéquate entre ThunderAI et le serveur Ollama, n'oubliez pas de définir OLLAMA_ORIGINS=moz-extension://*.",
"description": ""
},
"prefsInfoDesc_4": {
"message": "Pour utiliser cette intégration, vous devez configurer un serveur local compatible avec l'API OpenAI, tel que LM Studio. Une fois le serveur lancé, entrez son adresse dans le champ prévu à cet effet dans l'application. Pour assurer une communication correcte entre ThunderAI et le serveur local, n'oubliez pas de configurer correctement les paramètres CORS.",
"description": ""
},
"prefsInfoDesc_5": {
"message": "N'oubliez pas que vous pouvez ouvrir le menu ThunderAI en utilisant le raccourci clavier CTRL+ALT+A.",
"description": ""
},
"prefsInfoDesc_6": {
"message": "Vous pouvez changer le raccourci en cliquant sur l'icône de l'engrenage en haut à droite de cette page et en choisissant \"Gérer le raccourci de l'extension\".",
"description": ""
},
"prefsDonation_1": {
@ -451,7 +455,11 @@
"message": "API Ollama (LLM local)",
"description": ""
},
"prefs_Ollama_Host": {
"prefs_Connection_type_OpenAI_Comp_API": {
"message": "API compatible OpenAI",
"description": ""
},
"prefs_API_Host": {
"message": "Adresse de l'hôte",
"description": ""
},
@ -467,7 +475,7 @@
"message": "Erreur lors de la tentative de récupération des modèles Ollama",
"description": ""
},
"Ollama_Models_Error_NoModels": {
"API_Models_Error_NoModels": {
"message": "Aucun modèle trouvé",
"description": ""
},
@ -499,8 +507,76 @@
"message": "En attente de la réponse du serveur",
"description": ""
},
"prefs_Ollama_Host_Info": {
"prefs_API_Host_Info": {
"message": "Quelque chose comme",
"description": ""
},
"OpenAIComp_Models": {
"message": "Modèles d'API compatibles OpenAI",
"description": ""
},
"OpenAIComp_Models_Fetch": {
"message": "Mettre à jour la liste des modèles d'API compatibles OpenAI",
"description": ""
},
"OpenAIComp_Models_Error_fetching": {
"message": "Erreur lors de la tentative de récupération des modèles d'API compatibles OpenAI",
"description": ""
},
"OpenAIComp_empty_host": {
"message": "Vous n'avez pas ajouté d'adresse hôte pour l'API compatible OpenAI. Veuillez en insérer une dans la page des options.",
"description": ""
},
"OpenAIComp_empty_model": {
"message": "Vous n'avez pas choisi de modèle pour l'API compatible OpenAI. Veuillez en choisir un dans la page des options.",
"description": ""
},
"OpenAIComp_api_connecting": {
"message": "Tentative de connexion au serveur local de l'API compatible OpenAI en utilisant l'hôte",
"description": ""
},
"OpenAIComp_api_request_failed": {
"message": "La requête de l'API compatible OpenAI a échoué",
"description": ""
},
"prefs_OpenAIComp_ChatName": {
"message": "Nom du chat",
"description": ""
},
"prefs_OpenAIComp_ChatName_Info": {
"message": "C'est le nom qui sera utilisé dans le chat de l'IA.",
"description": ""
},
"StorageSpace": {
"message": "Espace de stockage total occupé",
"description": ""
},
"SearchPrompt": {
"message": "Rechercher des invites",
"description": ""
},
"prefs_OptionText_dynamic_menu_force_enter": {
"message": "Menu : Envoi immédiat de l'invite",
"description": ""
},
"prefs_OptionText_dynamic_menu_force_enter_info": {
"message": "Si cette option est cochée, le raccourci clavier CTRL+ALT+A enverra automatiquement l'invite sélectionnée dans le menu. Sinon, le nom de l'invite sera affiché à l'utilisateur, nécessitant une nouvelle pression sur la touche Entrée pour l'envoyer.",
"description": ""
},
"prefs_OptionText_dynamic_menu_order_alphabet": {
"message": "Menu : Trier par ordre alphabétique",
"description": ""
},
"prefs_OptionText_dynamic_menu_order_alphabet_info": {
"message": "Si cette option est cochée, les invites dans le menu seront triées par ordre alphabétique.",
"description": ""
},
"prefs_OptionText_chatgpt_win_dims_info": {
"message": "Réglez sur 0 si vous ne souhaitez pas spécifier la taille de la fenêtre.",
"description": ""
},
"Optional": {
"message": "Optionnel",
"description": ""
}
}

View file

@ -303,14 +303,6 @@
"message": "Modalità di risposta",
"description": ""
},
"prefs_OptionText_chatpgt_use_gpt4": {
"message": "Usa il modello ChatGPT-4",
"description": ""
},
"prefs_OptionText_chatpgt_use_gpt4_info": {
"message": "Dopo aver modificato questa opzione, devi scegliere il modello corretto la prima volta che lo usi.",
"description": ""
},
"prefs_OptionText_btnManagePrompts": {
"message": "Gestisci i tuoi prompts",
"description": ""
@ -320,7 +312,7 @@
"description": ""
},
"prefsInfoDesc_1": {
"message": "Se all'apertura della finestra dell'interfaccia web di ChatGPT, viene mostrata una pagina bianca o vuota, significa che c'è un problema di caricamento della pagina di ChatGPT, non relativo all'utilizzo di questo componente aggiuntivo.",
"message": "È possibile che l'interfaccia web di ChatGPT cambi in modo tale da compromettere il funzionamento dell'addon. Controlla la pagina \"Stato del servizio\" collegata in fondo a questa pagina. Ricorda inoltre che la prima volta che utilizzi ThunderAI, devi effettuare l'accesso a ChatGPT.",
"description": ""
},
"prefsInfoDesc_2": {
@ -328,7 +320,19 @@
"description": ""
},
"prefsInfoDesc_3": {
"message": "Per utilizzare l'integrazione con Ollama, è necessario configurare un server Ollama locale. Una volta che il server è attivo, inserisci il suo indirizzo nel campo designato all'interno dell'applicazione. Per garantire una corretta comunicazione tra l'app e il server Ollama, ricorda di impostare OLLAMA_ORIGINS=moz-extension://*.",
"message": "Per utilizzare l'integrazione con Ollama, è necessario configurare un server Ollama locale. Una volta che il server è attivo, inserisci il suo indirizzo nel campo designato all'interno dell'applicazione. Per garantire una corretta comunicazione tra ThunderAI e il server Ollama, ricorda di impostare OLLAMA_ORIGINS=moz-extension://*.",
"description": ""
},
"prefsInfoDesc_4": {
"message": "Per utilizzare questa integrazione, è necessario configurare un server locale compatibile con l'API di OpenAI, come LM Studio. Una volta avviato il server, inserisci il suo indirizzo nella pagina delle opzioni. Per garantire una corretta comunicazione tra ThunderAI e il server locale, ricorda di configurare correttamente le impostazioni CORS.",
"description": ""
},
"prefsInfoDesc_5": {
"message": "Ricorda che puoi aprire il menu di ThunderAI utilizzando la combinazione di tasti CTRL+ALT+A.",
"description": ""
},
"prefsInfoDesc_6": {
"message": "Puoi modificare la scorciatoia cliccando sull'icona a forma di ingranaggio in alto a destra di questa pagina e scegliendo \"Gestisci scorciatoie da tastiera\".",
"description": ""
},
"prefsDonation_1": {
@ -451,7 +455,11 @@
"message": "API Ollama (LLM Locale)",
"description": ""
},
"prefs_Ollama_Host": {
"prefs_Connection_type_OpenAI_Comp_API": {
"message": "API compatibile con OpenAI",
"description": ""
},
"prefs_API_Host": {
"message": "Indirizzo dell'host",
"description": ""
},
@ -467,7 +475,7 @@
"message": "Errore nel tentativo di recuperare i modelli Ollama",
"description": ""
},
"Ollama_Models_Error_NoModels": {
"API_Models_Error_NoModels": {
"message": "Nessun modello trovato",
"description": ""
},
@ -499,8 +507,76 @@
"message": "In attesa della risposta del server",
"description": ""
},
"prefs_Ollama_Host_Info": {
"prefs_API_Host_Info": {
"message": "Qualcosa come",
"description": ""
},
"OpenAIComp_Models": {
"message": "Modelli dell'API compatibile con OpenAI",
"description": ""
},
"OpenAIComp_Models_Fetch": {
"message": "Aggiorna la lista dei modelli dell'API compatibile con OpenAI",
"description": ""
},
"OpenAIComp_Models_Error_fetching": {
"message": "Errore durante il tentativo di recuperare i modelli dell'API compatibile con OpenAI",
"description": ""
},
"OpenAIComp_empty_host": {
"message": "Non hai aggiunto un indirizzo del server per l'API compatibile con OpenAI. Inseriscine uno nella pagina delle opzioni.",
"description": ""
},
"OpenAIComp_empty_model": {
"message": "Non hai scelto un modello per l'API compatibile con OpenAI. Scegline uno nella pagina delle opzioni.",
"description": ""
},
"OpenAIComp_api_connecting": {
"message": "Tentativo di connessione al server locale dell'API compatibile con OpenAI utilizzando l'host",
"description": ""
},
"OpenAIComp_api_request_failed": {
"message": "Richiesta all'API compatibile con OpenAI fallita",
"description": ""
},
"prefs_OpenAIComp_ChatName": {
"message": "Nome della chat",
"description": ""
},
"prefs_OpenAIComp_ChatName_Info": {
"message": "Questo è il nome che verrà utilizzato nella chat con l'IA.",
"description": ""
},
"StorageSpace": {
"message": "Spazio di archiviazione totale occupato",
"description": ""
},
"SearchPrompt": {
"message": "Cerca prompt",
"description": ""
},
"prefs_OptionText_dynamic_menu_force_enter": {
"message": "Menu: invio immediato del prompt",
"description": ""
},
"prefs_OptionText_dynamic_menu_force_enter_info": {
"message": "Se selezionato, l'utilizzo della combinazione di tasti CTRL+ALT+A invierà automaticamente il prompt evidenziato dal menu. In caso contrario, verrà visualizzato il nome del prompt, richiedendo un'ulteriore pressione del tasto Invio per inviarlo.",
"description": ""
},
"prefs_OptionText_dynamic_menu_order_alphabet": {
"message": "Menu: ordina alfabeticamente",
"description": ""
},
"prefs_OptionText_dynamic_menu_order_alphabet_info": {
"message": "Se selezionato, i prompt nel menu verranno ordinati in ordine alfabetico.",
"description": ""
},
"prefs_OptionText_chatgpt_win_dims_info": {
"message": "Imposta a 0 se non desideri specificare la dimensione della finestra.",
"description": ""
},
"Optional": {
"message": "Opzionale",
"description": ""
}
}

View file

@ -0,0 +1,506 @@
{
"extensionDescription": {
"message": "Use o ChatGPT ou o Ollama para aprimorar seus e-mails!",
"description": "Description of the extension."
},
"menu_title": {
"message": "IA",
"description": ""
},
"prompt_lang": {
"message": "Responda em",
"description": ""
},
"prompt_reply": {
"message": "Responda a isso",
"description": ""
},
"prompt_rewrite_polite": {
"message": "Reescreva de forma educada",
"description": ""
},
"prompt_rewrite_formal": {
"message": "Reescreva de forma formal",
"description": ""
},
"prompt_classify": {
"message": "Classificar",
"description": ""
},
"prompt_summarize_this": {
"message": "Resuma isso",
"description": ""
},
"prompt_translate_this": {
"message": "Traduza isso",
"description": ""
},
"prompt_this": {
"message": "Prompt isso",
"description": ""
},
"prompt_selection_needed": {
"message": "Para prosseguir, você precisa selecionar algum texto!",
"description": ""
},
"customPrompts_managePrompts": {
"message": "Gerenciar Prompts",
"description": ""
},
"customPrompts_managePrompts_help": {
"message": "Mais informações",
"description": ""
},
"customPrompts_managePrompts_info_default": {
"message": "Os prompts padrão não são editáveis. Você pode desativá-los, copiar o texto do prompt e colá-lo em um novo para criar uma versão modificada.",
"description": ""
},
"customPrompts_managePrompts_info_default_2": {
"message": "Você pode importar e exportar os prompts. Prompts existentes com o mesmo ID serão sobrescritos. Prompts com novos IDs serão adicionados.",
"description": ""
},
"customPrompts_managePrompts_info_default_3": {
"message": "Se tudo estiver correto, após a importação, clique no botão 'Salvar Tudo'.",
"description": ""
},
"customPrompts_start_saving": {
"message": "Salvando prompts...",
"description": ""
},
"customPrompts_reindexing_list": {
"message": "Reindexando lista...",
"description": ""
},
"customPrompts_filtering_prompts": {
"message": "Filtrando prompts...",
"description": ""
},
"customPrompts_saving_default_prompts": {
"message": "Salvando prompts padrão...",
"description": ""
},
"customPrompts_saving_custom_prompts": {
"message": "Salvando prompts personalizados...",
"description": ""
},
"customPrompts_reloading_menus": {
"message": "Recarregando menus...",
"description": ""
},
"customPrompts_saved": {
"message": "Prompts salvos!",
"description": ""
},
"customPrompts_form_label_ID": {
"message": "ID",
"description": ""
},
"customPrompts_form_label_ID_rules": {
"message": "Deve ser único, em letras minúsculas e sem espaços.",
"description": ""
},
"customPrompts_form_label_Name": {
"message": "Nome",
"description": ""
},
"customPrompts_form_label_Text": {
"message": "Texto do Prompt",
"description": ""
},
"customPrompts_form_label_Action": {
"message": "Ação",
"description": ""
},
"customPrompts_form_label_need_selected": {
"message": "Seleção de texto necessária",
"description": ""
},
"customPrompts_form_label_need_signature": {
"message": "Sempre adicione a assinatura",
"description": ""
},
"customPrompts_form_label_need_custom_text": {
"message": "Peça por texto adicional",
"description": ""
},
"customPrompts_form_label_enabled": {
"message": "Ativado",
"description": ""
},
"customPrompts_form_required_fields": {
"message": "Campos obrigatórios\n",
"description": ""
},
"customPrompts_btnEdit": {
"message": "Editar",
"description": ""
},
"customPrompts_btnCancel": {
"message": "Cancelar",
"description": ""
},
"customPrompts_btnOK": {
"message": "OK",
"description": ""
},
"customPrompts_btnDelete": {
"message": "Deletar",
"description": ""
},
"customPrompts_btnDelete_confirmText": {
"message": "Você tem certeza de que deseja excluir este item?",
"description": ""
},
"customPrompts_unsaved_changes": {
"message": "Existem alterações não salvas!",
"description": ""
},
"customPrompts_btnSaveAll": {
"message": "Salvar Tudo",
"description": ""
},
"customPrompts_save_button": {
"message": "Salvar",
"description": ""
},
"customPrompts_btnNew": {
"message": "Adicionar Novo",
"description": ""
},
"customPrompts_btnAddNewCommit": {
"message": "Adicionar o Prompt",
"description": ""
},
"customPrompts_add_to_menu": {
"message": "Adicionar ao menu",
"description": ""
},
"customPrompts_add_to_menu_always": {
"message": "Sempre",
"description": ""
},
"customPrompts_add_to_menu_reading": {
"message": "Lendo um e-mail",
"description": ""
},
"customPrompts_add_to_menu_composing": {
"message": "Redigindo um e-mail",
"description": ""
},
"customPrompts_close_button": {
"message": "Botão fechar",
"description": ""
},
"customPrompts_do_reply": {
"message": "Fazer uma resposta",
"description": ""
},
"customPrompts_substitute_text": {
"message": "Substituir texto",
"description": ""
},
"chatgpt_win_working": {
"message": "Trabalho em andamento...",
"description": ""
},
"chatgpt_win_job_completed": {
"message": "Concluído!",
"description": ""
},
"chatgpt_win_job_completed_select": {
"message": "Selecione o texto que você deseja usar e clique no botão.",
"description": ""
},
"chatgpt_win_get_answer": {
"message": "Usar a resposta selecionada",
"description": ""
},
"chatgpt_win_close": {
"message": "Fechar",
"description": ""
},
"chatgpt_textarea_not_found_error": {
"message": "Parece que a página do ChatGPT está demorando muito para carregar. Se terminar de carregar, clique no botão à direita. Se o problema persistir, por favor, verifique o status do serviço.",
"description": ""
},
"chatgpt_btn_retry": {
"message": "Tentar novamente",
"description": ""
},
"chatgpt_sendbutton_not_found_error": {
"message": "Clique no botão enviar para enviar o prompt.",
"description": ""
},
"chatgpt_user_not_logged_in": {
"message": "Você não está logado no ChatGPT. Por favor, faça login com suas credenciais, feche a janela do ChatGPT e, em seguida, repita a ação que você tentou. Você permanecerá logado depois.",
"description": ""
},
"chatgpt_win_model_warning": {
"message": "Você definiu a opção para usar o ChatGPT-4, mas parece que não está carregando corretamente. Você escolheu o modelo 4 ou 4o na parte superior?",
"description": ""
},
"chatgpt_win_custom_text": {
"message": "Insira aqui o texto adicional para o prompt.",
"description": ""
},
"chatgpt_win_send": {
"message": "Enviar",
"description": ""
},
"chatgpt_use_gpt35": {
"message": "Usar GPT-3.5",
"description": ""
},
"chatgpt_force_completion": {
"message": "forçar conclusão",
"description": ""
},
"chatgpt_force_completion_title": {
"message": "Clique aqui para exibir o botão 'Usar última resposta' se o ChatGPT tiver terminado seu trabalho, mas o botão não apareceu.",
"description": ""
},
"msg_prompt_too_long": {
"message": "O texto que você forneceu é muito longo. Você precisa reduzi-lo para usar o ChatGPT.",
"description": ""
},
"prefs_OptionText_release_notes": {
"message": "Notas de versão",
"description": ""
},
"prefs_status_page": {
"message": "status do serviço",
"description": ""
},
"prefs_OptionText_chatgpt_win_text": {
"message": "Dimensões da janela do ChatGPT",
"description": ""
},
"prefs_OptionText_chatgpt_win_height": {
"message": "Altura",
"description": ""
},
"prefs_OptionText_chatgpt_win_width": {
"message": "Largura",
"description": ""
},
"prefs_OptionText_default_sign_name": {
"message": "Nome da assinatura padrão",
"description": ""
},
"prefs_OptionText_default_chatgpt_lang": {
"message": "Idioma padrão para as respostas",
"description": ""
},
"prefs_OptionText_reply_all": {
"message": "Responder a Todos",
"description": ""
},
"prefs_OptionText_reply_sender": {
"message": "Responder ao Remetente",
"description": ""
},
"prefs_OptionText_reply_type": {
"message": "Tipo de resposta",
"description": ""
},
"prefs_OptionText_chatpgt_use_gpt4": {
"message": "Usar ChatGPT-4",
"description": ""
},
"prefs_OptionText_chatpgt_use_gpt4_info": {
"message": "Após alterar esta opção, você deve escolher o modelo correto na primeira vez que usá-lo.",
"description": ""
},
"prefs_OptionText_btnManagePrompts": {
"message": "Gerenciar seus prompts",
"description": ""
},
"prefsInfoTitle": {
"message": "Informações importantes",
"description": ""
},
"prefsInfoDesc_1": {
"message": "Se uma página em branco ou branca aparecer ao abrir a janela da interface web do ChatGPT, isso significa que há um problema ao carregar a página do ChatGPT, não relacionado ao uso deste complemento.",
"description": ""
},
"prefsInfoDesc_2": {
"message": "Para usar a API do ChatGPT, você precisa de uma chave de API do OpenAI ChatGPT e deve escolher um modelo.",
"description": ""
},
"prefsInfoDesc_3": {
"message": "Para usar a integração com o Ollama, você precisa configurar um servidor local do Ollama. Uma vez que o servidor esteja em funcionamento, insira seu endereço no campo designado dentro do aplicativo. Para garantir a comunicação adequada entre o aplicativo e o servidor do Ollama, lembre-se de definir OLLAMA_ORIGINS=moz-extension://*.",
"description": ""
},
"prefsDonation_1": {
"message": "Você gosta deste complemento?",
"description": ""
},
"prefsDonation_2": {
"message": "Considere fazer uma doação!",
"description": ""
},
"backToOptionsText": {
"message": "Opções",
"description": ""
},
"TranslateText": {
"message": "Você quer ajudar a traduzir este complemento?",
"description": ""
},
"TranslateLink": {
"message": "Descubra como!",
"description": ""
},
"customPrompts_ExportAll": {
"message": "Exportar todos os prompts",
"description": ""
},
"customPrompts_Import": {
"message": "Importar novos prompts",
"description": ""
},
"importPrompts_confirmText": {
"message": "Você está prestes a importar novos prompts.",
"description": ""
},
"customPrompts_start_import": {
"message": "Iniciando a importação de prompts personalizados...",
"description": ""
},
"customPrompts_import_completed": {
"message": "Importação de prompts personalizados concluída! Você precisa clicar no botão 'Salvar tudo' para salvar suas alterações.",
"description": ""
},
"importPrompts_invalidFile": {
"message": "O arquivo que você está tentando importar não é um arquivo de prompts personalizados válido.",
"description": ""
},
"importPrompts_invalidPrompts": {
"message": "O arquivo que você está tentando importar não contém nenhum prompt válido.",
"description": ""
},
"currently_used_prompt": {
"message": "Nome do prompt atualmente utilizado",
"description": ""
},
"customprompts_form_label_define_response_lang": {
"message": "Defina o idioma da resposta no prompt",
"description": ""
},
"prefs_Connection_type": {
"message": "Tipo de Conexão",
"description": ""
},
"prefs_Connection_type_ChatGTP_Web": {
"message": "Interface Web do ChatGPT",
"description": ""
},
"prefs_Connection_type_ChatGTP_API": {
"message": "API OpenAI do ChatGPT",
"description": ""
},
"prefs_ChatGPT_API_Key": {
"message": "Chave da API do ChatGPT",
"description": ""
},
"ChatGPT_Models": {
"message": "Modelos do ChatGPT",
"description": ""
},
"ChatGPT_Models_Fetch": {
"message": "Atualizar lista de modelos do ChatGPT",
"description": ""
},
"ChatGPT_Models_Error_fetching": {
"message": "Erro ao tentar buscar os modelos do ChatGPT",
"description": ""
},
"Loading": {
"message": "Carregando...",
"description": ""
},
"error": {
"message": "Erro ThunderAI!",
"description": ""
},
"chatgpt_empty_apikey": {
"message": "Você não adicionou uma chave de API para a API do ChatGPT. Por favor, insira uma na página de opções.",
"description": ""
},
"chatgpt_empty_model": {
"message": "Você não escolheu um modelo para a API do ChatGPT. Por favor, escolha um na página de opções.",
"description": ""
},
"chagtp_api_send_button": {
"message": "Usando modelo",
"description": ""
},
"chagpt_api_connecting": {
"message": "Tentando conectar ao OpenAI ChatGPT usando a chave de API fornecida",
"description": ""
},
"Debug": {
"message": "Depurar",
"description": ""
},
"prefs_OptionText_do_debug_info": {
"message": "Ativar o sistema de depuração",
"description": ""
},
"prefs_Connection_type_Ollama_API": {
"message": "API Ollama (LLM Local)",
"description": ""
},
"prefs_Ollama_Host": {
"message": "Endereço do Host",
"description": ""
},
"Ollama_Models": {
"message": "Modelos do Ollama",
"description": ""
},
"Ollama_Models_Fetch": {
"message": "Atualizar lista de modelos do Ollama",
"description": ""
},
"Ollama_Models_Error_fetching": {
"message": "Erro ao tentar buscar os modelos do Ollama",
"description": ""
},
"Ollama_Models_Error_NoModels": {
"message": "Nenhum modelo encontrado",
"description": ""
},
"ollama_empty_host": {
"message": "Você não adicionou um endereço de host para a API do Ollama. Por favor, insira um na página de opções.",
"description": ""
},
"ollama_empty_model": {
"message": "Você não escolheu um modelo para a API do Ollama. Por favor, escolha um na página de opções.",
"description": ""
},
"ollama_api_connecting": {
"message": "Tentando conectar ao servidor local do Ollama usando o host.",
"description": ""
},
"andModel": {
"message": "e modelo",
"description": ""
},
"error_connection_interrupted": {
"message": "A conexão com o servidor foi interrompida inesperadamente.",
"description": ""
},
"ollama_api_request_failed": {
"message": "A solicitação da API do Ollama falhou",
"description": ""
},
"WaitingServerReponse": {
"message": "Aguardando a resposta do servidor",
"description": ""
},
"prefs_Ollama_Host_Info": {
"message": "Algo como",
"description": ""
}
}

View file

@ -25,7 +25,16 @@ const urlParams = new URLSearchParams(window.location.search);
const llm = urlParams.get('llm');
const call_id = urlParams.get('call_id');
//console.log(">>>>>>>>>>> llm: " + llm);
// Data received from the user
let promptData = null;
const messageInput = document.querySelector('message-input');
const messagesArea = document.querySelector('messages-area');
//console.log(">>>>>>>>>> controller.js DOMContentLoaded");
// console.log(">>>>>>>>>>> llm: " + llm);
// console.log(">>>>>>>>>>> call_id: " + call_id);
// The controller wires up all the components and workers together,
// managing the dependencies. A kind of "DI" class.
@ -33,65 +42,55 @@ let worker = null;
switch (llm) {
case "chatgpt_api":
browser.runtime.sendMessage({command: "openai_api_ready_" + call_id, window_id: (await browser.windows.getCurrent()).id});
worker = new Worker('model-worker-openai.js', { type: 'module' });
break;
case "ollama_api": {
browser.runtime.sendMessage({command: "ollama_api_ready_" + call_id, window_id: (await browser.windows.getCurrent()).id});
case "ollama_api":
worker = new Worker('model-worker-ollama.js', { type: 'module' });
break;
}
case "openai_comp_api":
worker = new Worker('model-worker-openai_comp.js', { type: 'module' });
break;
}
const messagesArea = document.querySelector('messages-area');
messagesArea.init(worker);
// Initialize the messageInput component and pass the worker to it
const messageInput = document.querySelector('message-input');
messageInput.init(worker);
messageInput.setMessagesArea(messagesArea);
// Data received from the user
let promptData = null;
// ============================== TESTING
// let browser = {
// i18n: {
// getMessage: async function(key) {
// return key;
// }
// },
// storage: {
// sync: {
// get: async function(key) {
// return 'apitest';
// }
// }
// }
// }
// ============================== TESTING - END
switch (llm) {
case "chatgpt_api":
let prefs_api = await browser.storage.sync.get({chatgpt_api_key: '', chatgpt_model: ''});
//console.log(">>>>>>>>>>> chatgpt_api_key: " + prefs_api_key.chatgpt_api_key);
let prefs_api = await browser.storage.sync.get({chatgpt_api_key: '', chatgpt_model: '', do_debug: false});
messageInput.setModel(prefs_api.chatgpt_model);
messagesArea.setLLMName("ChatGPT");
worker.postMessage({ type: 'init', chatgpt_api_key: prefs_api.chatgpt_api_key, chatgpt_model: prefs_api.chatgpt_model});
worker.postMessage({ type: 'init', chatgpt_api_key: prefs_api.chatgpt_api_key, chatgpt_model: prefs_api.chatgpt_model, do_debug: prefs_api.do_debug});
messagesArea.appendUserMessage(browser.i18n.getMessage("chagpt_api_connecting") + " " +browser.i18n.getMessage("AndModel") + " \"" + prefs_api.chatgpt_model + "\"...", "info");
browser.runtime.sendMessage({command: "openai_api_ready_" + call_id, window_id: (await browser.windows.getCurrent()).id});
break;
case "ollama_api": {
let prefs_api = await browser.storage.sync.get({ollama_host: '', ollama_model: ''});
let prefs_api = await browser.storage.sync.get({ollama_host: '', ollama_model: '', do_debug: false});
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, i18nStrings: i18nStrings});
worker.postMessage({ type: 'init', ollama_host: prefs_api.ollama_host, ollama_model: prefs_api.ollama_model, do_debug: prefs_api.do_debug, i18nStrings: i18nStrings});
browser.runtime.sendMessage({command: "ollama_api_ready_" + call_id, window_id: (await browser.windows.getCurrent()).id});
messagesArea.appendUserMessage(browser.i18n.getMessage("ollama_api_connecting") + " \"" + prefs_api.ollama_host + "\" " +browser.i18n.getMessage("AndModel") + " \"" + prefs_api.ollama_model + "\"...", "info");
break;
}
case "openai_comp_api": {
let prefs_api = await browser.storage.sync.get({openai_comp_host: '', openai_comp_model: '', openai_comp_api_key: '', openai_comp_chat_name: '', do_debug: false});
let i18nStrings = {};
i18nStrings["OpenAIComp_api_request_failed"] = browser.i18n.getMessage('OpenAIComp_api_request_failed');
i18nStrings["error_connection_interrupted"] = browser.i18n.getMessage('error_connection_interrupted');
messageInput.setModel(prefs_api.openai_comp_model);
messagesArea.setLLMName(prefs_api.openai_comp_chat_name);
worker.postMessage({ type: 'init', openai_comp_host: prefs_api.openai_comp_host, openai_comp_model: prefs_api.openai_comp_model, openai_comp_api_key: prefs_api.openai_comp_api_key, do_debug: prefs_api.do_debug, i18nStrings: i18nStrings});
messagesArea.appendUserMessage(browser.i18n.getMessage("OpenAIComp_api_connecting") + " \"" + prefs_api.openai_comp_host + "\" " +browser.i18n.getMessage("AndModel") + " \"" + prefs_api.openai_comp_model + "\"...", "info");
browser.runtime.sendMessage({command: "openai_comp_api_ready_" + call_id, window_id: (await browser.windows.getCurrent()).id});
break;
}
}
@ -121,7 +120,6 @@ worker.onmessage = function(event) {
// handling commands from the backgound page
browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
//console.log(">>>>>>>>>>>>> controller.js onMessage: " + JSON.stringify(message));
switch (message.command) {
case "api_send":
//send the received prompt to the llm api

View file

@ -21,12 +21,15 @@
*/
import { Ollama } from '../js/api/ollama.js';
import { taLogger } from '../js/mzta-logger.js';
let ollama_host = null;
let ollama_model = '';
let ollama = null;
let stopStreaming = false;
let i18nStrings = null;
let do_debug = false;
let taLog = null;
let conversationHistory = [];
let assistantResponseAccumulator = '';
@ -38,7 +41,9 @@ 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);
do_debug = event.data.do_debug;
i18nStrings = event.data.i18nStrings;
taLog = new taLogger('model-worker-ollama', do_debug);
break; // init
case 'chatMessage':
conversationHistory.push({ role: 'user', content: event.data.message });
@ -55,7 +60,7 @@ self.onmessage = async function(event) {
const errorJSON = await response.json();
errorDetail = JSON.stringify(errorJSON);
error_message = errorJSON.error;
//console.log(">>>>>>>>>>>>> errorJSON.error.message: " + JSON.stringify(errorJSON.error.message));
taLog.log("errorJSON.error.message: " + JSON.stringify(errorJSON.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);
@ -63,6 +68,7 @@ self.onmessage = async function(event) {
const reader = response.body.getReader();
const decoder = new TextDecoder("utf-8");
let buffer= '';
try {
while (true) {
@ -84,12 +90,23 @@ self.onmessage = async function(event) {
}
// 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
buffer += chunk;
taLog.log("buffer: " + buffer);
const lines = buffer.split("\n");
buffer = lines.pop();
let parsedLines = [];
try{
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
.map((line) => {
taLog.log("line: " + JSON.stringify(line));
return JSON.parse(line);
});
}catch(e){
taLog.error("Error parsing lines: " + e);
}
for (const parsedLine of parsedLines) {
const { message } = parsedLine;
@ -115,4 +132,4 @@ self.onmessage = async function(event) {
stopStreaming = true;
break; //stop
}
};
};

View file

@ -21,28 +21,14 @@
*/
import { OpenAI } from '../js/api/openai.js';
//========================== for testing
// const MOCK_TOKENS = ['Good', ' morning', ' Mr', ' Plop', 'py', ',', 'and', ' I', ' said', '\n', '"', 'Good', ' morn', 'ing', ' Mrs',' Plop', 'py', ,'"', '\n', 'Oh', ' how', ' the', ' win', 'ter', ' even', 'ings', ' must', ' just', ' fly'];
//
// function mockDelay(ms) {
// return new Promise(resolve => setTimeout(resolve, ms));
// }
// async function processMockTokens() {
// for (const token of MOCK_TOKENS) {
// await mockDelay(Math.random() * 50 + 50); // Random delay between 100ms and 150ms
// postMessage({ type: 'newToken', payload: { token } });
// }
// postMessage({ type: 'tokensDone' });
// }
//========================== for testing - END
import { taLogger } from '../js/mzta-logger.js';
let chatgpt_api_key = null;
let chatgpt_model = '';
let openai = null;
let stopStreaming = false;
let do_debug = false;
let taLog = null;
let conversationHistory = [];
let assistantResponseAccumulator = '';
@ -51,41 +37,12 @@ self.onmessage = async function(event) {
if (event.data.type === 'init') {
chatgpt_api_key = event.data.chatgpt_api_key;
chatgpt_model = event.data.chatgpt_model;
//console.log(">>>>>>>>>>> chatgpt_api_key: " + chatgpt_api_key);
openai = new OpenAI(chatgpt_api_key, chatgpt_model, true);
do_debug = event.data.do_debug;
taLog = new taLogger('model-worker-openai', do_debug);
} else if (event.data.type === 'chatMessage') {
conversationHistory.push({ role: 'user', content: event.data.message });
// ============================== TESTING
// // Simulate sending the message to an HTTP endpoint
// await mockDelay(1000); // Wait for 1 second
// // Notify that the chat message was sent
// postMessage({ type: 'messageSent' });
// // Start processing tokens
// await processMockTokens();
// return;
// ============================== TESTING - END
// https://platform.openai.com/docs/models/gpt-4-and-gpt-4-turbo
// 4096 output tokens
// 128,000 input tokens
// const response = await fetch(API_URL, {
// method: "POST",
// headers: {
// "Content-Type": "application/json",
// "Authorization": `Bearer ${openaiApiKey}`,
// },
// body: JSON.stringify({
// model: "gpt-4-1106-preview",
// messages: conversationHistory,
// stream: true,
// }),
// });
const response = await openai.fetchResponse(conversationHistory); //4096);
postMessage({ type: 'messageSent' });
@ -98,7 +55,7 @@ self.onmessage = async function(event) {
const errorJSON = await response.json();
errorDetail = JSON.stringify(errorJSON);
error_message = errorJSON.error.message;
//console.log(">>>>>>>>>>>>> errorJSON.error.message: " + JSON.stringify(errorJSON.error.message));
taLog.error("errorJSON.error.message: " + JSON.stringify(errorJSON.error.message));
}
postMessage({ type: 'error', payload: error_message });
throw new Error("[ThunderAI] OpenAI API request failed: " + response.status + " " + response.statusText + ", Detail: " + errorDetail);
@ -106,6 +63,7 @@ self.onmessage = async function(event) {
const reader = response.body.getReader();
const decoder = new TextDecoder("utf-8");
let buffer = '';
while (true) {
if (stopStreaming) {
@ -125,11 +83,23 @@ self.onmessage = async function(event) {
}
// lots of low-level OpenAI response parsing stuff
const chunk = decoder.decode(value);
const lines = chunk.split("\n");
const parsedLines = lines
.map((line) => line.replace(/^data: /, "").trim()) // Remove the "data: " prefix
.filter((line) => line !== "" && line !== "[DONE]") // Remove empty lines and "[DONE]"
.map((line) => JSON.parse(line)); // Parse the JSON string
buffer += chunk;
taLog.log("buffer " + buffer);
const lines = buffer.split("\n");
buffer = lines.pop();
let parsedLines = [];
try{
parsedLines = lines
.map((line) => line.replace(/^data: /, "").trim()) // Remove the "data: " prefix
.filter((line) => line !== "" && line !== "[DONE]") // Remove empty lines and "[DONE]"
// .map((line) => JSON.parse(line)); // Parse the JSON string
.map((line) => {
taLog.log("line: " + JSON.stringify(line));
return JSON.parse(line);
});
}catch(e){
taLog.error("Error parsing lines: " + e);
}
for (const parsedLine of parsedLines) {
const { choices } = parsedLine;

View file

@ -0,0 +1,122 @@
/*
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
* Copyright (C) 2024 Mic (m@micz.it)
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
* This file contains a modified version of the code from the project at https://github.com/boxabirds/chatgpt-frontend-nobuild
* The original code has been released under the Apache License, Version 2.0.
*/
import { OpenAIComp } from '../js/api/openai_comp.js';
import { taLogger } from '../js/mzta-logger.js';
let openai_comp_host = null;
let openai_comp_model = '';
let openai_comp_api_key = '';
let openai_comp = null;
let stopStreaming = false;
let i18nStrings = null;
let do_debug = false;
let taLog = null;
let conversationHistory = [];
let assistantResponseAccumulator = '';
self.onmessage = async function(event) {
if (event.data.type === 'init') {
openai_comp_host = event.data.openai_comp_host;
openai_comp_model = event.data.openai_comp_model;
openai_comp_api_key = event.data.openai_comp_api_key;
openai_comp = new OpenAIComp(openai_comp_host, openai_comp_model, openai_comp_api_key, true);
do_debug = event.data.do_debug;
i18nStrings = event.data.i18nStrings;
taLog = new taLogger('model-worker-openai_comp', do_debug);
} else if (event.data.type === 'chatMessage') {
conversationHistory.push({ role: 'user', content: event.data.message });
const response = await openai_comp.fetchResponse(conversationHistory); //4096);
postMessage({ type: 'messageSent' });
if (!response.ok) {
let error_message = '';
let errorDetail = '';
if(response.is_exception === true){
error_message = response.error;
}else{
const errorJSON = await response.json();
errorDetail = JSON.stringify(errorJSON);
error_message = errorJSON.error.message;
taLog.log("errorJSON.error.message: " + JSON.stringify(errorJSON.error.message));
}
postMessage({ type: 'error', payload: i18nStrings["OpenAIComp_api_request_failed"] + ": " + error_message });
throw new Error("[ThunderAI] OpenAI Comp API request failed: " + response.status + " " + response.statusText + ", Detail: " + errorDetail);
}
const reader = response.body.getReader();
const decoder = new TextDecoder("utf-8");
let buffer = '';
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 OpenAI response parsing stuff
const chunk = decoder.decode(value);
buffer += chunk;
taLog.log("buffer: " + buffer);
const lines = buffer.split("\n");
buffer = lines.pop();
let parsedLines = [];
try{
parsedLines = lines
.map((line) => line.replace(/^data: /, "").trim()) // Remove the "data: " prefix
.filter((line) => line !== "" && line !== "[DONE]") // Remove empty lines and "[DONE]"
// .map((line) => JSON.parse(line)); // Parse the JSON string
.map((line) => {
taLog.log("line: " + JSON.stringify(line));
return JSON.parse(line);
});
}catch(e){
taLog.error("Error parsing lines: " + e);
}
for (const parsedLine of parsedLines) {
const { choices } = parsedLine;
const { delta } = choices[0];
const { content } = delta;
// Update the UI with the new content
if (content) {
assistantResponseAccumulator += content;
postMessage({ type: 'newToken', payload: { token: content } });
}
}
}
} else if (event.data.type === 'stop') {
stopStreaming = true;
}
};

View file

@ -2016,5 +2016,4 @@ module.exports = naturalCompare;
/******/ // Load entry module and return exports
/******/ return __webpack_require__("./src/index.js");
/******/ })()
;
//# sourceMappingURL=./list.js.map
;

View file

@ -16,38 +16,6 @@
}
}
@media (prefers-color-scheme: dark) {
body {
background-color: #1C1B22;
color: rgb(251, 251, 254);
}
a:link { color: #409EFF; }
a:visited { color: #409EFF; }
a:hover { color: #66B1FF; }
a:active { color: #66B1FF; }
table {
background-color: #1C1B22;
border: 1px solid #ccc;
}
table.prompts_list th {
background-color: #4c4e58;
border: 1px solid rgb(251, 251, 254);
color: rgb(194, 194, 194);
}
#command_palette{
background-color: inherit;
}
table.prompts_list tr:nth-child(even) {
background-color: #2E2F36;
}
}
table {
border-collapse: collapse;
border-spacing: 0;
@ -180,4 +148,54 @@ table .sort.desc {
#btnExportAll{
margin-bottom: 3px;
}
.storage_space{
font-size: 0.8em;
font-weight: normal;
font-style: italic;
color: rgb(41, 41, 41);
text-align: right;
margin-right: 1px;
}
.page_title{
margin-bottom: 0px;
}
@media (prefers-color-scheme: dark) {
body {
background-color: #1C1B22;
color: rgb(251, 251, 254);
}
a:link { color: #409EFF; }
a:visited { color: #409EFF; }
a:hover { color: #66B1FF; }
a:active { color: #66B1FF; }
table {
background-color: #1C1B22;
border: 1px solid #ccc;
}
table.prompts_list th {
background-color: #4c4e58;
border: 1px solid rgb(251, 251, 254);
color: rgb(194, 194, 194);
}
#command_palette{
background-color: inherit;
}
table.prompts_list tr:nth-child(even) {
background-color: #2E2F36;
}
.storage_space{
color: rgb(182, 182, 182);
}
}

View file

@ -7,7 +7,7 @@
</head>
<body>
<div>
<h1>__MSG_customPrompts_managePrompts__</h1>
<h1 class="page_title">__MSG_customPrompts_managePrompts__</h1>
<p>__MSG_customPrompts_managePrompts_info_default__<br>
__MSG_customPrompts_managePrompts_info_default_2__<br>
__MSG_customPrompts_managePrompts_info_default_3__
@ -92,9 +92,9 @@
</tbody>
</table>
</div>
<div class="storage_space">__MSG_StorageSpace__: <span id="storage_space"></span></div>
<script src="list.js"></script>
<script src="mzta-custom-prompts.js" type="module"></script>
<script src="../js/mzta-i18n.js"></script>
</body>
</html>
</html>

View file

@ -17,7 +17,8 @@
*/
import { getPrompts, setDefaultPromptsProperties, setCustomPrompts, preparePromptsForExport, preparePromptsForImport } from "../js/mzta-prompts.js";
import { isThunderbird128OrGreater } from "../js/mzta-utils.js";
import { isThunderbird128OrGreater, getCustomPromptsUsedSpace } from "../js/mzta-utils.js";
import { taLogger } from "../js/mzta-logger.js";
var promptsList = null;
var somethingChanged = false;
@ -25,8 +26,14 @@ var positionMax_compose = 0;
var positionMax_display = 0;
var idnumMax = 0;
var msgTimeout = null;
let taLog = null;
document.addEventListener('DOMContentLoaded', async () => {
let prefs_debug = await browser.storage.sync.get({do_debug: false});
taLog = new taLogger("mzta-custom-prompts", prefs_debug.do_debug);
setStorageSpace();
let values = await getPrompts();
@ -358,6 +365,7 @@ function handleInputChange(e) {
function loadPromptsList(values){
// console.log('>>>>>>>> loadPromptsList values: ' + JSON.stringify(values));
let options = {
valueNames: [ { data: ['idnum'] }, 'is_default', 'id', 'name', 'text', 'type', 'action', 'position_compose', 'position_display', { 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: 'enabled', attr: 'checked_val'} ],
item: function(values) {
@ -413,7 +421,7 @@ function loadPromptsList(values){
<br>
<input type="checkbox" class="need_signature" disabled> __MSG_customPrompts_form_label_need_signature__
<br>
<input type="checkbox" class="need_custom_text" disabled> __MSG_customPrompts_form_label_need_custom_text__
<input type="checkbox" class="need_custom_text` + ((values.is_default == 1) ? ' input_mod':'') + `"` + ((values.is_default == 0) ? ' disabled':'') + ` > __MSG_customPrompts_form_label_need_custom_text__
<br>
<input type="checkbox" class="define_response_lang" disabled> __MSG_customPrompts_form_label_define_response_lang__
<br>
@ -582,6 +590,7 @@ async function saveAll() {
// For each item in the array, return only the '_values' part
return item.values();
});
taLog.log('newPrompts: ' + JSON.stringify(newPrompts));
// newPrompts.forEach(prompt => {
// console.log('>>>>>>>>>>>>> id: ' + JSON.stringify(prompt));
// });
@ -601,6 +610,7 @@ async function saveAll() {
clearMessage();
}, 10000)
}
setStorageSpace();
}
function setMessage(msg, color = '') {
@ -618,6 +628,11 @@ function clearMessage() {
msgDisplay.style.color = '';
}
async function setStorageSpace() {
let storage_space = await getCustomPromptsUsedSpace();
document.getElementById('storage_space').textContent = storage_space;
}
if(await isThunderbird128OrGreater()){
window.addEventListener('beforeunload', function (event) {

BIN
images/pwd-hide.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 479 B

BIN
images/pwd-show.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 B

94
js/api/openai_comp.js Normal file
View file

@ -0,0 +1,94 @@
/*
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
* Copyright (C) 2024 Mic (m@micz.it)
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// Some original methods derived from https://github.com/ali-raheem/Aify/blob/4ece286095ea7a6cf89d696902e6b81b5d1c3a4b/plugin/html/API.js
export class OpenAIComp {
host = '';
model = '';
apiKey = '';
stream = false;
constructor(host, model, apiKey = '', stream = false) {
this.host = host.trim().replace(/\/+$/, "");
this.model = model;
this.stream = stream;
this.apiKey = apiKey;
}
fetchModels = async () => {
const curr_headers = {
"Content-Type": "application/json",
};
if(this.apiKey !== '') curr_headers["Authorization"] = "Bearer "+ this.apiKey;
const response = await fetch(this.host + "/v1/models", {
method: "GET",
headers: curr_headers,
});
if (!response.ok) {
const errorDetail = await response.text();
let err_msg = "[ThunderAI] OpenAI API Comp request failed: " + response.status + " " + response.statusText + ", Detail: " + errorDetail;
console.error(err_msg);
let output = {};
output.ok = false;
output.error = errorDetail;
return output;
}
let output = {};
output.ok = true;
let output_response = await response.json();
output.response = output_response.data;
return output;
}
fetchResponse = async (messages, maxTokens = 0) => {
const curr_headers = {
"Content-Type": "application/json",
};
if(this.apiKey !== '') curr_headers["Authorization"] = "Bearer "+ this.apiKey;
try {
const response = await fetch(this.host + "/v1/chat/completions", {
method: "POST",
headers: curr_headers,
body: JSON.stringify({
model: this.model,
messages: messages,
stream: this.stream,
...(maxTokens > 0 ? { 'max_tokens': parseInt(maxTokens) } : {})
}),
});
return response;
}catch (error) {
console.error("[ThunderAI] OpenAI API Comp request failed: " + error);
let output = {};
output.is_exception = true;
output.ok = false;
output.error = "OpenAI API Comp request failed: " + error;
return output;
}
}
}

View file

@ -37,9 +37,9 @@ async function chatgpt_sendMsg(msg, method ='') { // return -1 send button
textArea = document.querySelector('form textarea');
old_textArea = true;
}
let sendButton = document.querySelector('path[d*="M15.1918 8.90615C15.6381"]')?.parentNode.parentNode // from sept-2024;
|| document.querySelector('path[d*="M15.192 8.906a1.143"]')?.parentNode.parentNode // post-GPT-4o;
|| document.querySelector('[data-testid="send-button"]'); // pre-GPT-4o
let sendButton = document.querySelector('[data-testid="send-button"]') // pre-GPT-4o
|| document.querySelector('path[d*="M15.1918 8.90615C15.6381"]')?.parentNode.parentNode // from sept-2024;
|| document.querySelector('path[d*="M15.192 8.906a1.143"]')?.parentNode.parentNode; // post-GPT-4o;
//check if the textarea has been found
if(!textArea) {
console.error("[ThunderAI] Textarea not found!");
@ -89,7 +89,7 @@ function chatgpt_getRegenerateButton() {
function chatpgt_scrollToBottom () {
try {
//document.querySelector('button[class*="cursor"][class*="bottom"]').click();
document.querySelector('path[d^="M12 21C11.7348"]').parentNode.parentNode.click();
document.querySelector('path[d^="M12 21C11.7348"]')?.parentNode?.parentNode?.click();
}
catch (err) { console.error('[ThunderAI] ', err); }
}
@ -105,7 +105,6 @@ function addCustomDiv(prompt_action,tabId,mailMessageId) {
style.textContent += ".btn_disabled:hover {background-color:#6a829b !important;color:white !important;}";
style.textContent += "#mzta-loading{height:50px;display:inline-block;}";
style.textContent += "#mzta-model_warn{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);max-height:100%px;min-width:30%;max-width:50%;padding:3px;border-radius:5px;text-align:center;background-color:#FFBABA;border:1px solid;font-size:13px;color:#D8000C;display:none;}#mzta-model_warn a{color:blue;text-decoration: underline;}";
style.textContent += "#mzta-btn_gpt35 {background-color: #007bff;border: none;color: white;padding: 2px 4px;text-align: center;text-decoration: none;display: none;font-size: 13px;margin-left: 4px;transition-duration: 0.4s;cursor: pointer;border-radius: 2px;}";
style.textContent += "#mzta-status-page{position:fixed;bottom:0;left:0;padding-left:5px;font-size:13px;font-style:italic;text-decoration:underline;color:#919191;}";
style.textContent += "#mzta-force-completion{cursor:pointer;position:fixed;bottom:0;right:0;padding-right:5px;font-size:13px;font-style:italic;text-decoration:underline;color:#919191;}";
style.textContent += "#mzta-status-page:hover, #mzta-force-completion:hover{color:#007bff;}";
@ -129,16 +128,6 @@ function addCustomDiv(prompt_action,tabId,mailMessageId) {
modelWarnDiv.textContent = browser.i18n.getMessage("chatgpt_win_model_warning");
fixedDiv.appendChild(modelWarnDiv);
// GPT-3 Button
var btn_gpt35 = document.createElement('button');
btn_gpt35.id="mzta-btn_gpt35";
btn_gpt35.textContent = browser.i18n.getMessage("chatgpt_use_gpt35");
btn_gpt35.onclick = async function() {
browser.storage.sync.set({chatpgt_use_gpt4: false});
force_go = true;
};
modelWarnDiv.appendChild(btn_gpt35);
//prompt name
var prompt_name_div = document.createElement('div');
prompt_name_div.id = 'mzta-prompt-name';
@ -245,40 +234,6 @@ function customTextBtnClick(args) {
args.customDiv.style.display = 'none';
}
function checkGPT4Model() {
return new Promise((resolve, reject) => {
// Set up an interval that shows the warning after 2 seconds
const intervalId2 = setTimeout(() => {
document.getElementById('mzta-model_warn').style.display = 'inline-block';
document.getElementById('mzta-btn_gpt35').style.display = 'inline';
}, 2000);
// Set up an interval that checks the value every 100 milliseconds
const intervalId = setInterval(() => {
// Get the '.text-token-text-secondary' element
// const element = document.querySelector('div#radix-\\\\:ri2\\\\: > div > span.text-token-text-secondary');
const elements = document.querySelectorAll('[id*=radix] span')
for(element of elements){
// Check if the element exists and its content is '4' or '4o'
if ((element && element.textContent === '4')||(element && element.textContent === '4o')||(force_go)) {
// console.log("[ThunderAI] The GPT Model is now '4' or '4o'");
clearInterval(intervalId);
clearTimeout(intervalId2);
document.getElementById('mzta-model_warn').style.display = 'none';
document.getElementById('mzta-btn_gpt35').style.display = 'none';
resolve("GPT4");
break;
} else if (!element) {
console.error("[ThunderAI] Element not found!");
clearInterval(intervalId);
reject("Element not found.");
}
}
}, 200);
});
}
function operation_done(){
let curr_msg = document.getElementById('mzta-curr_msg');
curr_msg.textContent = browser.i18n.getMessage("chatgpt_win_job_completed");
@ -301,11 +256,6 @@ function showCustomTextField(){
}
async function doProceed(message, customText = ''){
let prefs = await browser.storage.sync.get({chatpgt_use_gpt4:false});
if(prefs.chatpgt_use_gpt4){
await checkGPT4Model();
}
//await chatgpt.isLoaded();
let send_result = await chatgpt_sendMsg(message.prompt+' '+customText,'click');
//console.log(">>>>>>>>>>> send_result: " + send_result);
switch(send_result){

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
browser.runtime.onMessage.addListener(async (message) => {
browser.runtime.onMessage.addListener((message) => {
switch (message.command) {
case "getSelectedText":
return Promise.resolve(window.getSelection().toString());
@ -24,11 +24,11 @@ switch (message.command) {
case "replaceSelectedText":
const selectedText = window.getSelection().toString();
if (selectedText === '') {
return;
return false;
}
const sel = window.getSelection();
if (!sel || sel.type !== "Range" || !sel.rangeCount) {
return;
return false;
}
const r = sel.getRangeAt(0);
r.deleteContents();
@ -36,6 +36,7 @@ switch (message.command) {
const doc = parser.parseFromString(message.text, 'text/html');
r.insertNode(doc.body);
browser.runtime.sendMessage({command: "compose_reloadBody", tabId: message.tabId});
return Promise.resolve(true);
break;
case "getText":
@ -56,14 +57,17 @@ switch (message.command) {
case 'promptTooLong':
alert(browser.i18n.getMessage('msg_prompt_too_long'));
return Promise.resolve(true);
break;
case 'sendAlert':
alert(message.message);
return Promise.resolve(true);
break;
default:
// do nothing
return false;
break;
}
});

View file

@ -19,7 +19,8 @@
// Some original methods are derived from https://github.com/ali-raheem/Aify/blob/cfadf52f576b7be3720b5b73af7c8d3129c054da/plugin/html/actions.js
import { getPrompts } from './mzta-prompts.js';
import { getLanguageDisplayName, getMenuContextCompose, getMenuContextDisplay } from './mzta-utils.js'
import { getLanguageDisplayName, getMenuContextCompose, getMenuContextDisplay, i18nConditionalGet } from './mzta-utils.js'
import { taLogger } from './mzta-logger.js';
export class mzta_Menus {
@ -28,23 +29,30 @@ export class mzta_Menus {
menu_context_compose = null;
menu_context_display = null;
menu_listeners = {};
logger = null;
rootMenu = [
//{ id: 'ItemC', act: (info, tab) => { console.log('ItemC', info, tab, info.menuItemId); alert('ItemC') } },
];
constructor(openChatGPT) {
shortcutMenu = [
//{ id: 'ItemD', label: 'LabelD' },
];
constructor(openChatGPT, do_debug = false) {
this.menu_context_compose = getMenuContextCompose();
this.menu_context_display = getMenuContextDisplay();
this.openChatGPT = openChatGPT;
this.allPrompts = [];
this.listener = this.listener.bind(this);
this.logger = new taLogger('mzta_Menus', do_debug);
}
async initialize() {
this.allPrompts = [];
this.rootMenu = [];
this.shortcutMenu = [];
this.menu_listeners = {};
this.allPrompts = await getPrompts(true);
this.allPrompts.sort((a, b) => a.name.localeCompare(b.name));
@ -55,7 +63,7 @@ export class mzta_Menus {
async reload(){
await browser.menus.removeAll().catch(error => {
console.error("[ThunderAI] ERROR removing the menus: ", error);
console.error("[ThunderAI] ERROR removing the menus: ", error);
});
this.removeClickListener();
this.loadMenus();
@ -140,10 +148,24 @@ export class mzta_Menus {
}
}
loadShortcutMenu() {
this.shortcutMenu = [];
this.allPrompts.forEach((prompt) => {
this.addShortcutMenu(prompt);
});
}
addShortcutMenu(prompt) {
let curr_menu_entry = {id: prompt.id, label: i18nConditionalGet(prompt.name), type: prompt.type};
this.shortcutMenu.push(curr_menu_entry);
}
async loadMenus() {
await this.initialize();
await this.addMenu(this.rootMenu);
this.addClickListener();
this.loadShortcutMenu();
this.logger.log("Menus loaded");
}
listener(info, tab) {
@ -165,6 +187,8 @@ export class mzta_Menus {
for (let item of menu) {
let {id, is_default, name, menu, act} = item;
this.logger.log("addMenu: " + id);
await new Promise(resolve =>
browser.menus.create({
id: id,
@ -219,4 +243,23 @@ export class mzta_Menus {
}
}
async executeMenuAction(id) {
// Retrieve the action callback from the menu listeners using the provided ID
const action = this.menu_listeners[id];
if (action) {
try {
// Execute the action callback
await action();
} catch (error) {
// Log any errors that occur during execution
console.error(`Error executing action for menu item ${id}:`, error);
}
} else {
// Warn if no action is found for the provided ID
console.warn(`No action found for menu item ID: ${id}`);
}
}
}

View file

@ -150,9 +150,11 @@ const defaultPrompts = [
export async function getPrompts(onlyEnabled = false){
const defaultPrompts = await getDefaultPrompts_withProps();
const _defaultPrompts = await getDefaultPrompts_withProps();
// console.log('>>>>>>>>>>>> getPrompts _defaultPrompts: ' + JSON.stringify(_defaultPrompts));
const customPrompts = await getCustomPrompts();
let output = defaultPrompts.concat(customPrompts);
// console.log('>>>>>>>>>>>> getPrompts customPrompts: ' + JSON.stringify(customPrompts));
let output = _defaultPrompts.concat(customPrompts);
if(onlyEnabled){
output = output.filter(obj => obj.enabled != 0);
}else{ // order only if we are not filtering, the filtering is for the menus and we are ordering there after i18n
@ -161,6 +163,7 @@ export async function getPrompts(onlyEnabled = false){
for(let i=1; i<=output.length; i++){
output[i-1].idnum = i;
}
// console.log('>>>>>>>>>>>> getPrompts output: ' + JSON.stringify(output));
return output;
}
@ -169,7 +172,7 @@ export function preparePromptsForExport(prompts){
output.forEach(prompt => {
if(prompt.is_default == 1){
Object.keys(prompt).forEach(key => {
if(!['id', 'enabled', 'position_compose', 'position_display'].includes(key)){
if(!['id', 'enabled', 'position_compose', 'position_display', 'need_custom_text'].includes(key)){
delete prompt[key];
}
})
@ -181,7 +184,9 @@ export function preparePromptsForExport(prompts){
}
export async function preparePromptsForImport(prompts){
// console.log(">>>>>>>>>>> preparePromptsForImport prompts: " + JSON.stringify(prompts));
const output = await getPrompts();
// console.log(">>>>>>>>>>> preparePromptsForImport output: " + JSON.stringify(output));
prompts.forEach(prompt => {
if(output.some(p => p.id == prompt.id)){
Object.keys(prompt).forEach(key => {
@ -192,13 +197,17 @@ export async function preparePromptsForImport(prompts){
}
});
output.sort((a, b) => a.id.localeCompare(b.id));
//console.log(">>>>>>>>>>> preparePromptsForImport: " + JSON.stringify(output));
// console.log(">>>>>>>>>>> preparePromptsForImport final output: " + JSON.stringify(output));
return output;
}
async function getDefaultPrompts_withProps() {
let prefs = await browser.storage.sync.get({_default_prompts_properties: null}); //production
let defaultPrompts_prop = [...defaultPrompts];
let prefs = await browser.storage.local.get({_default_prompts_properties: null});
// console.log('>>>>>>>>>>>> getDefaultPrompts_withProps prefs: ' + JSON.stringify(prefs));
//let defaultPrompts_prop = [...defaultPrompts];
let defaultPrompts_prop = JSON.parse(JSON.stringify(defaultPrompts));
// console.log('>>>>>>>>>>>> getDefaultPrompts_withProps defaultPrompts: ' + JSON.stringify(defaultPrompts));
// console.log('>>>>>>>>>>>> getDefaultPrompts_withProps defaultPrompts_prop: ' + JSON.stringify(defaultPrompts_prop));
if(prefs._default_prompts_properties === null){ // no default prompts properties saved
let pos = 1;
defaultPrompts_prop.forEach((prompt) => {
@ -207,6 +216,7 @@ async function getDefaultPrompts_withProps() {
prompt.enabled = 1;
pos++;
})
// console.log('>>>>>>>>>>>> getDefaultPrompts_withProps [no prop saved] defaultPrompts_prop: ' + JSON.stringify(defaultPrompts_prop));
} else { // we have saved default prompts properties
let pos = 1000;
defaultPrompts_prop.forEach((prompt) => {
@ -214,6 +224,7 @@ async function getDefaultPrompts_withProps() {
prompt.position_compose = prefs._default_prompts_properties[prompt.id].position_compose;
prompt.position_display = prefs._default_prompts_properties[prompt.id].position_display;
prompt.enabled = prefs._default_prompts_properties[prompt.id].enabled;
prompt.need_custom_text = prefs._default_prompts_properties[prompt.id].need_custom_text;
}else{
prompt.position_display = pos;
prompt.position_compose = pos;
@ -221,13 +232,14 @@ async function getDefaultPrompts_withProps() {
pos++;
}
})
// console.log('>>>>>>>>>>>> getDefaultPrompts_withProps [prop saved] defaultPrompts_prop: ' + JSON.stringify(defaultPrompts_prop));
}
return defaultPrompts_prop;
}
async function getCustomPrompts() {
let prefs = await browser.storage.sync.get({_custom_prompt: null});
let prefs = await browser.storage.local.get({_custom_prompt: null});
if(prefs._custom_prompt === null){
return [];
} else {
@ -238,14 +250,13 @@ async function getCustomPrompts() {
export async function setDefaultPromptsProperties(prompts) {
let default_prompts_properties = {};
prompts.forEach((prompt) => {
default_prompts_properties[prompt.id] = {position_compose: prompt.position_compose, position_display: prompt.position_display, enabled: prompt.enabled};
default_prompts_properties[prompt.id] = {position_compose: prompt.position_compose, position_display: prompt.position_display, enabled: prompt.enabled, need_custom_text: prompt.need_custom_text};
});
//console.log('>>>>>>>>>>>>>> default_prompts_properties: ' + JSON.stringify(default_prompts_properties));
await browser.storage.sync.set({_default_prompts_properties: default_prompts_properties});
await browser.storage.local.set({_default_prompts_properties: default_prompts_properties});
}
export async function setCustomPrompts(prompts) {
await browser.storage.sync.set({_custom_prompt: prompts});
await browser.storage.local.set({_custom_prompt: prompts});
}

32
js/mzta-store.js Normal file
View file

@ -0,0 +1,32 @@
/*
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
* Copyright (C) 2024 Mic (m@micz.it)
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
export const taStore = {
async setSessionData (key, value) {
let obj = {};
obj[key] = value;
await browser.storage.session.set(obj);
},
async getSessionData (key) {
let output = await browser.storage.session.get(key);
return output[key];
},
};

View file

@ -182,3 +182,71 @@ const insertHtml = function (replyHtml, fullBody_string) {
fullBody.body.insertBefore(fragment, fullBody.body.firstChild);
return fullBody.body.innerHTML;
}
export async function getCustomPromptsUsedSpace(){
// we can't use this, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1385832
//let customprompts_space = await browser.storage.local.getBytesInUse("_custom_prompt");
//let's use a workaround
let customprompts_space = Object.entries(await browser.storage.local.get(["_custom_prompt", "_default_prompts_properties"])).map(([key, value]) => key.length + JSON.stringify(value).length).reduce((acc, x) => acc + x, 0)
return formatBytes(customprompts_space);
}
function formatBytes(bytes, decimals = 2) {
if (bytes === 0) return '0 Bytes';
const step = 1024;
const suffixes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const index = Math.floor(Math.log(bytes) / Math.log(step));
const value = bytes / Math.pow(step, index);
// Ensure the index does not exceed the suffix array length
const suffix = suffixes[index] || suffixes[suffixes.length - 1];
return `${value.toFixed(decimals)} ${suffix}`;
}
// We need to migrate the Custom Prompts storage from storage.sync to storage.local
// because the storage.sync has a too narrow limit, see https://github.com/micz/ThunderAI/issues/129
export async function migrateCustomPromptsStorage(){
//check if storage.sync has custom prompts
let custom_prompts_sync = await browser.storage.sync.get({_custom_prompt: null});
if(custom_prompts_sync._custom_prompt === null){
// There are no custom prompts in storage.sync, nothing to do
// console.log("migrateCustomPromptsStorage: no custom prompts in storage.sync, nothing to do");
return;
}
//check if storage.local has custom prompts
let custom_prompts_local = await browser.storage.local.get({_custom_prompt: null});
if(custom_prompts_local._custom_prompt !== null){
// There are custom prompts in storage.local, nothing to do
// console.log("migrateCustomPromptsStorage: there are custom prompts in storage.local, nothing to do");
return;
}
//copy custom prompts from storage.sync to storage.local
await browser.storage.local.set({_custom_prompt: custom_prompts_sync._custom_prompt});
await browser.storage.sync.remove("_custom_prompt");
// console.log("migrateCustomPromptsStorage: migrated custom prompts from storage.sync to storage.local");
}
// Do the same for the default prompts properties
export async function migrateDefaultPromptsPropStorage(){
//check if storage.sync has default prompts properties
let default_prompts_properties_sync = await browser.storage.sync.get({_default_prompts_properties: null});
if(default_prompts_properties_sync._default_prompts_properties === null){
// There are no default prompts properties in storage.sync, nothing to do
// console.log("migrateDefaultPromptsPropStorage: no default prompts properties in storage.sync, nothing to do");
return;
}
//check if storage.local has default prompts properties
let default_prompts_properties_local = await browser.storage.local.get({_default_prompts_properties: null});
if(default_prompts_properties_local._default_prompts_properties !== null){
// There are default prompts properties in storage.local, nothing to do
// console.log("migrateDefaultPromptsPropStorage: there are default prompts properties in storage.local, nothing to do");
return;
}
//copy default prompts properties from storage.sync to storage.local
await browser.storage.local.set({_default_prompts_properties: default_prompts_properties_sync._default_prompts_properties});
await browser.storage.sync.remove("_default_prompts_properties");
// console.log("migrateDefaultPromptsPropStorage: migrated default prompts properties from storage.sync to storage.local");
}

View file

@ -2,7 +2,7 @@
"manifest_version": 2,
"name": "ThunderAI",
"description": "__MSG_extensionDescription__",
"version": "2.1.5",
"version": "2.2.0pre5",
"author": "Mic (m@micz.it)",
"homepage_url": "https://micz.it/thunderbird-addon-thunderai/",
"browser_specific_settings": {
@ -14,12 +14,12 @@
"message_display_action": {
"default_title": "__MSG_menu_title__",
"default_icon": "images/icon-32px.png",
"type": "menu"
"default_popup": "popup/mzta-popup.html"
},
"compose_action": {
"default_title": "__MSG_menu_title__",
"default_icon": "images/icon-32px.png",
"type":"menu"
"default_popup": "popup/mzta-popup.html"
},
"permissions": [
"compose",
@ -45,6 +45,11 @@
"options_ui": {
"page": "options/mzta-options.html"
},
"commands": {
"_thunderai__do_action": {
"description": "Opens the ThunderAI prompts menu"
}
},
"content_scripts": [
{
"matches": ["https://*.chatgpt.com/*"],

View file

@ -20,7 +20,11 @@ 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 } from './js/mzta-utils.js';
import { taStore } from './js/mzta-store.js';
import { getCurrentIdentity, getOriginalBody, replaceBody, setBody, i18nConditionalGet, generateCallID, migrateCustomPromptsStorage, migrateDefaultPromptsPropStorage } from './js/mzta-utils.js';
await migrateCustomPromptsStorage();
await migrateDefaultPromptsPropStorage();
var original_html = '';
var modified_html = '';
@ -62,7 +66,64 @@ browser.contentScripts.register({
runAt: "document_idle"
});
messenger.runtime.onMessage.addListener(async (message, sender, sendResponse) => {
let ThunderAI_Shortcut = "Ctrl+Alt+A";
// Shortcut
messenger.commands.update({
name: "_thunderai__do_action",
shortcut: ThunderAI_Shortcut
}).then(() => {
taLog.log('Shortcut [' + ThunderAI_Shortcut + '] registered successfully!');
}).catch((error) => {
taLog.error('Error registering shortcut [' + ThunderAI_Shortcut + ']: ' + error);
});
// Listen for shortcut command
messenger.commands.onCommand.addListener((command, tab) => {
if (command === "_thunderai__do_action") {
handleShortcut(tab);
}
});
async function handleShortcut(tab) {
taLog.log("Shortcut triggered!");
if(!["mail", "messageCompose","messageDisplay"].includes(tab.type)){
return;
}
switch (tab.type) {
case "mail":
case "messageDisplay":
browser.messageDisplayAction.openPopup();
break;
case "messageCompose":
browser.composeAction.openPopup();
break;
default:
break;
}
}
function preparePopupMenu(tab) {
let output = {};
output.lastShortcutTabId = tab.id;
output.lastShortcutTabType = tab.type;
output.lastShortcutPromptsData = menus.shortcutMenu;
output.lastShortcutFiltering = 0;
switch (tab.type) {
case "mail":
case "messageDisplay":
output.lastShortcutFiltering = 1;
break;
case "messageCompose":
output.lastShortcutFiltering = 2;
break;
default:
break;
}
return output;
}
messenger.runtime.onMessage.addListener((message, sender, sendResponse) => {
// Check what type of message we have received and invoke the appropriate
// handler function.
if (message && message.hasOwnProperty("command")){
@ -80,69 +141,95 @@ messenger.runtime.onMessage.addListener(async (message, sender, sendResponse) =>
});
break;
case 'chatgpt_replaceSelectedText':
//console.log('chatgpt_replaceSelectedText: [' + message.tabId +'] ' + message.text)
original_html = await getOriginalBody(message.tabId);
await browser.tabs.sendMessage(message.tabId, { command: "replaceSelectedText", text: message.text, tabId: message.tabId });
async function _replaceSelectedText(tabId, text) {
//console.log('chatgpt_replaceSelectedText: [' + tabId +'] ' + text)
original_html = await getOriginalBody(tabId);
await browser.tabs.sendMessage(tabId, { command: "replaceSelectedText", text: text, tabId: tabId });
return true;
case 'chatgpt_replyMessage':
const paragraphsHtmlString = message.text;
//console.log(">>>>>>>>>>>> paragraphsHtmlString: " + paragraphsHtmlString);
let prefs = await browser.storage.sync.get({reply_type: 'reply_all'});
//console.log('reply_type: ' + prefs.reply_type);
let replyType = 'replyToAll';
if(prefs.reply_type === 'reply_sender'){
replyType = 'replyToSender';
}
//console.log('replyType: ' + replyType);
// browser.messageDisplay.getDisplayedMessage(message.tabId).then(async (mailMessage) => {
// let reply_tab = await browser.compose.beginReply(mailMessage.id, replyType, {
// type: "reply",
// //body: paragraphsHtmlString,
// isPlainText: false,
// identityId: await getCurrentIdentity(mailMessage),
// })
//console.log(">>>>>>>>>>>> message.mailMessageId: " + message.mailMessageId);
browser.messages.get(message.mailMessageId).then(async (mailMessage) => {
let curr_idn = await getCurrentIdentity(mailMessage)
let reply_tab = await browser.compose.beginReply(mailMessage.id, replyType, {
type: "reply",
//body: paragraphsHtmlString,
isPlainText: false,
identityId: curr_idn,
})
// 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);
//console.log(">>>>>>>>>>>> reply_tab: " + tabId);
resolve();
return _replaceSelectedText(message.tabId, message.text);
case 'chatgpt_replyMessage':
async function _replyMessage(message) {
const paragraphsHtmlString = message.text;
//console.log(">>>>>>>>>>>> paragraphsHtmlString: " + paragraphsHtmlString);
let prefs = await browser.storage.sync.get({reply_type: 'reply_all'});
//console.log('reply_type: ' + prefs.reply_type);
let replyType = 'replyToAll';
if(prefs.reply_type === 'reply_sender'){
replyType = 'replyToSender';
}
//console.log('replyType: ' + replyType);
// browser.messageDisplay.getDisplayedMessage(message.tabId).then(async (mailMessage) => {
// let reply_tab = await browser.compose.beginReply(mailMessage.id, replyType, {
// type: "reply",
// //body: paragraphsHtmlString,
// isPlainText: false,
// identityId: await getCurrentIdentity(mailMessage),
// })
//console.log(">>>>>>>>>>>> message.mailMessageId: " + message.mailMessageId);
let _mailMessage = await browser.messages.get(message.mailMessageId);
let curr_idn = await getCurrentIdentity(_mailMessage)
let reply_tab = await browser.compose.beginReply(_mailMessage.id, replyType, {
type: "reply",
//body: paragraphsHtmlString,
isPlainText: false,
identityId: curr_idn,
})
// 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);
//console.log(">>>>>>>>>>>> reply_tab: " + tabId);
resolve();
}
}
}
// Early exit if loaded already
if (tabIsLoaded(reply_tab)) {
resolve();
} else {
browser.tabs.onUpdated.addListener(listener);
}
});
// we need to wait for the compose windows to load the content script
//setTimeout(() => browser.tabs.sendMessage(reply_tab.id, { command: "insertText", text: paragraphsHtmlString, tabId: reply_tab.id }), 500);
setTimeout(async () => await replaceBody(reply_tab.id, paragraphsHtmlString), 500);
return true;
});
// Early exit if loaded already
if (tabIsLoaded(reply_tab)) {
resolve();
} else {
browser.tabs.onUpdated.addListener(listener);
}
});
// we need to wait for the compose windows to load the content script
//setTimeout(() => browser.tabs.sendMessage(reply_tab.id, { command: "insertText", text: paragraphsHtmlString, tabId: reply_tab.id }), 500);
setTimeout(async () => await replaceBody(reply_tab.id, paragraphsHtmlString), 500);
return true;
}
return _replyMessage(message);
break;
case 'compose_reloadBody':
modified_html = await getOriginalBody(message.tabId);
await setBody(message.tabId, original_html);
await setBody(message.tabId, modified_html);
async function _reloadBody(tabId) {
modified_html = await getOriginalBody(tabId);
await setBody(tabId, original_html);
await setBody(tabId, modified_html);
return true;
}
return _reloadBody(message.tabId);
break;
case 'reload_menus':
await menus.reload();
taLog.log("[ThunderAI] Reloaded menus");
menus.reload();
taLog.log("Reloading menus");
return Promise.resolve(true);
break;
case 'shortcut_do_prompt':
taLog.log("Executing shortcut, promptId: " + message.promptId);
menus.executeMenuAction(message.promptId);
return Promise.resolve(true);
break;
case 'popup_menu_ready':
async function _popup_menu_ready() {
let tabs = await browser.tabs.query({ active: true, currentWindow: true });
if(tabs.length == 0){
return false;
}
return preparePopupMenu(tabs[0]);
//return true;
}
return _popup_menu_ready();
break;
default:
break;
@ -165,29 +252,31 @@ async function openChatGPT(promptText, action, curr_tabId, prompt_name = '', do_
return;
}
let mailMessage = await browser.messageDisplay.getDisplayedMessage(curr_tabId);
switch(prefs.connection_type){
case 'chatgpt_web':
// We are using the ChatGPT web interface
let rand_call_id = '_chatgptweb_' + generateCallID();
// We are using the ChatGPT web interface
let newWindow = await browser.windows.create({
let win_options = {
url: "https://chatgpt.com?call_id=" + rand_call_id,
type: "popup",
width: prefs.chatgpt_win_width,
height: prefs.chatgpt_win_height
});
}
taLog.log("[chatgpt_web] prefs.chatgpt_win_width: " + prefs.chatgpt_win_width + ", prefs.chatgpt_win_height: " + prefs.chatgpt_win_height);
let newWindowId = newWindow.id;
let createdTab = newWindow.tabs[0];
const listener = async (message, sender, sendResponse) => {
if (message.command === "chatgpt_web_ready_" + rand_call_id) {
if((prefs.chatgpt_win_width != '') && (prefs.chatgpt_win_height != '') && (prefs.chatgpt_win_width != 0) && (prefs.chatgpt_win_height != 0)){
win_options.width = prefs.chatgpt_win_width,
win_options.height = prefs.chatgpt_win_height
}
const listener = (message, sender, sendResponse) => {
async function handleChatGptWeb(createdTab) {
taLog.log("[ThunderAI] ChatGPT web interface script started...");
let pre_script = `let mztaWinId = `+ newWindowId +`;
let pre_script = `let mztaWinId = `+ createdTab.windowId +`;
let mztaStatusPageDesc="`+ browser.i18n.getMessage("prefs_status_page") +`";
let mztaForceCompletionDesc="`+ browser.i18n.getMessage("chatgpt_force_completion") +`";
let mztaForceCompletionTitle="`+ browser.i18n.getMessage("chatgpt_force_completion_title") +`";
@ -200,7 +289,7 @@ async function openChatGPT(promptText, action, curr_tabId, prompt_name = '', do_
taLog.log("Waiting 1 sec done");
await browser.tabs.executeScript(createdTab.id, { code: pre_script + mzta_script, matchAboutBlank: false });
let mailMessage = await browser.messageDisplay.getDisplayedMessage(curr_tabId);
// 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});
@ -208,100 +297,174 @@ async function openChatGPT(promptText, action, curr_tabId, prompt_name = '', do_
taLog.log("[ThunderAI] ChatGPT Web script injected successfully");
browser.runtime.onMessage.removeListener(listener);
}
if (message.command === "chatgpt_web_ready_" + rand_call_id) {
return handleChatGptWeb(sender.tab)
}
return false;
}
browser.runtime.onMessage.addListener(listener);
break; // chatgpt_web
await browser.windows.create(win_options);
break; // chatgpt_web - END
case 'chatgpt_api':
// We are using the ChatGPT API
// We are using the ChatGPT API
let rand_call_id2 = '_openai_' + generateCallID();
await browser.windows.create({
url: browser.runtime.getURL('api_webchat/index.html?llm='+prefs.connection_type+'&call_id='+rand_call_id2),
type: "popup",
width: prefs.chatgpt_win_width,
height: prefs.chatgpt_win_height
});
const listener2 = (message, sender, sendResponse) => {
const listener2 = async (message, sender, sendResponse) => {
if (message.command === "openai_api_ready_"+rand_call_id2) {
let newWindow2 = await browser.windows.get(message.window_id, {populate: true});
let createdTab2 = newWindow2.tabs[0];
let mailMessage = await browser.messageDisplay.getDisplayedMessage(curr_tabId);
function handleChatGptApi(createdTab) {
let mailMessageId2 = -1;
if(mailMessage) mailMessageId2 = mailMessage.id;
// check if the config is present, or give a message error
if (prefs.chatgpt_api_key == '') {
browser.tabs.sendMessage(createdTab2.id, { command: "api_error", error: browser.i18n.getMessage('chatgpt_empty_apikey')});
browser.tabs.sendMessage(createdTab.id, { command: "api_error", error: browser.i18n.getMessage('chatgpt_empty_apikey')});
return;
}
if (prefs.chatgpt_model == '') {
browser.tabs.sendMessage(createdTab2.id, { command: "api_error", error: browser.i18n.getMessage('chatgpt_empty_model')});
browser.tabs.sendMessage(createdTab.id, { command: "api_error", error: browser.i18n.getMessage('chatgpt_empty_model')});
return;
}
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!');
//console.log(">>>>>>>>>> sender: " + JSON.stringify(sender));
browser.tabs.sendMessage(createdTab.id, { command: "api_send", prompt: promptText, action: action, tabId: curr_tabId, mailMessageId: mailMessageId2, do_custom_text: do_custom_text});
taLog.log('[OpenAI ChatGPT] Connection succeded!');
browser.runtime.onMessage.removeListener(listener2);
}
if (message.command === "openai_api_ready_"+rand_call_id2) {
return handleChatGptApi(sender.tab);
}
return false;
}
browser.runtime.onMessage.addListener(listener2);
break; // chatgpt_api
let win_options2 = {
url: browser.runtime.getURL('api_webchat/index.html?llm='+prefs.connection_type+'&call_id='+rand_call_id2),
type: "popup",
}
taLog.log("[chatgpt_api] prefs.chatgpt_win_width: " + prefs.chatgpt_win_width + ", prefs.chatgpt_win_height: " + prefs.chatgpt_win_height);
if((prefs.chatgpt_win_width != '') && (prefs.chatgpt_win_height != '') && (prefs.chatgpt_win_width != 0) && (prefs.chatgpt_win_height != 0)){
win_options2.width = prefs.chatgpt_win_width,
win_options2.height = prefs.chatgpt_win_height
}
await browser.windows.create(win_options2);
break; // chatgpt_api - END
case 'ollama_api':
// We are using the Ollama API
// We are using the Ollama API
taLog.log("Ollama API window opening...");
let rand_call_id3 = '_ollama_' + generateCallID();
await browser.windows.create({
url: browser.runtime.getURL('api_webchat/index.html?llm='+prefs.connection_type+'&call_id='+rand_call_id3),
type: "popup",
width: prefs.chatgpt_win_width,
height: prefs.chatgpt_win_height
});
const listener3 = async (message, sender, sendResponse) => {
if (message.command === "ollama_api_ready_"+rand_call_id3) {
function handleOllamaApi(createdTab3) {
taLog.log("Ollama API window ready.");
taLog.log("message.window_id: " + message.window_id)
let newWindow3 = await browser.windows.get(message.window_id, {populate: true});
let createdTab3 = newWindow3.tabs[0];
taLog.log(">>>>>> createdTab3.id: " + createdTab3.id)
let mailMessage3 = await browser.messageDisplay.getDisplayedMessage(curr_tabId);
taLog.log("createdTab3.id: " + createdTab3.id)
// let mailMessage3 = await browser.messageDisplay.getDisplayedMessage(curr_tabId);
let mailMessageId3 = -1;
if(mailMessage3) mailMessageId3 = mailMessage3.id;
taLog.log(">>>>>> mailMessageId3: " + mailMessageId3)
if(mailMessage) mailMessageId3 = mailMessage.id;
taLog.log("mailMessageId3: " + mailMessageId3)
// check if the config is present, or give a message error
if (prefs.ollama_host == '') {
await browser.tabs.sendMessage(createdTab3.id, { command: "api_error", error: browser.i18n.getMessage('ollama_empty_host')});
browser.tabs.sendMessage(createdTab3.id, { command: "api_error", error: browser.i18n.getMessage('ollama_empty_host')});
return;
}
if (prefs.ollama_model == '') {
await browser.tabs.sendMessage(createdTab3.id, { command: "api_error", error: browser.i18n.getMessage('ollama_empty_model')});
browser.tabs.sendMessage(createdTab3.id, { command: "api_error", error: browser.i18n.getMessage('ollama_empty_model')});
return;
}
await browser.tabs.sendMessage(createdTab3.id, { command: "api_send", prompt: promptText, action: action, tabId: curr_tabId, mailMessageId: mailMessageId3, do_custom_text: do_custom_text});
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!');
browser.runtime.onMessage.removeListener(listener3);
}
if (message.command === "ollama_api_ready_"+rand_call_id3) {
return handleOllamaApi(sender.tab);
}else{
return false;
}
}
browser.runtime.onMessage.addListener(listener3);
break; // ollama_api
let win_options3 = {
url: browser.runtime.getURL('api_webchat/index.html?llm='+prefs.connection_type+'&call_id='+rand_call_id3),
type: "popup",
}
taLog.log("[ollama_api] prefs.chatgpt_win_width: " + prefs.chatgpt_win_width + ", prefs.chatgpt_win_height: " + prefs.chatgpt_win_height);
if((prefs.chatgpt_win_width != '') && (prefs.chatgpt_win_height != '') && (prefs.chatgpt_win_width != 0) && (prefs.chatgpt_win_height != 0)){
win_options3.width = prefs.chatgpt_win_width,
win_options3.height = prefs.chatgpt_win_height
}
await browser.windows.create(win_options3);
break; // ollama_api - END
case 'openai_comp_api':
// We are using the OpenAI Comp API
let rand_call_id4 = '_openai_comp_api_' + generateCallID();
const listener4 = async (message, sender, sendResponse) => {
function handleOpenAICompApi(createdTab) {
let mailMessageId4 = -1;
if(mailMessage) mailMessageId4 = mailMessage.id;
// check if the config is present, or give a message error
if (prefs.openai_comp_host == '') {
browser.tabs.sendMessage(createdTab.id, { command: "api_error", error: browser.i18n.getMessage('OpenAIComp_empty_host')});
return;
}
if (prefs.openai_comp_model == '') {
browser.tabs.sendMessage(createdTab.id, { command: "api_error", error: browser.i18n.getMessage('OpenAIComp_empty_model')});
return;
}
browser.tabs.sendMessage(createdTab.id, { command: "api_send", prompt: promptText, action: action, tabId: curr_tabId, mailMessageId: mailMessageId4, do_custom_text: do_custom_text});
taLog.log('[OpenAI Comp API] Connection succeded!');
browser.runtime.onMessage.removeListener(listener4);
}
if (message.command === "openai_comp_api_ready_"+rand_call_id4) {
return handleOpenAICompApi(sender.tab);
}
return false;
}
browser.runtime.onMessage.addListener(listener4);
let win_options4 = {
url: browser.runtime.getURL('api_webchat/index.html?llm='+prefs.connection_type+'&call_id='+rand_call_id4),
type: "popup",
}
taLog.log("[openai_comp_api] prefs.chatgpt_win_width: " + prefs.chatgpt_win_width + ", prefs.chatgpt_win_height: " + prefs.chatgpt_win_height);
if((prefs.chatgpt_win_width != '') && (prefs.chatgpt_win_height != '') && (prefs.chatgpt_win_width != 0) && (prefs.chatgpt_win_height != 0)){
win_options4.width = prefs.chatgpt_win_width,
win_options4.height = prefs.chatgpt_win_height
}
await browser.windows.create(win_options4);
break; // openai_comp_api
}
}
@ -316,5 +479,5 @@ function checkScreenDimensions(prefs){
}
// Menus handling
const menus = new mzta_Menus(openChatGPT);
const menus = new mzta_Menus(openChatGPT, prefs_debug.do_debug);
menus.loadMenus();

View file

@ -20,11 +20,16 @@ export const prefs_default = {
do_debug: false,
chatgpt_win_height: 800,
chatgpt_win_width: 700,
chatpgt_use_gpt4: false,
default_chatgpt_lang: '',
connection_type: 'chatgpt_web', //Other values: 'chatgpt_api', 'ollama_api'
connection_type: 'chatgpt_web', //Other values: 'chatgpt_api', 'ollama_api', 'openai_comp_api'
chatgpt_api_key: '',
chatgpt_model: '',
ollama_host: '',
ollama_model: '',
openai_comp_host: '', // For OpenAI Compatible API as LM-Studio
openai_comp_model: '',
openai_comp_api_key: '',
openai_comp_chat_name: 'OpenAI Comp',
dynamic_menu_force_enter: false,
dynamic_menu_order_alphabet: true,
}

View file

@ -96,22 +96,56 @@ tr.conntype_chatgpt_web, tr.conntype_chatgpt_web2{
background-color: rgb(255, 209, 183);
}
/* color to be used for a local LLM
.conntype_chatgpt_web,.conntype_local_llm{
background-color: rgb(205, 255, 205);
tr.conntype_ollama_api, tr.conntype_ollama_api2{
background-color: rgb(177, 238, 169);
}
tr.conntype_openai_comp_api, tr.conntype_openai_comp_api2{
background-color: rgb(213, 169, 238);
}
*/
#chatgpt_model_fetch_loading{
display: none;
font-style: italic;
}
.api_key-container {
position: relative;
width: 100%;
}
.api_key-container input {
width: -moz-available;
margin-right: 25px;
}
input[type="text"], input[type="password"]{
border-radius: 4px;
padding: 2px;
}
input.option-input[type="text"]{
width: -moz-available;
}
.api_key-container .toggle-icon {
position: absolute;
right: 0px;
top: 50%;
transform: translateY(-50%);
cursor: pointer;
}
#ollama_model_fetch_loading{
display: none;
font-style: italic;
}
#openai_comp_model_fetch_loading{
display: none;
font-style: italic;
}
@media (prefers-color-scheme: dark) {
body {
background-color: rgb(35, 34, 43);
@ -135,9 +169,12 @@ tr.conntype_chatgpt_web, tr.conntype_chatgpt_web2{
background-color: rgb(7, 58, 0);
}
/* color to be used for a local LLM
.conntype_chatgpt_web,.conntype_local_llm{
background-color: rgb(0, 39, 0);
tr.conntype_openai_comp_api, tr.conntype_openai_comp_api2{
background-color: rgb(89, 20, 129);
}
*/
.api_key-container .toggle-icon img {
filter: invert(1);
}
}

View file

@ -18,7 +18,7 @@
</td>
</tr>
<tr>
<td></td>
<td><i>__MSG_prefs_OptionText_chatgpt_win_dims_info__</i></td>
<td>
<span class="dims_label">__MSG_prefs_OptionText_chatgpt_win_width__</span>
<input type="number" id="chatgpt_win_width" name="chatgpt_win_width" class="option-input" />
@ -59,6 +59,28 @@
</label>
</td>
</tr>
<tr>
<td><label>
<span>__MSG_prefs_OptionText_dynamic_menu_order_alphabet__</span>
</label></td>
<td>
<label>
<input type="checkbox" id="dynamic_menu_order_alphabet" name="dynamic_menu_order_alphabet" class="option-input" />
&nbsp;<span>__MSG_prefs_OptionText_dynamic_menu_order_alphabet_info__</span>
</label>
</td>
</tr>
<tr>
<td><label>
<span>__MSG_prefs_OptionText_dynamic_menu_force_enter__</span>
</label></td>
<td>
<label>
<input type="checkbox" id="dynamic_menu_force_enter" name="dynamic_menu_force_enter" class="option-input" />
&nbsp;<span>__MSG_prefs_OptionText_dynamic_menu_force_enter_info__</span>
</label>
</td>
</tr>
<tr>
<td>
<label>
@ -71,31 +93,23 @@
<option value="chatgpt_web">__MSG_prefs_Connection_type_ChatGTP_Web__</option>
<option value="chatgpt_api">__MSG_prefs_Connection_type_ChatGTP_API__</option>
<option value="ollama_api">__MSG_prefs_Connection_type_Ollama_API__</option>
<option value="openai_comp_api">__MSG_prefs_Connection_type_OpenAI_Comp_API__</option>
</select>
</label>
</td>
</tr>
<tr>
<tr class="conntype_chatgpt_web">
<td><label>
<span>__MSG_prefs_OptionText_chatpgt_use_gpt4__</span>
</label></td>
<td>
<label>
<input type="checkbox" id="chatpgt_use_gpt4" name="chatpgt_use_gpt4" class="option-input" />
&nbsp;<span>__MSG_prefs_OptionText_chatpgt_use_gpt4_info__</span>
</label>
</td>
</tr>
<tr class="conntype_chatgpt_api">
<td><label>
<span>__MSG_prefs_ChatGPT_API_Key__</span>
</label></td>
<td>
<label>
<input type="text" id="chatgpt_api_key" name="chatgpt_api_key" class="option-input" style="width:28em" />
&nbsp;<span></span>
</label>
<div class="api_key-container">
<label>
<input type="password" id="chatgpt_api_key" name="chatgpt_api_key" class="option-input"/>
</label>
<span class="toggle-icon" id="toggle_chatgpt_api_key"><img src="../images/pwd-show.png" id="pwd-icon_chatgpt_api_key"></span>
</div>
</td>
</tr>
<tr class="conntype_chatgpt_api">
@ -114,13 +128,12 @@
</tr>
<tr class="conntype_ollama_api">
<td><label>
<span>__MSG_prefs_Ollama_Host__</span>
<br><i style="font-size:smaller;">__MSG_prefs_Ollama_Host_Info__ http://127.0.0.1:11434</i>
<span>__MSG_prefs_API_Host__</span>
<br><i style="font-size:smaller;">__MSG_prefs_API_Host_Info__ http://127.0.0.1:11434</i>
</label></td>
<td>
<label>
<input type="text" id="ollama_host" name="ollama_host" class="option-input" style="width:28em" />
&nbsp;<span></span>
<input type="text" id="ollama_host" name="ollama_host" class="option-input"/>
</label>
</td>
</tr>
@ -138,8 +151,58 @@
</label>
</td>
</tr>
<tr>
<tr class="conntype_openai_comp_api">
<td><label>
<span>__MSG_prefs_API_Host__</span>
<br><i style="font-size:smaller;">__MSG_prefs_API_Host_Info__ http://localhost:1234</i>
</label></td>
<td>
<label>
<input type="text" id="openai_comp_host" name="openai_comp_host" class="option-input" />
</label>
</td>
</tr>
<tr class="conntype_openai_comp_api">
<td><label>
<span>__MSG_prefs_OpenAIComp_API_Key__</span>
<br><i style="font-size:smaller;">__MSG_Optional__</i>
</label></td>
<td>
<div class="api_key-container">
<label>
<input type="password" id="openai_comp_api_key" name="openai_comp_api_key" class="option-input"/>
</label>
<span class="toggle-icon" id="toggle_openai_comp_api_key"><img src="../images/pwd-show.png" id="pwd-icon_openai_comp_api_key"></span>
</div>
</td>
</tr>
<tr class="conntype_openai_comp_api">
<td>
<label>
<span>__MSG_OpenAIComp_Models__</span>
</label>
</td>
<td>
<button id="btnUpdateOpenAICompModels">__MSG_OpenAIComp_Models_Fetch__</button> <span id="openai_comp_model_fetch_loading">__MSG_Loading__</span><br>
<label>
<select id="openai_comp_model" name="openai_comp_model" class="option-input">
</select>
</label>
</td>
</tr>
<tr class="conntype_openai_comp_api">
<td><label>
<span>__MSG_prefs_OpenAIComp_ChatName__</span>
</label></td>
<td>
<label>
<input type="text" id="openai_comp_chat_name" name="openai_comp_chat_name" class="option-input" />
<br>__MSG_prefs_OpenAIComp_ChatName_Info__
</label>
</td>
</tr>
<tr>
<td style="width:20em"><label>
<span>__MSG_Debug__</span>
</label></td>
<td>
@ -155,9 +218,14 @@
</div>
<div id="miczDescription">
<h1>__MSG_prefsInfoTitle__</h1>
<p><span class="conntype_chatgpt_web">__MSG_prefsInfoDesc_1__<br/></span>
<span class="conntype_chatgpt_api">__MSG_prefsInfoDesc_2__<br/></span>
<span class="conntype_ollama_api">__MSG_prefsInfoDesc_3__</span></p>
<p>
<span class="conntype_chatgpt_web">__MSG_prefsInfoDesc_1__<br></span>
<span class="conntype_chatgpt_api">__MSG_prefsInfoDesc_2__<br></span>
<span class="conntype_ollama_api">__MSG_prefsInfoDesc_3__ [<a href="https://micz.it/thunderbird-addon-thunderai/ollama-cors-information/">__MSG_customPrompts_managePrompts_help__</a>]<br></span>
<span class="conntype_openai_comp_api">__MSG_prefsInfoDesc_4__</span>
<br><span>__MSG_prefsInfoDesc_5__
<br>__MSG_prefsInfoDesc_6__</span>
</p>
</div>
<div id="miczTranslate">__MSG_TranslateText__ <a href="https://micz.it/thunderbird-addon-thunderai/translate/">__MSG_TranslateLink__</a></div>
<div id="miczDonation">__MSG_prefsDonation_1__<br/><a href="http://micz.it/thunderbird-addon-thunderai/donate/">__MSG_prefsDonation_2__</a></div>

View file

@ -21,6 +21,7 @@ import { prefs_default } from './mzta-options-default.js';
import { taLogger } from '../js/mzta-logger.js';
import { OpenAI } from '../js/api/openai.js';
import { Ollama } from '../js/api/ollama.js';
import { OpenAIComp } from '../js/api/openai_comp.js'
let taLog = new taLogger("mzta-options",true);
@ -36,7 +37,8 @@ function saveOptions(e) {
case 'number':
options[element.id] = element.valueAsNumber;
break;
case 'text':
case 'text':
case 'password':
options[element.id] = element.value.trim();
break;
default:
@ -53,7 +55,7 @@ function saveOptions(e) {
async function restoreOptions() {
function setCurrentChoice(result) {
document.querySelectorAll(".option-input").forEach(element => {
taLog.log("Options restoring " + element.id + " = " + result[element.id]);
taLog.log("Options restoring " + element.id + " = " + (element.id=="chatgpt_api_key" || element.id=="openai_comp_api_key" ? "****************" : result[element.id]));
switch (element.type) {
case 'checkbox':
element.checked = result[element.id] || false;
@ -62,9 +64,10 @@ async function restoreOptions() {
let default_number_value = 0;
if(element.id == 'chatgpt_win_height') default_number_value = prefs_default.chatgpt_win_height;
if(element.id == 'chatgpt_win_width') default_number_value = prefs_default.chatgpt_win_width;
element.value = result[element.id] || default_number_value;
element.value = result[element.id] ?? default_number_value;
break;
case 'text':
case 'password':
let default_text_value = '';
if(element.id == 'default_chatgpt_lang') default_text_value = prefs_default.default_chatgpt_lang;
element.value = result[element.id] || default_text_value;
@ -93,11 +96,13 @@ function showConnectionOptions() {
let chatgpt_web_display = 'table-row';
let chatgpt_api_display = 'none';
let ollama_api_display = 'none';
let openai_comp_api_display = 'none';
let conntype_select = document.getElementById("connection_type");
let parent = conntype_select.parentElement.parentElement.parentElement;
parent.classList.toggle("conntype_chatgpt_web", (conntype_select.value === "chatgpt_web"));
parent.classList.toggle("conntype_chatgpt_web", (conntype_select.value === "chatgpt_web"));
parent.classList.toggle("conntype_chatgpt_api", (conntype_select.value === "chatgpt_api"));
parent.classList.toggle("conntype_ollama_api", (conntype_select.value === "ollama_api"));
parent.classList.toggle("conntype_openai_comp_api", (conntype_select.value === "openai_comp_api"));
if (conntype_select.value === "chatgpt_web") {
chatgpt_web_display = 'table-row';
}else{
@ -113,6 +118,11 @@ function showConnectionOptions() {
}else{
ollama_api_display = 'none';
}
if (conntype_select.value === "openai_comp_api") {
openai_comp_api_display = 'table-row';
}else{
openai_comp_api_display = 'none';
}
document.querySelectorAll(".conntype_chatgpt_web").forEach(element => {
element.style.display = chatgpt_web_display;
});
@ -122,6 +132,9 @@ function showConnectionOptions() {
document.querySelectorAll(".conntype_ollama_api").forEach(element => {
element.style.display = ollama_api_display;
});
document.querySelectorAll(".conntype_openai_comp_api").forEach(element => {
element.style.display = openai_comp_api_display;
});
}
function warn_ChatGPT_APIKeyEmpty() {
@ -133,15 +146,15 @@ function warn_ChatGPT_APIKeyEmpty() {
btnFetchChatGPTModels.disabled = true;
modelChatGPT.disabled = true;
modelChatGPT.selectedIndex = -1;
modelChatGPT.style.border = 'none';
modelChatGPT.style.border = '';
}else{
apiKeyInput.style.border = 'none';
apiKeyInput.style.border = '';
btnFetchChatGPTModels.disabled = false;
modelChatGPT.disabled = false;
if((modelChatGPT.selectedIndex === -1)||(modelChatGPT.value === '')){
modelChatGPT.style.border = '2px solid red';
}else{
modelChatGPT.style.border = 'none';
modelChatGPT.style.border = '';
}
}
}
@ -155,9 +168,9 @@ function warn_Ollama_HostEmpty() {
btnFetchOllamaModels.disabled = true;
modelOllama.disabled = true;
modelOllama.selectedIndex = -1;
modelOllama.style.border = 'none';
modelOllama.style.border = '';
}else{
hostInput.style.border = 'none';
hostInput.style.border = '';
btnFetchOllamaModels.disabled = false;
modelOllama.disabled = false;
if((modelOllama.selectedIndex === -1)||(modelOllama.value === '')){
@ -168,14 +181,34 @@ function warn_Ollama_HostEmpty() {
}
}
function warn_OpenAIComp_HostEmpty() {
let hostInput = document.getElementById('openai_comp_host');
let btnUpdateOpenAICompModels = document.getElementById('btnUpdateOpenAICompModels');
let modelOpenAIComp = document.getElementById('openai_comp_model');
if(hostInput.value === ''){
hostInput.style.border = '2px solid red';
btnUpdateOpenAICompModels.disabled = true;
modelOpenAIComp.disabled = true;
modelOpenAIComp.selectedIndex = -1;
modelOpenAIComp.style.border = '';
}else{
hostInput.style.border = '';
btnUpdateOpenAICompModels.disabled = false;
modelOpenAIComp.disabled = false;
if((modelOpenAIComp.selectedIndex === -1)||(modelOpenAIComp.value === '')){
modelOpenAIComp.style.border = '2px solid red';
}else{
modelOpenAIComp.style.border = '';
}
}
}
document.addEventListener('DOMContentLoaded', async () => {
await restoreOptions();
i18n.updateDocument();
document.querySelectorAll(".option-input").forEach(element => {
element.addEventListener("change", saveOptions);
});
showConnectionOptions();
document.getElementById('btnManagePrompts').addEventListener('click', () => {
// check if the tab is already there
@ -194,10 +227,12 @@ document.addEventListener('DOMContentLoaded', async () => {
conntype_select.addEventListener("change", showConnectionOptions);
conntype_select.addEventListener("change", warn_ChatGPT_APIKeyEmpty);
conntype_select.addEventListener("change", warn_Ollama_HostEmpty);
conntype_select.addEventListener("change", warn_OpenAIComp_HostEmpty);
document.getElementById("chatgpt_api_key").addEventListener("change", warn_ChatGPT_APIKeyEmpty);
document.getElementById("ollama_host").addEventListener("change", warn_Ollama_HostEmpty);
document.getElementById("openai_comp_host").addEventListener("change", warn_OpenAIComp_HostEmpty);
let prefs = await browser.storage.sync.get({chatgpt_model: '', ollama_model: ''});
let prefs = await browser.storage.sync.get({chatgpt_model: '', ollama_model: '', openai_comp_model: ''});
// OpenAI API ChatGPT model fetching
let select_chatgpt_model = document.getElementById('chatgpt_model');
@ -228,7 +263,7 @@ document.addEventListener('DOMContentLoaded', async () => {
}
});
document.getElementById('chatgpt_model_fetch_loading').style.display = 'none';
})
});
warn_ChatGPT_APIKeyEmpty();
});
@ -262,7 +297,7 @@ select_ollama_model.addEventListener("change", warn_Ollama_HostEmpty);
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"));
alert(browser.i18n.getMessage("Ollama_Models_Error_fetching")+": " + browser.i18n.getMessage("API_Models_Error_NoModels"));
return;
}
taLog.log("Ollama models: " + JSON.stringify(data));
@ -283,4 +318,67 @@ select_ollama_model.addEventListener("change", warn_Ollama_HostEmpty);
warn_Ollama_HostEmpty();
});
// OpenAI Comp API Model fetching
let select_openai_comp_model = document.getElementById('openai_comp_model');
const openai_comp_option = document.createElement('option');
openai_comp_option.value = prefs.openai_comp_model;
openai_comp_option.text = prefs.openai_comp_model;
select_openai_comp_model.appendChild(openai_comp_option);
select_openai_comp_model.addEventListener("change", warn_OpenAIComp_HostEmpty);
document.getElementById('btnUpdateOpenAICompModels').addEventListener('click', async () => {
document.getElementById('openai_comp_model_fetch_loading').style.display = 'inline';
let openai_comp = new OpenAIComp(document.getElementById("openai_comp_host").value , null, document.getElementById("openai_comp_api_key").value, true);
openai_comp.fetchModels().then((data) => {
if(!data.ok){
let errorDetail = JSON.parse(data.error);
document.getElementById('openai_comp_model_fetch_loading').style.display = 'none';
console.error("[ThunderAI] " + browser.i18n.getMessage("OpenAIComp_Models_Error_fetching"));
alert(browser.i18n.getMessage("OpenAIComp_Models_Error_fetching")+": " + errorDetail.error.message);
return;
}
taLog.log("OpenAIComp models: " + JSON.stringify(data));
data.response.forEach(model => {
if (!Array.from(select_openai_comp_model.options).some(option => option.value === model.id)) {
const option = document.createElement('option');
option.value = model.id;
option.text = model.id;
select_openai_comp_model.appendChild(option);
}
});
document.getElementById('openai_comp_model_fetch_loading').style.display = 'none';
});
warn_OpenAIComp_HostEmpty();
});
showConnectionOptions();
warn_ChatGPT_APIKeyEmpty();
warn_Ollama_HostEmpty();
warn_OpenAIComp_HostEmpty();
const passwordField_chatgpt_api_key = document.getElementById('chatgpt_api_key');
const toggleIcon_chatgpt_api_key = document.getElementById('toggle_chatgpt_api_key');
const icon_img_chatgpt_api_key = document.getElementById('pwd-icon_chatgpt_api_key');
toggleIcon_chatgpt_api_key.addEventListener('click', () => {
const type = passwordField_chatgpt_api_key.getAttribute('type') === 'password' ? 'text' : 'password';
passwordField_chatgpt_api_key.setAttribute('type', type);
icon_img_chatgpt_api_key.src = type === 'password' ? "../images/pwd-show.png" : "../images/pwd-hide.png";
});
const passwordField_openai_comp_api_key = document.getElementById('openai_comp_api_key');
const toggleIcon_openai_comp_api_key = document.getElementById('toggle_openai_comp_api_key');
const icon_img_openai_comp_api_key = document.getElementById('pwd-icon_openai_comp_api_key');
toggleIcon_openai_comp_api_key.addEventListener('click', () => {
const type = passwordField_openai_comp_api_key.getAttribute('type') === 'password' ? 'text' : 'password';
passwordField_openai_comp_api_key.setAttribute('type', type);
icon_img_openai_comp_api_key.src = type === 'password' ? "../images/pwd-show.png" : "../images/pwd-hide.png";
});
}, { once: true });

View file

@ -7,6 +7,22 @@
<body>
<div id="miczBackPrefs"><a href="mzta-options.html">__MSG_backToOptionsText__</a></div>
<div id="miczRelNotes"><h1>ThunderAI Release Notes</h1>
<h2>Version 2.2.0 - ??/??/2024</h2>
<ul>
<li><i>[ChatGPT Web]</i> Removed the option to force the ChatGPT4 model over ChatGPT3.5, since it was useless now.</li>
<li><i>[ChatGPT Web]</i> Minor internal improvements.</li>
<li><i>[OpenAI Comp API]</i> Added a new integration method to use a local LLM via an API compatible with OpenAI's API specifications [<a href="https://github.com/micz/ThunderAI/issues/126">#126</a>].</li>
<li>Custom Prompts storage space incremented to 5MB, using <pre>storage.local</pre>. Added also the total occupied space at the bottom of the Custom Prompts page [<a href="https://github.com/micz/ThunderAI/issues/129">#129</a>].</li>
<li><i>[ChatGPT Web]</i> Updated the information in the option page.</li>
<li>A new dynamic menu for selecting prompts has been added. In addition to clicking the ThunderAI button, you can now use the CTRL+ALT+A keyboard shortcut [<a href="https://micz.it/thunderbird-addon-thunderai/dynamic-menu/">More info</a>] [<a href="https://github.com/micz/ThunderAI/issues/130">#130</a>].</li>
<li>Added an option to order alphabetically the prompts in the menu.</li>
<li>Allow 0 as the dimensions for the chat window in the options page to prevent issues, such as those encountered with NixOS/Hyprland [<a href="https://github.com/micz/ThunderAI/issues/137">#137</a>].</li>
<li>Is now possible to ask for additional text before sending the prompts also for default prompts [<a href="https://github.com/micz/ThunderAI/issues/135">#135</a>].</li>
<li>Fixed a bug that occurred when importing prompts after an export without closing the Custom Prompts page.</li>
<li>Brazilian Portuguese (pt-br) translation added, thanks to Bruno Pereira de Souza.</li>
<li><i>[Ollama API]</i> Added a link to the <a href="https://micz.it/thunderbird-addon-thunderai/ollama-cors-information/">CORS information page</a>.</li>
<li><i>[ChatGPT API][Ollama API][OpenAI Comp API]</i> Improved handling of streaming responses even in case of broken chunks [<a href="https://github.com/micz/ThunderAI/issues/147">#147</a>].</li>
<li>Fixed a race condition that occurred when opening the chat window under certain circumstances. Thanks to <a href="https://github.com/jobisoft" target="_blank">@jobisoft</a> for helping refine the code, and to <a href="https://github.com/Mikilio" target="_blank">@Mikilio</a> and <a href="https://github.com/mattcaron" target="_blank">@mattcaron</a> for the extensive testing [<a href="https://github.com/micz/ThunderAI/issues/143">#143</a>].</li>
<h2>Version 2.1.5 - 23/09/2024</h2>
<ul>
<li><i>[ChatGPT Web]</i> Working again in Thunderbird 115. Implemented a workaround for <i>Intl.Segmenter</i> [<a href="https://github.com/micz/ThunderAI/issues/139">#139</a>].</li>

10
popup/mzta-loading.svg Normal file
View file

@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" style="shape-rendering: auto; display: block; background: transparent;" width="16" height="16" xmlns:xlink="http://www.w3.org/1999/xlink"><g><circle r="25" fill="#408ee0" cy="50" cx="25">
<animate begin="-0.5s" values="25;75;25" keyTimes="0;0.5;1" dur="1s" repeatCount="indefinite" attributeName="cx"></animate>
</circle>
<circle r="25" fill="#89bff8" cy="50" cx="75">
<animate begin="0s" values="25;75;25" keyTimes="0;0.5;1" dur="1s" repeatCount="indefinite" attributeName="cx"></animate>
</circle>
<circle r="25" fill="#408ee0" cy="50" cx="25">
<animate begin="-0.5s" values="25;75;25" keyTimes="0;0.5;1" dur="1s" repeatCount="indefinite" attributeName="cx"></animate>
<animate repeatCount="indefinite" dur="1s" keyTimes="0;0.499;0.5;1" calcMode="discrete" values="0;0;1;1" attributeName="fill-opacity"></animate>
</circle><g></g></g><!-- [ldio] generated by https://loading.io --></svg>

After

Width:  |  Height:  |  Size: 990 B

95
popup/mzta-popup.css Normal file
View file

@ -0,0 +1,95 @@
body {
background-color: #FAFAFA;
color:black;
font-size: 14px;
padding: 1px;
margin:3px;
}
#mzta_search_banner{
background-color: #FAFAFA;
color:black;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
position: relative;
height: fit-content;
width: 15em;
max-height: 23em;
min-height: 1em;
padding: 0;
}
#mzta_search_input{
width: 12em;
position: fixed;
top:0;
left:0;
width: -moz-available;
z-index: 1001;
font-size: 13px;
}
#_spacer_div{
width: 100%;
height: 2.2em;
}
#mzta_autocomplete-items {
border: none;
z-index: 1000;
background-color: #FAFAFA;
width: 100%;
overflow-y: auto;
padding: 0;
overflow-x: hidden;
}
.mzta_autocomplete-item {
padding: 4px;
cursor: pointer;
}
.mzta_autocomplete-item:hover {
background-color: #e9e9e9;
}
.mzta_autocomplete-item-active {
background-color: #e9e9e9;
}
@media (prefers-color-scheme: dark) {
body{
background-color: #2c2c2c;
color: #ffffff;
}
#mzta_search_input {
background-color: #2c2c2c;
color: #ffffff;
}
#mzta_search_banner{
background-color: #2c2c2c;
color:#ffffff;
}
#mzta_autocomplete-items {
background-color: #2c2c2c;
}
.mzta_autocomplete-item {
color: #ffffff;
}
.mzta_autocomplete-item:hover {
background-color: #444444;
}
.mzta_autocomplete-item-active {
background-color: #444444;
}
}

20
popup/mzta-popup.html Normal file
View file

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<link rel="stylesheet" href="mzta-popup.css">
<title>ThunderAI Prompts</title>
</head>
<body>
<div id="mzta_search_banner">
<div id="_spacer_div">&nbsp;</div>
<input type="text" id="mzta_search_input" placeholder="__MSG_SearchPrompt__">
<div id="mzta_autocomplete-items-loading"><img src="mzta-loading.svg" title="__MSG_Loading__"></div>
<div id="mzta_autocomplete-items" style="display: none;"></div>
</div>
<script src="mzta-popup.js" type="module"></script>
<script src="../js/mzta-i18n.js"></script>
</body>
</html>

252
popup/mzta-popup.js Normal file
View file

@ -0,0 +1,252 @@
/*
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
* Copyright (C) 2024 Mic (m@micz.it)
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { taStore } from "../js/mzta-store.js";
import { taLogger } from "../js/mzta-logger.js";
let menuSendImmediately = false;
let taLog = console;
document.addEventListener('DOMContentLoaded', async () => {
let prefs = await browser.storage.sync.get({do_debug: false, dynamic_menu_force_enter: false});
taLog = new taLogger("mzta-popup",prefs.do_debug);
let reponse = await browser.runtime.sendMessage({command: "popup_menu_ready"});
taLog.log("Preparing data to load the popup menu: " + JSON.stringify(reponse));
let tabId = reponse.lastShortcutTabId;
let tabType = reponse.lastShortcutTabType;
let filtering = reponse.lastShortcutFiltering;
let _prompts_data = reponse.lastShortcutPromptsData;
taLog.log("_prompts_data: " + JSON.stringify(_prompts_data));
let active_prompts = filterPromptsForTab(_prompts_data, filtering);
taLog.log("active_prompts: " + JSON.stringify(active_prompts));
menuSendImmediately = prefs.dynamic_menu_force_enter;
searchPrompt(active_prompts, tabId, tabType);
i18n.updateDocument();
}, { once: true });
async function searchPrompt(allPrompts, tabId, tabType){
taLog.log("tabType: " + tabType);
let prefs_order = await browser.storage.sync.get({dynamic_menu_order_alphabet: true});
if(prefs_order.dynamic_menu_order_alphabet){
allPrompts.sort((a, b) => a.label.localeCompare(b.label));
}
// console.log(">>>>>>>>> allPrompts: " + JSON.stringify(allPrompts));
let input = document.getElementById('mzta_search_input');
let autocompleteList = document.getElementById('mzta_autocomplete-items');
let autocompleteListLoading = document.getElementById('mzta_autocomplete-items-loading');
let _spacer_div = document.getElementById('_spacer_div');
let banner = document.getElementById('mzta_search_banner');
// Initialize variables to track focus and selection
let currentFocus = -1; // Tracks the currently highlighted item
let selectedId = null; // Tracks the ID of the selected item
// Function to filter and display autocomplete suggestions
input.addEventListener('input', function() {
const query = this.value.trim().toLowerCase();
// console.log(">>>>>>>>>>>> query: " + query);
autocompleteList.innerHTML = ''; // Clear previous suggestions
currentFocus = -1; // Reset the highlighted index
selectedId = null; // Reset the selected ID since input has changed
// Uncomment the following lines if you want to hide suggestions when input is empty
/*
if (query === '') {
autocompleteList.style.display = 'none';
return;
}
*/
// Filter data based on the query
const filteredData = allPrompts.filter(item =>
item.label.toLowerCase().includes(query)
);
taLog.log("filteredData: " + JSON.stringify(filteredData));
if (filteredData.length === 0) {
autocompleteList.style.display = 'none';
_spacer_div.style.display = 'none';
return;
}
// Prepend numbers to the first 10 items
Array.from(filteredData).slice(0, 10).forEach((item, index) => {
const number = (index < 9) ? (index + 1).toString() : '0';
// Check if the number is already prepended to avoid duplication
if (!item.numberPrepended) {
item.label = `${number}. ${item.label}`;
item.numberPrepended = 'true'; // Mark as prepended
}
});
// console.log(">>>>>>>>>>>>> filteredData: " + JSON.stringify(filteredData));
// Create a div for each filtered result
filteredData.forEach(item => {
const itemDiv = document.createElement('div');
itemDiv.classList.add('mzta_autocomplete-item');
itemDiv.textContent = item.label;
itemDiv.setAttribute('data-id', item.id);
// Add a mousedown event to select the item
itemDiv.addEventListener('mousedown', function(e) { // Use mousedown instead of click
e.preventDefault(); // Prevents the input from losing focus
input.value = item.label;
selectedId = item.id; // Store the selected item's ID
taLog.log('mousedown selectedId:', selectedId);
autocompleteList.style.display = 'none';
_spacer_div.style.display = 'none';
sendPrompt(selectedId, tabId);
});
// Add a select_prompt event to select the item
itemDiv.addEventListener('select_prompt', function(e) { // Use select_prompt instead of click
e.preventDefault(); // Prevents the input from losing focus
input.value = item.label;
selectedId = item.id; // Store the selected item's ID
// console.log('>>>>>>>>>>>>> select_prompt selectedId:', selectedId);
autocompleteList.style.display = 'none';
_spacer_div.style.display = 'none';
if(menuSendImmediately){
sendPrompt(selectedId, tabId);
}
});
autocompleteList.appendChild(itemDiv);
});
autocompleteListLoading.style.display = 'none';
autocompleteList.style.display = 'block';
_spacer_div.style.display = 'block';
});
// Add a keydown event listener to handle arrow navigation and selection
input.addEventListener('keydown', function (e) {
const items = autocompleteList.getElementsByClassName('mzta_autocomplete-item');
if ((autocompleteList.style.display === 'none' || items.length === 0)
&& (e.key !== 'Enter')
&& !['1','2','3','4','5','6','7','8','9','0'].includes(e.key))
{
return; // Do nothing if the autocomplete list is not visible
}
// Handle number key presses (1-9,0) to select the corresponding item directly
if (['1','2','3','4','5','6','7','8','9','0'].includes(e.key)) {
// Map '1' to index 0, '2' to 1, ..., '9' to 8, '0' to 9
const numIndex = (e.key === '0') ? 9 : parseInt(e.key, 10) - 1;
if (items[numIndex]) {
e.preventDefault(); // Prevent any default behavior
// Dispatch a select_prompt event to simulate a click/select action
items[numIndex].dispatchEvent(new Event('select_prompt'));
return; // Exit after handling the number key
}
}
if (e.key === 'ArrowDown') {
// Navigate down the list
currentFocus++;
if (currentFocus >= items.length) currentFocus = 0; // Wrap to the first item
addActive(items);
e.preventDefault(); // Prevent cursor from moving to the end
} else if (e.key === 'ArrowUp') {
// Navigate up the list
currentFocus--;
if (currentFocus < 0) currentFocus = items.length - 1; // Wrap to the last item
addActive(items);
e.preventDefault(); // Prevent cursor from moving to the start
} else if (e.key === 'Enter') {
// console.log(">>>>>>>>>>>>>> keydown == enter selectedId: " + selectedId);
if (selectedId) {
// If an item is already selected, call sendPrompt with the selected ID
e.preventDefault();
sendPrompt(selectedId, tabId); // Call your sendPrompt function
//banner.remove(); // Remove the banner after sending the prompt
} else {
// If no item is selected yet, select the highlighted item
// Select the highlighted item, or the first item if none is highlighted
e.preventDefault(); // Prevent form submission if inside a form
if (currentFocus > -1) {
if (items[currentFocus]) {
items[currentFocus].dispatchEvent(new Event('select_prompt')); // Trigger the select_prompt event
}
} else if (items.length > 0) {
// If no item is highlighted, select the first item
items[0].dispatchEvent(new Event('select_prompt'));
}
}
}
});
// Function to add the "active" class to the current item
function addActive(items) {
removeActive(items); // Remove the "active" class from all items
if (currentFocus >= items.length) currentFocus = 0;
if (currentFocus < 0) currentFocus = items.length - 1;
items[currentFocus].classList.add('mzta_autocomplete-item-active'); // Add "active" class to the current item
// Ensure the active item is visible within the scrollable list
items[currentFocus].scrollIntoView({
behavior: 'auto', // You can change to 'smooth' if you prefer smooth scrolling
block: 'nearest', // Align the item to the nearest edge of the visible area
});
}
// Function to remove the "active" class from all items
function removeActive(items) {
for (let i = 0; i < items.length; i++) {
items[i].classList.remove('mzta_autocomplete-item-active');
}
}
document.body.insertBefore(banner, document.body.firstChild);
setTimeout(() => {
input.dispatchEvent(new InputEvent('input', { bubbles: true }));
input.focus();
}, 100);
}
function sendPrompt(prompt_id, tabId){
taLog.log("sendPrompt: " + prompt_id);
browser.runtime.sendMessage({command: "shortcut_do_prompt", tabId: tabId, promptId: prompt_id});
}
function filterPromptsForTab(prompts_data, filtering){
// If filtering is 0, return the original array without any filters (btw it should not happen)
if (filtering === 0) {
return prompts_data;
}
// Define the types to include based on the value of filtering
let allowedTypes;
if (filtering === 1) {
allowedTypes = ["0", "1"];
} else if (filtering === 2) {
allowedTypes = ["0", "2"];
} else {
// If filtering has an unexpected value, return the original data
return prompts_data;
}
// Filter the array based on the allowed types
return prompts_data.filter(prompt => allowedTypes.includes(prompt.type));
}