The red border in the Custom Prompts configuration page that highlights a needed prompt configuration that is not selected is now removed when the corresponding placeholder is removed [#201].
+
Minor bugs fixed.
...
Version 2.3.4 - 06/12/2024
diff --git a/README.md b/README.md
index 97672745..fc06157d 100644
--- a/README.md
+++ b/README.md
@@ -6,11 +6,9 @@ It enables users to analyse, write, correct, and optimize their emails, facilita
ThunderAI is a tool for anyone looking to improve their email quality, both in content and grammar, making the writing process quicker and more intuitive.
-You can also define, export and import your own **custom prompts**!
-[Find out how!](https://micz.it/thunderbird-addon-thunderai/custom-prompts/)
+You can also define, export and import your own **[custom prompts](https://micz.it/thunderbird-addon-thunderai/custom-prompts/)**!
-In any custom prompt you can use additional **data placeholders**!
-[Find out how!](https://micz.it/thunderbird-addon-thunderai/data-placeholders/)
+In any custom prompt you can use additional **[data placeholders](https://micz.it/thunderbird-addon-thunderai/data-placeholders/)**!
@@ -19,16 +17,16 @@ In any custom prompt you can use additional **data placeholders**!
> [!TIP]
> **Using ChatGPT**
>
-> There is no need for an API key and is possibile to use this extension even with a free account, using the web interface!
+> There is no need for an API key and is possibile to use this extension even with a free account, using the **ChatGPT web interface**!
>
-> Starting from version 2.0.0, if you want to connect with the OpenAI API integration, now you can use an API Key!
+> If you want to connect with the OpenAI API integration, instead, you can use an **API Key**!
> [!TIP]
> **Using Ollama**
>
-> From version 2.1.0 is possible to use a local Ollama server!
+> It's also possible to use a local Ollama server!
>
> Just remember to add `OLLAMA_ORIGINS = moz-extension://*` to the Ollama server environment variables.
>
@@ -39,7 +37,8 @@ In any custom prompt you can use additional **data placeholders**!
> [!TIP]
> **Using an OpenAI Compatible API**
>
-> From version 2.2.0 is possible to use a local OpenAI Compatible API server, like LM Studio!
+> You can also use a local OpenAI Compatible API server, like LM Studio!
+>
> There is also an option to remove the "v1" segment from the API url, if needed.
@@ -75,16 +74,10 @@ Are you using this addon in your Thunderbird?
- French (fr): Generated automatically
- German (de): Generated automatically
- Italian (it): [Mic](https://github.com/micz/)
+- Polski (pl): [neexpl](https://github.com/neexpl)
- Português Brasileiro (pt-br): Bruno Pereira de Souza
-
-
-### Miscellaneous
-- chatgpt.js for providing methods to interact with the ChatGPT web frontend
-- Aify for inspiration
-- Julian Harris for his project chatgpt-frontend-nobuild, that has been used as a starting point for the API Web Interface
-
@@ -93,3 +86,12 @@ Are you using this addon in your Thunderbird?
- loading.io 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
+
+
+
+
+
+### Miscellaneous
+- chatgpt.js for providing methods to interact with the ChatGPT web frontend
+- Julian Harris for his project chatgpt-frontend-nobuild, that has been used as a starting point for the API Web Interface
+- Aify for inspiration
diff --git a/_locales/de/messages.json b/_locales/de/messages.json
index 99968a32..6e0e6292 100644
--- a/_locales/de/messages.json
+++ b/_locales/de/messages.json
@@ -680,7 +680,7 @@
"description": ""
},
"prefs_OptionText_btnManagePrompts_infoline": {
- "message": "Sie können jetzt zusätzliche Datenplatzhalter verwenden!",
+ "message": "Sie können zusätzliche Datenplatzhalter verwenden.",
"description": ""
},
"prefs_OptionText_openai_comp_use_v1": {
diff --git a/_locales/en/messages.json b/_locales/en/messages.json
index dfd37c48..6529c09f 100644
--- a/_locales/en/messages.json
+++ b/_locales/en/messages.json
@@ -680,7 +680,11 @@
"description": ""
},
"prefs_OptionText_btnManagePrompts_infoline": {
- "message": "You can now use additional data placeholders!",
+ "message": "You can use additional data placeholders.",
+ "description": ""
+ },
+ "prefs_OptionText_btnManagePrompts_infoline2": {
+ "message": "You can change the prompt as you wish, but the response received from the AI must be a comma-separated list of tags!",
"description": ""
},
"prefs_OptionText_openai_comp_use_v1" : {
@@ -742,5 +746,133 @@
"prompt_this_full_text": {
"message": "Reply with only the needed text and with no extra comments or other text.",
"description": ""
+ },
+ "prefs_OptionText_add_tags": {
+ "message": "Add tags to emails",
+ "description": ""
+ },
+ "prefs_OptionText_add_tags_Info": {
+ "message": "If checked, a menu item will be added to the menu to add tags to emails.",
+ "description": ""
+ },
+ "prompt_add_tags": {
+ "message": "Add tags to this email",
+ "description": ""
+ },
+ "prompt_add_tags_full_text": {
+ "message": "Analyze the following email text and generate a comma-separated list of tags that summarize its content. Use themes, key topics, and relevant descriptors as tags. Ensure the tags are concise and relevant to the email's content.\nEmail text: {%mail_text_body%}\nConsider the following details for context:\n- Sender: {%author%}\n- Recipients: {%recipients%}\n- CC list: {%cc_list%}\n- Email subject: {%mail_subject%}\nPlease base your tags on the email's text and context, ignoring unnecessary information or trivial details.",
+ "description": ""
+ },
+ "placeholder_mail_tags": {
+ "message": "Mail tags",
+ "description": ""
+ },
+ "placeholder_full_tags_list": {
+ "message": "Existing tags",
+ "description": ""
+ },
+ "prefs_OptionText_add_tags_maxnum": {
+ "message": "Maximum number of tags",
+ "description": ""
+ },
+ "prefs_OptionText_add_tags_maxnum_Info": {
+ "message": "The maximum number of tags proposed by the AI. Set to 0 if you don't want to limit the number of tags.",
+ "description": ""
+ },
+ "prompt_add_tags_maxnum": {
+ "message": "Limit the number of tags to",
+ "description": ""
+ },
+ "prefs_OptionText_add_tags_hide_exclusions": {
+ "message": "Hide excluded tags",
+ "description": ""
+ },
+ "prefs_OptionText_add_tags_hide_exclusions_Info": {
+ "message": "If checked, the tags that are present in the exclusion list will be hidden in the confirm dialog.",
+ "description": ""
+ },
+ "prefs_OptionText_btnManageTagsInfo": {
+ "message": "Manage tags settings",
+ "description": ""
+ },
+ "AddTags_PageTitle": {
+ "message": "Manage Tags Settings",
+ "description": ""
+ },
+ "AddTags_info_default": {
+ "message": "In this page you can modify the default prompt used to add tags to emails and manage the exclusion list.",
+ "description": ""
+ },
+ "AddTags_prompt_text_title": {
+ "message": "Current prompt text",
+ "description": ""
+ },
+ "AddTags_excl_list_title": {
+ "message": "Exclusion list",
+ "description": ""
+ },
+ "AddTags_excl_list_infoline": {
+ "message": "This is a list of tags that are not allowed to be added to emails.",
+ "description": ""
+ },
+ "save": {
+ "message": "Save",
+ "description": ""
+ },
+ "addtags_info_limit_num": {
+ "message": "This statement will be added at the end of the prompt:",
+ "description": ""
+ },
+ "reset_default": {
+ "message": "Reset to default",
+ "description": ""
+ },
+ "addtags_excl_list_infoline2": {
+ "message": "Add a word per line, or separated by a comma.",
+ "description": ""
+ },
+ "addtags_dialog_title": {
+ "message": "Add tags to the email",
+ "description": ""
+ },
+ "addtags_exclude_tag": {
+ "message": "Exclude tag",
+ "description": ""
+ },
+ "addtags_no_tags_received": {
+ "message": "No tags received from the AI.",
+ "description": ""
+ },
+ "addtags_no_valid_tags": {
+ "message": "No valid tags found, after filtering with the exclusion list.",
+ "description": ""
+ },
+ "thunderai_error_title": {
+ "message": "ThunderAI Error",
+ "description": ""
+ },
+ "thunderai_warning_title": {
+ "message": "ThunderAI Warning",
+ "description": ""
+ },
+ "prefs_OptionText_add_tags_first_uppercase": {
+ "message": "First letter uppercase",
+ "description": ""
+ },
+ "prefs_OptionText_add_tags_first_uppercase_Info": {
+ "message": "If checked, the tags label will be set to lowercase letters with only the first letter uppercase.",
+ "description": ""
+ },
+ "AddTags_prompt_prefs_title": {
+ "message": "Add Tags Options",
+ "description": ""
+ },
+ "prefs_SurveyLinkText": {
+ "message": "Share your feedback and help us improve ThunderAI!",
+ "description": ""
+ },
+ "prefs_SurveyLinkText2": {
+ "message": "Click here, it only takes a minute!",
+ "description": ""
}
}
\ No newline at end of file
diff --git a/_locales/fr/messages.json b/_locales/fr/messages.json
index 1f218da7..da57a50d 100644
--- a/_locales/fr/messages.json
+++ b/_locales/fr/messages.json
@@ -680,7 +680,7 @@
"description": ""
},
"prefs_OptionText_btnManagePrompts_infoline": {
- "message": "Vous pouvez maintenant utiliser des espaces réservés de données supplémentaires !",
+ "message": "Vous pouvez utiliser des espaces réservés supplémentaires pour les données.",
"description": ""
},
"prefs_OptionText_openai_comp_use_v1": {
diff --git a/_locales/it/messages.json b/_locales/it/messages.json
index 37853b8d..32d08e4f 100644
--- a/_locales/it/messages.json
+++ b/_locales/it/messages.json
@@ -680,7 +680,7 @@
"description": ""
},
"prefs_OptionText_btnManagePrompts_infoline": {
- "message": "Ora puoi utilizzare segnaposti per i dati aggiuntivi!",
+ "message": "Puoi utilizzare segnaposti per i dati aggiuntivi.",
"description": ""
},
"prefs_OptionText_openai_comp_use_v1": {
diff --git a/_locales/pl/messages.json b/_locales/pl/messages.json
new file mode 100644
index 00000000..da72e70c
--- /dev/null
+++ b/_locales/pl/messages.json
@@ -0,0 +1,702 @@
+{
+ "extensionDescription": {
+ "message": "Używaj ChatGPT lub Ollama do ulepszania swoich e-maili!",
+ "description": "Description of the extension."
+ },
+ "menu_title": {
+ "message": "AI",
+ "description": ""
+ },
+ "prompt_lang": {
+ "message": "Odpowiedz w ",
+ "description": ""
+ },
+ "prompt_reply": {
+ "message": "Odpowiedz na ten e-mail",
+ "description": ""
+ },
+ "prompt_reply_advanced": {
+ "message": "Odpowiedz na ten wątek",
+ "description": ""
+ },
+ "prompt_rewrite_polite": {
+ "message": "Przepisz uprzejmie",
+ "description": ""
+ },
+ "prompt_rewrite_formal": {
+ "message": "Przepisz formalnie",
+ "description": ""
+ },
+ "prompt_classify": {
+ "message": "Klasyfikuj",
+ "description": ""
+ },
+ "prompt_summarize_this": {
+ "message": "Podsumuj to",
+ "description": ""
+ },
+ "prompt_translate_this": {
+ "message": "Przetłumacz to",
+ "description": ""
+ },
+ "prompt_this": {
+ "message": "Zapytaj o to",
+ "description": ""
+ },
+ "prompt_selection_needed": {
+ "message": "Aby kontynuować, musisz zaznaczyć tekst!",
+ "description": ""
+ },
+ "customPrompts_managePrompts": {
+ "message": "Zarządzaj poleceniami",
+ "description": ""
+ },
+ "customPrompts_managePrompts_help": {
+ "message": "Więcej informacji",
+ "description": ""
+ },
+ "customPrompts_managePrompts_info_default": {
+ "message": "Domyślne polecenia nie są edytowalne. Możesz je wyłączyć, skopiować tekst polecenia i wkleić go do nowego, aby stworzyć zmodyfikowaną wersję.",
+ "description": ""
+ },
+ "customPrompts_managePrompts_info_default_2": {
+ "message": "Możesz importować i eksportować polecenia. Istniejące polecenia o tym samym ID zostaną nadpisane. Polecenia z nowymi ID zostaną dodane.",
+ "description": ""
+ },
+ "customPrompts_managePrompts_info_default_3": {
+ "message": "Jeśli wszystko jest poprawne, po imporcie kliknij przycisk 'Zapisz wszystko'.",
+ "description": ""
+ },
+ "customPrompts_start_saving": {
+ "message": "Zapisywanie poleceń...",
+ "description": ""
+ },
+ "customPrompts_reindexing_list": {
+ "message": "Ponowne indeksowanie listy...",
+ "description": ""
+ },
+ "customPrompts_filtering_prompts": {
+ "message": "Filtrowanie polece��...",
+ "description": ""
+ },
+ "customPrompts_saving_default_prompts": {
+ "message": "Zapisywanie domyślnych poleceń...",
+ "description": ""
+ },
+ "customPrompts_saving_custom_prompts": {
+ "message": "Zapisywanie własnych poleceń...",
+ "description": ""
+ },
+ "customPrompts_reloading_menus": {
+ "message": "Odświeżanie menu...",
+ "description": ""
+ },
+ "customPrompts_saved": {
+ "message": "Polecenia zapisane!",
+ "description": ""
+ },
+ "customPrompts_form_label_ID": {
+ "message": "ID",
+ "description": ""
+ },
+ "customPrompts_form_label_ID_rules": {
+ "message": "Musi być unikalne, pisane małymi literami i bez spacji",
+ "description": ""
+ },
+ "customPrompts_form_label_Name": {
+ "message": "Nazwa",
+ "description": ""
+ },
+ "customPrompts_form_label_Text": {
+ "message": "Tekst polecenia",
+ "description": ""
+ },
+ "customPrompts_form_label_Action": {
+ "message": "Akcja",
+ "description": ""
+ },
+ "customPrompts_form_label_need_selected": {
+ "message": "Wymagane zaznaczenie tekstu",
+ "description": ""
+ },
+ "customPrompts_form_label_need_signature": {
+ "message": "Zawsze dodawaj podpis",
+ "description": ""
+ },
+ "customPrompts_form_label_need_custom_text": {
+ "message": "Poproś o dodatkowy tekst",
+ "description": ""
+ },
+ "customPrompts_form_label_enabled": {
+ "message": "Włączone",
+ "description": ""
+ },
+ "customPrompts_form_required_fields": {
+ "message": "Pola wymagane",
+ "description": ""
+ },
+ "customPrompts_btnEdit": {
+ "message": "Edytuj",
+ "description": ""
+ },
+ "customPrompts_btnCancel": {
+ "message": "Anuluj",
+ "description": ""
+ },
+ "customPrompts_btnOK": {
+ "message": "OK",
+ "description": ""
+ },
+ "customPrompts_btnDelete": {
+ "message": "Usuń",
+ "description": ""
+ },
+ "customPrompts_btnDelete_confirmText": {
+ "message": "Czy na pewno chcesz usunąć ten element?",
+ "description": ""
+ },
+ "customPrompts_unsaved_changes": {
+ "message": "Istnieją niezapisane zmiany!",
+ "description": ""
+ },
+ "customPrompts_btnSaveAll": {
+ "message": "Zapisz wszystko",
+ "description": ""
+ },
+ "customPrompts_save_button": {
+ "message": "Zapisz",
+ "description": ""
+ },
+ "customPrompts_btnNew": {
+ "message": "Dodaj nowe",
+ "description": ""
+ },
+ "customPrompts_btnAddNewCommit": {
+ "message": "Dodaj polecenie",
+ "description": ""
+ },
+ "customPrompts_add_to_menu": {
+ "message": "Dodaj do menu",
+ "description": ""
+ },
+ "customPrompts_add_to_menu_always": {
+ "message": "Zawsze",
+ "description": ""
+ },
+ "customPrompts_add_to_menu_reading": {
+ "message": "Podczas czytania e-maila",
+ "description": ""
+ },
+ "customPrompts_add_to_menu_composing": {
+ "message": "Podczas pisania e-maila",
+ "description": ""
+ },
+ "customPrompts_close_button": {
+ "message": "Przycisk zamknięcia",
+ "description": ""
+ },
+ "customPrompts_do_reply": {
+ "message": "Odpowiedz",
+ "description": ""
+ },
+ "customPrompts_substitute_text": {
+ "message": "Zastąp tekst",
+ "description": ""
+ },
+ "chatgpt_win_working": {
+ "message": "Praca w toku...",
+ "description": ""
+ },
+ "chatgpt_win_job_completed": {
+ "message": "Zakończono!",
+ "description": ""
+ },
+ "chatgpt_win_job_completed_select": {
+ "message": "Wybierz tekst, którego chcesz użyć i kliknij przycisk.",
+ "description": ""
+ },
+ "chatgpt_win_get_answer": {
+ "message": "Użyj wybranej odpowiedzi",
+ "description": ""
+ },
+ "chatgpt_win_close": {
+ "message": "Zamknij",
+ "description": ""
+ },
+ "chatgpt_textarea_not_found_error": {
+ "message": "Wygląda na to, że strona ChatGPT ładuje się zbyt długo. Jeśli ładowanie zakończy się, kliknij przycisk po prawej stronie. Jeśli problem będzie się powtarzał, sprawdź status usługi.",
+ "description": ""
+ },
+ "chatgpt_btn_retry": {
+ "message": "Spróbuj ponownie",
+ "description": ""
+ },
+ "chatgpt_sendbutton_not_found_error": {
+ "message": "Kliknij przycisk wysyłania, aby przesłać zapytanie.",
+ "description": ""
+ },
+ "chatgpt_user_not_logged_in": {
+ "message": "Nie jesteś zalogowany do ChatGPT. Zaloguj się swoimi danymi, zamknij okno ChatGPT, a następnie powtórz próbę. Po autoryzacji pozostaniesz zalogowany.",
+ "description": ""
+ },
+ "chatgpt_win_model_warning": {
+ "message": "Ustawiłeś opcję używania określonego modelu, ale wygląda na to, że nie ładuje się on poprawnie. Sprawdź wartość i spróbuj ponownie. Na razie możesz nacisnąć niebieski przycisk, aby kontynuować.",
+ "description": ""
+ },
+ "chatgpt_win_custom_text": {
+ "message": "Wstaw tutaj dodatkowy tekst dla zapytania.",
+ "description": ""
+ },
+ "chatgpt_win_send": {
+ "message": "Wyślij",
+ "description": ""
+ },
+ "chatgpt_use_gpt35": {
+ "message": "Użyj GPT3.5",
+ "description": ""
+ },
+ "chatgpt_force_completion": {
+ "message": "wymuś zakończenie",
+ "description": ""
+ },
+ "chatgpt_force_completion_title": {
+ "message": "Kliknij tutaj, aby wyświetlić przycisk 'Użyj ostatniej odpowiedzi', jeśli ChatGPT zakończył pracę, ale przycisk się nie pojawił.",
+ "description": ""
+ },
+ "msg_prompt_too_long": {
+ "message": "Podany tekst jest zbyt długi. Musisz go skrócić.",
+ "description": ""
+ },
+ "prefs_OptionText_release_notes": {
+ "message": "Informacje o wydaniu",
+ "description": ""
+ },
+ "prefs_status_page": {
+ "message": "status usługi",
+ "description": ""
+ },
+ "prefs_OptionText_chatgpt_win_text": {
+ "message": "Wymiary okna ChatGPT",
+ "description": ""
+ },
+ "prefs_OptionText_chatgpt_win_height": {
+ "message": "Wysokość",
+ "description": ""
+ },
+ "prefs_OptionText_chatgpt_win_width": {
+ "message": "Szerokość",
+ "description": ""
+ },
+ "prefs_OptionText_default_sign_name": {
+ "message": "Domyślna nazwa podpisu",
+ "description": ""
+ },
+ "prefs_OptionText_default_chatgpt_lang": {
+ "message": "Domyślny język odpowiedzi",
+ "description": ""
+ },
+ "prefs_OptionText_reply_all": {
+ "message": "Odpowiedz wszystkim",
+ "description": ""
+ },
+ "prefs_OptionText_reply_sender": {
+ "message": "Odpowiedz nadawcy",
+ "description": ""
+ },
+ "prefs_OptionText_reply_type": {
+ "message": "Typ odpowiedzi",
+ "description": ""
+ },
+ "prefs_OptionText_btnManagePrompts": {
+ "message": "Zarządzaj poleceniami",
+ "description": ""
+ },
+ "prefsInfoTitle": {
+ "message": "Ważne informacje",
+ "description": ""
+ },
+ "prefsInfoDesc_1": {
+ "message": "Może się zdarzyć, że interfejs ChatGPT zmieni się w sposób, który zakłóci działanie dodatku. Sprawdź stronę \"Status usługi\" połączoną na dole tej strony. Pamiętaj też, że przy pierwszym użyciu ThunderAI musisz zalogować się do ChatGPT.",
+ "description": ""
+ },
+ "prefsInfoDesc_2": {
+ "message": "Aby korzystać z API ChatGPT, potrzebujesz klucza API OpenAI ChatGPT i musisz wybrać model.",
+ "description": ""
+ },
+ "prefsInfoDesc_3": {
+ "message": "Aby korzystać z integracji z Ollama, musisz skonfigurować lokalny serwer Ollama. Po uruchomieniu serwera wprowadź jego adres w odpowiednim polu w aplikacji. Aby zapewnić prawidłową komunikację między ThunderAI a serwerem Ollama pamiętaj o ustawieniu OLLAMA_ORIGINS=moz-extension://*.",
+ "description": ""
+ },
+ "prefsInfoDesc_4": {
+ "message": "Aby korzystać z tej integracji, musisz skonfigurować lokalny serwer kompatybilny z API OpenAI, jak LM Studio. Po uruchomieniu serwera wprowadź jego adres w odpowiednim polu w aplikacji. Aby zapewnić prawidłową komunikację między ThunderAI a serwerem lokalnym pamiętaj o prawidłowym ustawieniu CORS.",
+ "description": ""
+ },
+ "prefsInfoDesc_5": {
+ "message": "Pamiętaj, że możesz otworzyć menu ThunderAI używając skrótu klawiszowego CTRL+ALT+A.",
+ "description": ""
+ },
+ "prefsInfoDesc_6": {
+ "message": "Możesz zmienić skrót klikając ikonę koła zębatego w prawym górnym rogu tej strony i wybierając \"Zarządzaj skrótami rozszerzenia\".",
+ "description": ""
+ },
+ "prefsDonation_1": {
+ "message": "Czy podoba Ci się ten dodatek?",
+ "description": ""
+ },
+ "prefsDonation_2": {
+ "message": "Rozważ wsparcie poprzez darowiznę!",
+ "description": ""
+ },
+ "backToOptionsText": {
+ "message": "Opcje",
+ "description": ""
+ },
+ "TranslateText": {
+ "message": "Czy chcesz pomóc w tłumaczeniu tego dodatku?",
+ "description": ""
+ },
+ "TranslateLink": {
+ "message": "Dowiedz się jak!",
+ "description": ""
+ },
+ "customPrompts_ExportAll": {
+ "message": "Eksportuj wszystkie polecenia",
+ "description": ""
+ },
+ "customPrompts_Import": {
+ "message": "Importuj nowe polecenia",
+ "description": ""
+ },
+ "importPrompts_confirmText": {
+ "message": "Zamierzasz zaimportować nowe polecenia.",
+ "description": ""
+ },
+ "customPrompts_start_import": {
+ "message": "Rozpoczynanie importu własnych poleceń...",
+ "description": ""
+ },
+ "customPrompts_import_completed": {
+ "message": "Import własnych poleceń zakończony! Musisz kliknąć przycisk 'Zapisz wszystko', aby zapisać zmiany.",
+ "description": ""
+ },
+ "importPrompts_invalidFile": {
+ "message": "Plik, który próbujesz zaimportować, nie jest prawidłowym plikiem poleceń.",
+ "description": ""
+ },
+ "importPrompts_invalidPrompts": {
+ "message": "Plik, który próbujesz zaimportować, nie zawiera żadnych prawidłowych poleceń.",
+ "description": ""
+ },
+ "currently_used_prompt": {
+ "message": "Aktualnie używana nazwa polecenia",
+ "description": ""
+ },
+ "customprompts_form_label_define_response_lang": {
+ "message": "Określ język odpowiedzi w poleceniu",
+ "description": ""
+ },
+ "prefs_Connection_type": {
+ "message": "Typ połączenia",
+ "description": ""
+ },
+ "prefs_Connection_type_ChatGPT_Web": {
+ "message": "Interfejs webowy ChatGPT",
+ "description": ""
+ },
+ "prefs_Connection_type_ChatGPT_API": {
+ "message": "API ChatGPT OpenAI",
+ "description": ""
+ },
+ "prefs_ChatGPT_API_Key": {
+ "message": "Klucz API ChatGPT",
+ "description": ""
+ },
+ "ChatGPT_Models": {
+ "message": "Modele ChatGPT",
+ "description": ""
+ },
+ "ChatGPT_Models_Fetch": {
+ "message": "Zaktualizuj listę modeli ChatGPT",
+ "description": ""
+ },
+ "ChatGPT_Models_Error_fetching": {
+ "message": "Błąd podczas pobierania modeli ChatGPT",
+ "description": ""
+ },
+ "Loading": {
+ "message": "Ładowanie...",
+ "description": ""
+ },
+ "error": {
+ "message": "Błąd ThunderAI!",
+ "description": ""
+ },
+ "chatgpt_empty_apikey": {
+ "message": "Nie dodałeś klucza API dla ChatGPT API. Proszę wprowadź go na stronie opcji.",
+ "description": ""
+ },
+ "chatgpt_empty_model": {
+ "message": "Nie wybrałeś modelu dla ChatGPT API. Proszę wybierz jeden na stronie opcji.",
+ "description": ""
+ },
+ "chagpt_api_send_button": {
+ "message": "Używając modelu",
+ "description": ""
+ },
+ "chagpt_api_connecting": {
+ "message": "Próba połączenia z OpenAI ChatGPT przy użyciu podanego klucza API",
+ "description": ""
+ },
+ "Debug": {
+ "message": "Debugowanie",
+ "description": ""
+ },
+ "prefs_OptionText_do_debug_info": {
+ "message": "Aktywuj system debugowania",
+ "description": ""
+ },
+ "prefs_Connection_type_Ollama_API": {
+ "message": "API Ollama (Lokalny LLM)",
+ "description": ""
+ },
+ "prefs_Connection_type_OpenAI_Comp_API": {
+ "message": "API kompatybilne z OpenAI",
+ "description": ""
+ },
+ "prefs_API_Host": {
+ "message": "Adres hosta",
+ "description": ""
+ },
+ "Ollama_Models": {
+ "message": "Modele Ollama",
+ "description": ""
+ },
+ "Ollama_Models_Fetch": {
+ "message": "Zaktualizuj listę modeli Ollama",
+ "description": ""
+ },
+ "Ollama_Models_Error_fetching": {
+ "message": "Błąd podczas pobierania modeli Ollama",
+ "description": ""
+ },
+ "API_Models_Error_NoModels": {
+ "message": "Nie znaleziono modeli",
+ "description": ""
+ },
+ "ollama_empty_host": {
+ "message": "Nie dodałeś adresu hosta dla API Ollama. Proszę wprowadź go na stronie opcji.",
+ "description": ""
+ },
+ "ollama_empty_model": {
+ "message": "Nie wybrałeś modelu dla API Ollama. Proszę wybierz jeden na stronie opcji.",
+ "description": ""
+ },
+ "ollama_api_connecting": {
+ "message": "Próba połączenia z lokalnym serwerem Ollama używając hosta",
+ "description": ""
+ },
+ "andModel": {
+ "message": "i modelu",
+ "description": ""
+ },
+ "error_connection_interrupted": {
+ "message": "Połączenie z serwerem zostało nieoczekiwanie przerwane",
+ "description": ""
+ },
+ "ollama_api_request_failed": {
+ "message": "Zapytanie do API Ollama nie powiodło się",
+ "description": ""
+ },
+ "chatgpt_api_request_failed": {
+ "message": "Zapytanie do API OpenAI ChatGPT nie powiodło się",
+ "description": ""
+ },
+ "WaitingServerReponse": {
+ "message": "Oczekiwanie na odpowiedź serwera",
+ "description": ""
+ },
+ "prefs_API_Host_Info": {
+ "message": "Taki jak",
+ "description": ""
+ },
+ "OpenAIComp_Models": {
+ "message": "Modele API kompatybilne z OpenAI",
+ "description": ""
+ },
+ "OpenAIComp_Models_Fetch": {
+ "message": "Zaktualizuj listę modeli API kompatybilnych z OpenAI",
+ "description": ""
+ },
+ "OpenAIComp_Models_Error_fetching": {
+ "message": "Błąd podczas pobierania modeli API kompatybilnych z OpenAI",
+ "description": ""
+ },
+ "OpenAIComp_empty_host": {
+ "message": "Nie dodałeś adresu hosta dla API kompatybilnego z OpenAI. Proszę wprowadź go na stronie opcji.",
+ "description": ""
+ },
+ "OpenAIComp_empty_model": {
+ "message": "Nie wybrałeś modelu dla API kompatybilnego z OpenAI. Proszę wybierz jeden na stronie opcji.",
+ "description": ""
+ },
+ "OpenAIComp_api_connecting": {
+ "message": "Próba połączenia z lokalnym serwerem API kompatybilnym z OpenAI używając hosta",
+ "description": ""
+ },
+ "OpenAIComp_api_request_failed": {
+ "message": "Zapytanie do API kompatybilnego z OpenAI nie powiodło się",
+ "description": ""
+ },
+ "prefs_OpenAIComp_ChatName": {
+ "message": "Nazwa czatu",
+ "description": ""
+ },
+ "prefs_OpenAIComp_ChatName_Info": {
+ "message": "To jest nazwa, która będzie używana w czacie AI.",
+ "description": ""
+ },
+ "StorageSpace": {
+ "message": "Całkowita zajęta przestrzeń dyskowa",
+ "description": ""
+ },
+ "SearchPrompt": {
+ "message": "Szukaj poleceń",
+ "description": ""
+ },
+ "prefs_OptionText_dynamic_menu_force_enter": {
+ "message": "Menu: natychmiastowe wysyłanie polecenia",
+ "description": ""
+ },
+ "prefs_OptionText_dynamic_menu_force_enter_info": {
+ "message": "Jeśli zaznaczone, użycie skrótu klawiszowego CTRL+ALT+A automatycznie wyśle zaznaczone polecenie z menu. W przeciwnym razie nazwa polecenia zostanie wyświetlona użytkownikowi, wymagając kolejnego naciśnięcia klawisza Enter, aby je wysłać.",
+ "description": ""
+ },
+ "prefs_OptionText_dynamic_menu_order_alphabet": {
+ "message": "Menu: sortuj alfabetycznie",
+ "description": ""
+ },
+ "prefs_OptionText_dynamic_menu_order_alphabet_info": {
+ "message": "Jeśli zaznaczone, polecenia w menu będą uporządkowane alfabetycznie.",
+ "description": ""
+ },
+ "prefs_OptionText_chatgpt_win_dims_info": {
+ "message": "Ustaw na 0, jeśli nie chcesz określać rozmiaru okna.",
+ "description": ""
+ },
+ "prefs_OpenAIComp_API_Key": {
+ "message": "Klucz API kompatybilny z OpenAI",
+ "description": ""
+ },
+ "Optional": {
+ "message": "Opcjonalne",
+ "description": ""
+ },
+ "OpenChatGPTTab": {
+ "message": "Otwórz kartę ChatGPT",
+ "description": ""
+ },
+ "OpenChatGPTTab_Info": {
+ "message": "W przypadku problemów z logowaniem w oknie ThunderAI, otwórz ChatGPT w nowej karcie używając przycisku po prawej stronie, zaloguj się, następnie zamknij kartę i kontynuuj korzystanie z ThunderAI.",
+ "description": ""
+ },
+ "placeholder_mail_text_body": {
+ "message": "Treść wiadomości",
+ "description": ""
+ },
+ "placeholder_mail_html_body": {
+ "message": "Treść wiadomości HTML",
+ "description": ""
+ },
+ "placeholder_mail_subject": {
+ "message": "Temat wiadomości",
+ "description": ""
+ },
+ "placeholder_selected_text": {
+ "message": "Zaznaczony tekst",
+ "description": ""
+ },
+ "placeholder_additional_text": {
+ "message": "Dodatkowy tekst",
+ "description": ""
+ },
+ "placeholder_junk_score": {
+ "message": "Wynik spam",
+ "description": ""
+ },
+ "placeholder_recipients": {
+ "message": "Odbiorcy",
+ "description": ""
+ },
+ "placeholder_cc_list": {
+ "message": "Lista DW",
+ "description": ""
+ },
+ "placeholder_author": {
+ "message": "Autor",
+ "description": ""
+ },
+ "prefs_OptionText_placeholders_use_default_value": {
+ "message": "Symbole zastępcze: użyj wartości domyślnej",
+ "description": ""
+ },
+ "prefs_OptionText_placeholders_use_default_value_info": {
+ "message": "Jeśli zaznaczone, symbole zastępcze zostaną wypełnione wartościami domyślnymi, gdy nie podano wartości. W przeciwnym razie symbole zastępcze pozostaną na miejscu.",
+ "description": ""
+ },
+ "prefs_OptionText_max_prompt_length": {
+ "message": "Maksymalna długość polecenia",
+ "description": ""
+ },
+ "prefs_OptionText_max_prompt_length_Info": {
+ "message": "To jest maksymalna liczba znaków, która może być użyta w poleceniu. W przeciwnym razie zostanie wyświetlony komunikat o błędzie. Wartość nie jest edytowalna dla interfejsu webowego ChatGPT.",
+ "description": ""
+ },
+ "prefs_OptionText_chatgpt_web_model": {
+ "message": "Model interfejsu webowego ChatGPT",
+ "description": ""
+ },
+ "prefs_OptionText_chatgpt_web_model_info": {
+ "message": "To jest model, który będzie wymuszony dla interfejsu webowego ChatGPT. Jeśli nie zostanie określony lub podano nieprawidłowy, domyślny model zostanie ustawiony przez ChatGPT na stronie.",
+ "description": ""
+ },
+ "prefs_OptionText_chatgpt_web_tempchat": {
+ "message": "Tymczasowy czat w interfejsie webowym ChatGPT",
+ "description": ""
+ },
+ "prefs_OptionText_chatgpt_web_tempchat_info": {
+ "message": "Jeśli zaznaczone, w interfejsie webowym ChatGPT będzie używany tymczasowy czat.",
+ "description": ""
+ },
+ "chatgpt_btn_model": {
+ "message": "Użyj bieżącego modelu",
+ "description": ""
+ },
+ "SendingPrompt": {
+ "message": "Wysyłanie polecenia...",
+ "description": ""
+ },
+ "AllowedValues": {
+ "message": "Dozwolone wartości",
+ "description": ""
+ },
+ "prefs_OptionText_btnManagePrompts_infoline": {
+ "message": "Możesz użyć dodatkowych symboli zastępczych danych.",
+ "description": ""
+ },
+ "prefs_OptionText_openai_comp_use_v1": {
+ "message": "Zachowaj kompatybilność z \"v1\"",
+ "description": ""
+ },
+ "prefs_OptionText_openai_comp_use_v1_info": {
+ "message": "Jeśli zaznaczone, segment \"v1\" w ścieżce wywołań API zostanie zachowany, np. \"http://localhost:1234/v1/chat/completions\".",
+ "description": ""
+ },
+ "prefs_OptionText_owl_warning": {
+ "message": "Wygląda na to, że przynajmniej jedno z Twoich kont używa dodatku Owl for Exchange. Istnieje znany problem między Thunderbirdem a Owl, który jest obecnie rozwiązywany. Na ten moment możesz używać ThunderAI podczas pisania e-maili, ale nie podczas ich czytania.",
+ "description": ""
+ },
+ "prefs_OptionText_chatgpt_web_model_tooltip": {
+ "message": "Kliknij na wartość, aby ją ustawić.",
+ "description": ""
+ }
+}
\ No newline at end of file
diff --git a/_locales/pt-br/messages.json b/_locales/pt-br/messages.json
index de11ee1c..9681d873 100644
--- a/_locales/pt-br/messages.json
+++ b/_locales/pt-br/messages.json
@@ -680,7 +680,7 @@
"description": ""
},
"prefs_OptionText_btnManagePrompts_infoline": {
- "message": "Agora você pode usar marcadores de posição adicionais!",
+ "message": "Você pode usar marcadores de posição adicionais para dados.",
"description": ""
},
"prefs_OptionText_openai_comp_use_v1": {
diff --git a/api_webchat/controller.js b/api_webchat/controller.js
index 9114ce11..aaed0d59 100644
--- a/api_webchat/controller.js
+++ b/api_webchat/controller.js
@@ -45,13 +45,13 @@ let worker = null;
switch (llm) {
case "chatgpt_api":
- worker = new Worker('model-worker-openai.js', { type: 'module' });
+ worker = new Worker('../js/workers/model-worker-openai.js', { type: 'module' });
break;
case "ollama_api":
- worker = new Worker('model-worker-ollama.js', { type: 'module' });
+ worker = new Worker('../js/workers/model-worker-ollama.js', { type: 'module' });
break;
case "openai_comp_api":
- worker = new Worker('model-worker-openai_comp.js', { type: 'module' });
+ worker = new Worker('../js/workers/model-worker-openai_comp.js', { type: 'module' });
break;
}
diff --git a/images/exclude-tag.svg b/images/exclude-tag.svg
new file mode 100644
index 00000000..abf70327
--- /dev/null
+++ b/images/exclude-tag.svg
@@ -0,0 +1,17 @@
+
\ No newline at end of file
diff --git a/js/mzta-addatags-exclusion-list.js b/js/mzta-addatags-exclusion-list.js
new file mode 100644
index 00000000..367c2c75
--- /dev/null
+++ b/js/mzta-addatags-exclusion-list.js
@@ -0,0 +1,28 @@
+/*
+ * 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 .
+ */
+
+// These methods are also defined in the file /js/mzta-compose-script.js
+
+export async function addTags_getExclusionList() {
+ let prefs_excluded_tags = await browser.storage.local.get({add_tags_exclusions: []});
+ return prefs_excluded_tags.add_tags_exclusions;
+}
+
+export function addTags_setExclusionList(add_tags_exclusions) {
+ browser.storage.local.set({add_tags_exclusions: add_tags_exclusions});
+}
\ No newline at end of file
diff --git a/js/mzta-compose-script.js b/js/mzta-compose-script.js
index 17670396..28ade65a 100644
--- a/js/mzta-compose-script.js
+++ b/js/mzta-compose-script.js
@@ -61,46 +61,12 @@ switch (message.command) {
case 'sendAlert':
//console.log(">>>>>> message.curr_tab_type: " + JSON.stringify(message.curr_tab_type));
if(message.curr_tab_type == 'mail'){ // workaround for Thunderbird bug not showing the alert
- // Create dialog elements
- const dialog_sendAlert = window.document.createElement('dialog');
- dialog_sendAlert.classList.add('mzta_dialog');
-
- const content_sendAlert = window.document.createElement('div');
- content_sendAlert.classList.add('mzta_dialog_content');
-
- // Create close button
- const closeButton_sendAlert = window.document.createElement('button');
- closeButton_sendAlert.classList.add('mzta_dialog_close');
- closeButton_sendAlert.id = 'mzta_dialog_close';
- closeButton_sendAlert.textContent = 'Close';
-
- // Create message element
- const message_sendAlert = window.document.createElement('div');
- message_sendAlert.classList.add('mzta_dialog_message');
- message_sendAlert.textContent = message.message;
-
- // Append elements to the dialog
- content_sendAlert.appendChild(message_sendAlert);
- content_sendAlert.appendChild(closeButton_sendAlert);
- dialog_sendAlert.appendChild(content_sendAlert);
- window.document.body.appendChild(dialog_sendAlert);
-
- // Show the dialog
- dialog_sendAlert.showModal();
-
- // Close dialog on button click and remove elements from the DOM
- closeButton_sendAlert.onclick = () => {
- dialog_sendAlert.close();
- dialog_sendAlert.addEventListener('close', () => {
- dialog_sendAlert.remove();
- style.remove();
- });
- };
-
// CSS styles for Thunderbird with support for light and dark themes
const style = document.createElement('style');
style.textContent = `
.mzta_dialog {
+ display: flex;
+ flex-direction: column;
border: none;
border-radius: 8px;
padding: 0;
@@ -118,18 +84,23 @@ switch (message.command) {
.mzta_dialog_content {
position: relative;
- padding: 20px;
+ padding: 10px;
+ overflow-y: auto;
+ overflow-x: clip;
+ max-height: 80vh;
+ flex: 1;
+ margin-bottom: 34px;
}
.mzta_dialog_close {
position: absolute;
- bottom: 10px;
- right: 10px;
+ bottom: 2px;
+ right: 2px;
background: #007bff;
border: none;
color: #ffffff;
font-size: 14px;
- padding: 8px 12px;
+ padding: 4px 6px;
cursor: pointer;
border-radius: 4px;
}
@@ -140,10 +111,22 @@ switch (message.command) {
.mzta_dialog_message {
margin-bottom: 40px;
- font-size: 14px;
}
- /* Theme support */
+ h2.addtags{
+ margin:0;
+ font-size: 1.2em;
+ text-align: center;
+ }
+
+ .div_btns{
+ width: 90%;
+ display: flex;
+ justify-content: center;
+ position: fixed;
+ bottom: 4px;
+ }
+
:root {
--dialog-bg-color: #ffffff;
--dialog-text-color: #000000;
@@ -155,29 +138,366 @@ switch (message.command) {
--dialog-text-color: #ffffff;
}
}
-
- /* Darker overlay for the page background */
- .mzta_dialog_overlay {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background: rgba(0, 0, 0, 0.7);
- z-index: 1000;
- }
`;
document.head.appendChild(style);
+ // Create dialog elements
+ const dialog_sendAlert = window.document.createElement('dialog');
+ dialog_sendAlert.classList.add('mzta_dialog');
+
+ const title = document.createElement('h2');
+ title.className = 'addtags';
+ if(message.is_error){
+ title.textContent = browser.i18n.getMessage("thunderai_error_title");
+ }else{
+ title.textContent = browser.i18n.getMessage("thunderai_warning_title");
+ }
+ dialog_sendAlert.appendChild(title);
+
+ const content_sendAlert = window.document.createElement('div');
+ content_sendAlert.classList.add('mzta_dialog_content');
+
+ // Add a div for buttons
+ const buttonsDiv = document.createElement('div');
+ buttonsDiv.className = 'div_btns';
+
+ // Create close button
+ const closeButton_sendAlert = window.document.createElement('button');
+ closeButton_sendAlert.classList.add('mzta_dialog_close');
+ closeButton_sendAlert.id = 'mzta_dialog_close';
+ closeButton_sendAlert.textContent = 'Close';
+ buttonsDiv.appendChild(closeButton_sendAlert);
+
+ // Create message element
+ const message_sendAlert = window.document.createElement('div');
+ message_sendAlert.classList.add('mzta_dialog_message');
+ message_sendAlert.textContent = message.message;
+
+ // Append elements to the dialog
+ content_sendAlert.appendChild(message_sendAlert);
+ dialog_sendAlert.appendChild(content_sendAlert);
+ dialog_sendAlert.appendChild(buttonsDiv);
+ window.document.body.appendChild(dialog_sendAlert);
+
+ // Show the dialog
+ dialog_sendAlert.showModal();
+
+ // Close dialog on button click and remove elements from the DOM
+ closeButton_sendAlert.onclick = () => {
+ dialog_sendAlert.close();
+ dialog_sendAlert.addEventListener('close', () => {
+ dialog_sendAlert.remove();
+ style.remove();
+ });
+ };
+
+ const iframeHeight = window.innerHeight;
+ const iframeWidth = window.innerWidth;
+
+ const dialogRect = dialog_sendAlert.getBoundingClientRect();
+
+ const top = Math.max(0, (iframeHeight - dialogRect.height) / 2);
+ const left = Math.max(0, (iframeWidth - dialogRect.width) / 2);
+
+ dialog_sendAlert.style.top = `${top}px`;
+ dialog_sendAlert.style.left = `${left}px`;
+ dialog_sendAlert.style.maxHeight = `${iframeHeight - 40}px`;
+
+ dialog_sendAlert.style.transform = 'translate(0, 0)';
+
}else{
alert(message.message);
}
return Promise.resolve(true);
break;
+ case "getTags":
+ console.log(">>>>>>>>>>>>>> getTags: " + JSON.stringify(message.tags));
+
+ // ===== These methods are also defined in the file /js/mzta-addatags-exclusion-list.js
+ async function addTags_getExclusionList() {
+ let prefs_excluded_tags = await browser.storage.local.get({add_tags_exclusions: []});
+ console.log(">>>>>>>>>>>>>>> addTags_getExclusionList prefs_excluded_tags: " + JSON.stringify(prefs_excluded_tags));
+ return prefs_excluded_tags.add_tags_exclusions;
+ }
+
+ function addTags_setExclusionList(add_tags_exclusions) {
+ browser.storage.local.set({add_tags_exclusions: add_tags_exclusions});
+ }
+ // ===================================================================================
+
+ // Create and append the styles
+ const style = document.createElement('style');
+ style.textContent = `
+ .mzta_dialog {
+ display: flex;
+ flex-direction: column;
+ border: none;
+ border-radius: 8px;
+ width: 300px;
+ max-width: 90%;
+ max-height: 90%;
+ padding: 0.5em;
+ overflow: hidden;
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
+ background-color: var(--dialog-bg-color, #fff);
+ color: var(--dialog-text-color, #000);
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ z-index: 1001;
+ }
+
+ .mzta_dialog_content {
+ position: relative;
+ padding: 0;
+ overflow-y: auto;
+ max-height: 80vh;
+ flex: 1;
+ margin-bottom: 20px;
+ }
+
+ .mzta_dialog_btn {
+ background: #007bff;
+ border: none;
+ color: #ffffff;
+ font-size: 14px;
+ padding: 4px 6px;
+ cursor: pointer;
+ border-radius: 4px;
+ flex-shrink: 0;
+ }
+
+ .mzta_dialog_btn_margin{
+ margin-right: 5px;
+ }
+
+ .mzta_dialog_btn:hover {
+ background: #0056b3;
+ }
+
+ .mzta_dialog_message {
+ margin-bottom: 40px;
+ font-size: 14px;
+ }
+
+ .mzta_dialog_message2{
+ margin: 20px auto;
+ }
+
+ .div_btns{
+ width: 90%;
+ display: flex;
+ justify-content: center;
+ position: fixed;
+ bottom: 4px;
+ }
+
+ .tag_excluded{
+ text-decoration: line-through;
+ }
+
+ h2.addtags{
+ margin:0;
+ font-size: 1.2em;
+ text-align: center;
+ }
+
+ img.exclude-tag-icon{
+ width: 16px;
+ height: 16px;
+ margin-right: 2px;
+ margin-left: 2px;
+ position: relative;
+ top: 3px;
+ cursor: pointer;
+ }
+
+ @media (prefers-color-scheme: dark) {
+ :root {
+ --dialog-bg-color: #2e2e2e;
+ --dialog-text-color: #ffffff;
+ }
+ }
+ `;
+ document.head.appendChild(style);
+
+ async function createDialog(inputString, onSubmit) {
+ // Create the dialog
+ const tags_dialog = document.createElement('dialog');
+ tags_dialog.className = 'mzta_dialog';
+
+ const title = document.createElement('h2');
+ title.className = 'addtags';
+ title.textContent = browser.i18n.getMessage("addtags_dialog_title");
+ tags_dialog.appendChild(title);
+
+ // Create the content
+ const content = document.createElement('div');
+ content.className = 'mzta_dialog_content';
+
+ let no_submit = false;
+
+ // Parse the input string into labels
+ const words = inputString.split(',').map(word => word.trim()).filter(word => word !== '');
+
+ console.log(">>>>>>>>>>>>> words: " + JSON.stringify(words));
+
+ if(words.length == 0){
+ const message = document.createElement('div');
+ message.className = 'mzta_dialog_message2';
+ message.textContent = browser.i18n.getMessage("addtags_no_tags_received");
+ content.appendChild(message);
+ no_submit = true;
+ }
+
+ let prefs_tags = await browser.storage.sync.get({add_tags_hide_exclusions: false});
+ let add_tags_exclusions_list = await addTags_getExclusionList();
+
+ console.log(">>>>>>>>>>>>> add_tags_exclusions_list: " + JSON.stringify(add_tags_exclusions_list));
+
+ const words_final = words
+ .filter(word => word !== '')
+ .map(word => {
+ const isExcluded = add_tags_exclusions_list.some(exclusion =>
+ word.toLowerCase().includes(exclusion.toLowerCase())
+ );
+
+ return {
+ tag: word,
+ excluded: isExcluded ? 1 : 0
+ };
+ });
+
+ console.log(">>>>>>>>>>>>> words_final: " + JSON.stringify(words_final));
+
+ // Create the form
+ const form = document.createElement('form');
+
+ let tags_shown = 0;
+
+ words_final.forEach(word => {
+ const label = document.createElement('label');
+ label.style.display = 'block';
+ const checkbox = document.createElement('input');
+ checkbox.type = 'checkbox';
+ checkbox.checked = true; // Checked by default
+ checkbox.value = word.tag;
+ // Add an image to the checkbox label
+ const img = document.createElement('img');
+ img.src = browser.runtime.getURL("/images/exclude-tag.svg");
+ img.alt = browser.i18n.getMessage("addtags_exclude_tag");
+ img.title = browser.i18n.getMessage("addtags_exclude_tag");
+ img.className = 'exclude-tag-icon';
+ img.addEventListener('click', () => {
+ if (!label.classList.contains('tag_excluded')) {
+ label.classList.add('tag_excluded');
+ if (!add_tags_exclusions_list.includes(word.tag)) {
+ add_tags_exclusions_list.push(word.tag);
+ addTags_setExclusionList(add_tags_exclusions_list);
+ }
+ } else {
+ label.classList.remove('tag_excluded');
+ const idx = add_tags_exclusions_list.indexOf(word.tag);
+ if (idx > -1) {
+ add_tags_exclusions_list.splice(idx, 1);
+ addTags_setExclusionList(add_tags_exclusions_list);
+ }
+ }
+ });
+
+ label.appendChild(checkbox);
+ label.appendChild(img);
+ label.appendChild(document.createTextNode(` ${word.tag}`));
+ if(!word.excluded || (word.excluded && !prefs_tags.add_tags_hide_exclusions)){
+ tags_shown++;
+ form.appendChild(label);
+ }
+ if(word.excluded){
+ label.className = 'tag_excluded';
+ checkbox.checked = false;
+ }
+ });
+
+ if((!no_submit) && (tags_shown == 0)){
+ const message = document.createElement('div');
+ message.className = 'mzta_dialog_message2';
+ message.textContent = browser.i18n.getMessage("addtags_no_valid_tags");
+ content.appendChild(message);
+ no_submit = true;
+ }
+
+ // Add a div for buttons
+ const buttonsDiv = document.createElement('div');
+ buttonsDiv.className = 'div_btns';
+
+ // Add the close button
+ const closeButton = document.createElement('button');
+ closeButton.type = 'button';
+ closeButton.id = 'mzta_dialog_close';
+ closeButton.textContent = 'Close';
+ closeButton.className = 'mzta_dialog_btn mzta_dialog_btn_margin';
+ closeButton.addEventListener('click', closeDialog);
+ buttonsDiv.appendChild(closeButton);
+
+ if(!no_submit){
+ // Add the submit button
+ const submitButton = document.createElement('button');
+ submitButton.type = 'button';
+ submitButton.id = 'mzta_dialog_submit';
+ submitButton.textContent = 'Submit';
+ submitButton.className = 'mzta_dialog_btn';
+ submitButton.addEventListener('click', () => {
+ const selected = Array.from(form.querySelectorAll('input[type=checkbox]:checked')).map(cb => cb.value);
+ onSubmit(selected);
+ closeDialog();
+ });
+ buttonsDiv.appendChild(submitButton);
+ }
+
+ content.appendChild(form);
+ tags_dialog.appendChild(content);
+ tags_dialog.appendChild(buttonsDiv);
+ document.body.appendChild(tags_dialog);
+
+ function closeDialog() {
+ tags_dialog.close();
+ tags_dialog.addEventListener('close', () => {
+ tags_dialog.remove();
+ style.remove();
+ });
+ }
+
+ tags_dialog.showModal();
+
+ const iframeHeight = window.innerHeight;
+ const iframeWidth = window.innerWidth;
+
+ const dialogRect = tags_dialog.getBoundingClientRect();
+
+ const top = Math.max(0, (iframeHeight - dialogRect.height) / 2);
+ const left = Math.max(0, (iframeWidth - dialogRect.width) / 2);
+
+ tags_dialog.style.top = `${top}px`;
+ tags_dialog.style.left = `${left}px`;
+ tags_dialog.style.maxHeight = `${iframeHeight - 40}px`;
+
+ tags_dialog.style.transform = 'translate(0, 0)';
+
+ return true;
+ }
+
+ return createDialog(message.tags, (selected) => {
+ console.log('>>>>>>>>>>>> Selected tags:', selected);
+ browser.runtime.sendMessage({ command: "assign_tags", tags: selected, messageId: message.messageId });
+ });
+
+ break;
+
default:
// do nothing
return Promise.resolve(false);
break;
-}
+}
});
\ No newline at end of file
diff --git a/js/mzta-menus.js b/js/mzta-menus.js
index aaf099cc..e860c132 100644
--- a/js/mzta-menus.js
+++ b/js/mzta-menus.js
@@ -19,9 +19,10 @@
// 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, i18nConditionalGet, getMailSubject } from './mzta-utils.js'
+import { getLanguageDisplayName, getMenuContextCompose, getMenuContextDisplay, i18nConditionalGet, getMailSubject, getTagsList, transformTagsLabels } from './mzta-utils.js'
import { taLogger } from './mzta-logger.js';
import { placeholdersUtils } from './mzta-placeholders.js';
+import { mzta_specialCommand_AddTags } from './special_commands/mzta-add-tags.js';
export class mzta_Menus {
@@ -50,29 +51,30 @@ export class mzta_Menus {
}
- async initialize() {
+ async initialize(also_special = false) {
this.allPrompts = [];
this.rootMenu = [];
this.shortcutMenu = [];
this.menu_listeners = {};
- this.allPrompts = await getPrompts(true);
+ this.allPrompts = await getPrompts(true,also_special);
this.allPrompts.sort((a, b) => a.name.localeCompare(b.name));
this.allPrompts.forEach((prompt) => {
this.addAction(prompt)
});
}
- async reload(){
+ async reload(also_special = false) {
await browser.menus.removeAll().catch(error => {
console.error("[ThunderAI] ERROR removing the menus: ", error);
});
this.removeClickListener();
- this.loadMenus();
+ this.loadMenus(also_special);
}
addAction = (curr_prompt) => {
let curr_menu_entry = {id: curr_prompt.id, is_default: curr_prompt.is_default, name: curr_prompt.name};
+ let curr_message = null;
const getMailBody = async (tabs) => {
//const tabs = await browser.tabs.query({ active: true, currentWindow: true });
@@ -92,7 +94,7 @@ export class mzta_Menus {
//A selection is needed, but nothing is selected!
//alert(browser.i18n.getMessage('prompt_selection_needed'));
browser.tabs.sendMessage(tabs[0].id, { command: "sendAlert", curr_tab_type: tabs[0].type, message : browser.i18n.getMessage('prompt_selection_needed') });
- return;
+ return {ok:'0'};
}
let body_text = '';
@@ -114,6 +116,7 @@ export class mzta_Menus {
}
let fullPrompt = '';
+ let full_tags_list = await getTagsList();
if(!placeholdersUtils.hasPlaceholder(curr_prompt.text)){
// no placeholders, do as usual
fullPrompt = curr_prompt.text + (String(curr_prompt.need_signature) == "1" ? " " + await this.getDefaultSignature():"") + " " + chatgpt_lang + " \"" + (selection_text=='' ? body_text : selection_text) + "\" ";
@@ -122,8 +125,21 @@ export class mzta_Menus {
let currPHs = await placeholdersUtils.extractPlaceholders(curr_prompt.text);
// console.log(">>>>>>>>>> currPHs: " + JSON.stringify(currPHs));
let finalSubs = {};
- let curr_messages = await browser.mailTabs.getSelectedMessages();
- let curr_message = curr_messages.messages[0];
+ let curr_messages = null;
+ switch(tabs[0].type){
+ case 'mail':
+ curr_messages = await browser.mailTabs.getSelectedMessages();
+ curr_message = curr_messages.messages[0];
+ break;
+ case 'messageDisplay':
+ curr_messages = await messenger.messageDisplay.getDisplayedMessage(tabs[0].id);
+ curr_message = curr_messages;
+ break;
+ case 'messageCompose':
+ curr_messages = await browser.compose.getComposeDetails(tabs[0].id);
+ curr_message = curr_messages;
+ break;
+ }
for(let currPH of currPHs){
switch(currPH.id){
case 'mail_text_body':
@@ -151,6 +167,13 @@ export class mzta_Menus {
case 'junk_score':
finalSubs['junk_score'] = curr_message.junkScore;
break;
+ case 'mail_tags':
+ let mail_tags_array = await transformTagsLabels(curr_message.tags, full_tags_list[1]);
+ finalSubs['mail_tags'] = mail_tags_array.join(", ");
+ break;
+ case 'full_tags_list':
+ finalSubs['full_tags_list'] = full_tags_list[0];
+ break;
default: // TODO Manage custom placeholders https://github.com/micz/ThunderAI/issues/156
break;
}
@@ -179,7 +202,46 @@ export class mzta_Menus {
// const tabs = await browser.tabs.query({ active: true, currentWindow: true });
// add custom text if needed
//browser.runtime.sendMessage({command: "chatgpt_open", prompt: fullPrompt, action: curr_prompt.action, tabId: tabs[0].id});
- this.openChatGPT(fullPrompt, curr_prompt.action, tabs[0].id, curr_prompt.name, curr_prompt.need_custom_text);
+ if(curr_prompt.is_special == '1'){ // Special prompts
+ switch(curr_prompt.id){
+ case 'prompt_add_tags': // Add tags to the email
+ let mail_tags = '';
+ let prefs_at = await browser.storage.sync.get({add_tags_maxnum: 3, connection_type: ''});
+ if((prefs_at.connection_type === '')||(prefs_at.connection_type === null)||(prefs_at.connection_type === undefined)||(prefs_at.connection_type === 'chatgpt_web')){
+ console.error("[ThunderAI | AddTags] Invalid connection type: " + prefs_at.connection_type);
+ return {ok:'0'};
+ }
+ let add_tags_maxnum = prefs_at.add_tags_maxnum;
+ if(add_tags_maxnum > 0){
+ fullPrompt += " " + browser.i18n.getMessage("prompt_add_tags_maxnum") + " " + add_tags_maxnum +".";
+ }
+ this.logger.log("fullPrompt: " + fullPrompt);
+ // TODO: use the current API, abort if using chatgpt web
+ // COMMENTED TO DO TESTS
+ mail_tags = "recipients, TEST, home, work, CAR, light";
+ // let cmd_addTags = new mzta_specialCommand_AddTags(fullPrompt,prefs_at.connection_type,true);
+ // await cmd_addTags.initWorker();
+ // try{
+ // mail_tags = await cmd_addTags.sendPrompt();
+ // // console.log(">>>>>>>>>>> mail_tags: " + mail_tags);
+ // }catch(err){
+ // console.error("[ThunderAI] Error getting tags: ", err);
+ // browser.tabs.sendMessage(tabs[0].id, { command: "sendAlert", curr_tab_type: tabs[0].type, message: "Error getting tags: " + err });
+ // return {ok:'0'};
+ // }
+ this.logger.log("mail_tags: " + mail_tags);
+ console.log(">>>>>>>>>>>> full_tags_list: " + JSON.stringify(full_tags_list));
+ browser.tabs.sendMessage(tabs[0].id, {command: "getTags", tags: mail_tags, messageId: curr_message.id});
+ return {ok:'1'};
+ break;
+ default:
+ console.error("[ThunderAI] Unknown special prompt id: " + curr_prompt.id);
+ break;
+ }
+ }else{ // Classic prompts for the API webchat
+ this.openChatGPT(fullPrompt, curr_prompt.action, tabs[0].id, curr_prompt.name, curr_prompt.need_custom_text);
+ return {ok:'1'};
+ }
};
this.rootMenu.push(curr_menu_entry);
};
@@ -205,8 +267,8 @@ export class mzta_Menus {
this.shortcutMenu.push(curr_menu_entry);
}
- async loadMenus() {
- await this.initialize();
+ async loadMenus(also_special = false) {
+ await this.initialize(also_special);
await this.addMenu(this.rootMenu);
this.addClickListener();
this.loadShortcutMenu();
@@ -296,7 +358,7 @@ export class mzta_Menus {
if (action) {
try {
// Execute the action callback
- await action();
+ return await action();
} catch (error) {
// Log any errors that occur during execution
console.error(`Error executing action for menu item ${id}:`, error);
@@ -305,6 +367,7 @@ export class mzta_Menus {
// Warn if no action is found for the provided ID
console.warn(`No action found for menu item ID: ${id}`);
}
+ return false;
}
}
\ No newline at end of file
diff --git a/js/mzta-placeholders-autocomplete.js b/js/mzta-placeholders-autocomplete.js
new file mode 100644
index 00000000..7b056870
--- /dev/null
+++ b/js/mzta-placeholders-autocomplete.js
@@ -0,0 +1,135 @@
+/*
+ * 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 .
+ */
+
+export function textareaAutocomplete(textarea, suggestions, type_value = -1) {
+ const container = textarea.closest('.autocomplete-container');
+ const autocompleteList = container.querySelector('.autocomplete-list');
+ let activeIndex = -1;
+
+ textarea.addEventListener('input', () => {
+ const cursorPosition = textarea.selectionStart;
+ const text = textarea.value.substring(0, cursorPosition);
+ const match = text.match(/{%[^\s]*$/);
+
+ if (match) {
+ const lastWord = match[0];
+ let type = type_value;
+ if(type_value === -1) {
+ const tr = textarea.parentNode.parentNode.parentNode;
+ type = tr.querySelector('.type_output').value
+ }
+ // console.log(">>>>>>>>> type: " + type);
+ // console.log(">>>>>>>>> suggestions: " + JSON.stringify(suggestions));
+ // console.log(">>>>>>>>> lastWord: " + lastWord);
+ const matches = suggestions.filter(s => s.command.startsWith(lastWord) && (String(s.type) == String(type) || String(s.type) == '0' )).map(s => s.command);
+ // console.log(">>>>>>>>> matches: " + JSON.stringify(matches));
+ showSuggestions(matches, autocompleteList);
+ } else {
+ hideSuggestions(autocompleteList);
+ }
+ });
+
+ textarea.addEventListener('keydown', (e) => {
+ if (autocompleteList.classList.contains('hidden')) {
+ return;
+ }
+ const items = autocompleteList.querySelectorAll('li');
+ if (items.length > 0) {
+ if (e.key === 'ArrowDown') {
+ e.preventDefault();
+ activeIndex = (activeIndex + 1) % items.length;
+ updateActiveSuggestion(items, activeIndex);
+ } else if (e.key === 'ArrowUp') {
+ e.preventDefault();
+ activeIndex = (activeIndex - 1 + items.length) % items.length;
+ updateActiveSuggestion(items, activeIndex);
+ } else if (e.key === 'Enter') {
+ e.preventDefault();
+ if(activeIndex === -1) activeIndex = 0;
+ if (activeIndex >= 0 && activeIndex < items.length) {
+ insertAutocomplete(items[activeIndex].textContent, textarea);
+ hideSuggestions(autocompleteList);
+ }
+ } else if (e.key === 'Escape') {
+ hideSuggestions(autocompleteList);
+ }
+ }
+ });
+
+ function showSuggestions(matches, autocompleteList) {
+ autocompleteList.innerHTML = '';
+ activeIndex = -1;
+ if (matches.length === 0) {
+ hideSuggestions(autocompleteList);
+ return;
+ }
+ matches.forEach(match => {
+ const li = document.createElement('li');
+ li.textContent = match;
+ li.addEventListener('click', () => {
+ insertAutocomplete(match, textarea);
+ hideSuggestions(autocompleteList);
+ });
+ autocompleteList.appendChild(li);
+ });
+ autocompleteList.classList.remove('hidden');
+ }
+
+ function hideSuggestions(autocompleteList) {
+ autocompleteList.classList.add('hidden');
+ activeIndex = -1;
+ }
+
+ function updateActiveSuggestion(items, activeIndex) {
+ items.forEach((item, index) => {
+ item.classList.remove('active');
+ if (index === activeIndex) {
+ item.classList.add('active');
+ item.scrollIntoView({ block: 'nearest' });
+ }
+ });
+ }
+
+ function insertAutocomplete(suggestion, textarea) {
+ const cursorPosition = textarea.selectionStart;
+ const textBefore = textarea.value.substring(0, cursorPosition);
+ const textAfter = textarea.value.substring(cursorPosition);
+ const match = textBefore.match(/{%[^\s]*$/);
+ if (match) {
+ const lastWord = match[0];
+ const completion = suggestion.substring(lastWord.length);
+ const newText = textBefore + completion + textAfter;
+ textarea.value = newText;
+ const newCursorPosition = cursorPosition + completion.length;
+ textarea.setSelectionRange(newCursorPosition, newCursorPosition);
+ }
+ }
+
+ document.addEventListener('click', (e) => {
+ // Check if the click was outside the textarea or suggestion list
+ const isClickInsideTextarea = e.target.closest('.editor');
+ const isClickInsideAutocompleteList = e.target.closest('.autocomplete-list');
+
+ if (!isClickInsideTextarea && !isClickInsideAutocompleteList) {
+ const container = textarea.closest('.autocomplete-container');
+ const autocompleteList = container.querySelector('.autocomplete-list');
+ hideSuggestions(autocompleteList);
+ }
+ });
+
+}
\ No newline at end of file
diff --git a/js/mzta-placeholders.js b/js/mzta-placeholders.js
index 5c1fbff8..f799f930 100644
--- a/js/mzta-placeholders.js
+++ b/js/mzta-placeholders.js
@@ -96,6 +96,20 @@ const defaultPlaceholders = [
type: 0,
is_default: "1",
},
+ {
+ id: 'mail_tags',
+ name: "__MSG_placeholder_mail_tags__",
+ default_value: "",
+ type: 0,
+ is_default: "1",
+ },
+ {
+ id: 'full_tags_list',
+ name: "__MSG_placeholder_full_tags_list__",
+ default_value: "",
+ type: 0,
+ is_default: "1",
+ },
];
diff --git a/js/mzta-prompts.js b/js/mzta-prompts.js
index a01b0e76..068554de 100644
--- a/js/mzta-prompts.js
+++ b/js/mzta-prompts.js
@@ -73,6 +73,7 @@ const defaultPrompts = [
need_custom_text: "0",
define_response_lang: "1",
is_default: "1",
+ is_special: "0",
},
{
id: 'prompt_reply_advanced',
@@ -85,6 +86,7 @@ const defaultPrompts = [
need_custom_text: "0",
define_response_lang: "1",
is_default: "1",
+ is_special: "0",
},
{
id: 'prompt_rewrite_polite',
@@ -97,6 +99,7 @@ const defaultPrompts = [
need_custom_text: "0",
define_response_lang: "1",
is_default: "1",
+ is_special: "0",
},
{
id: 'prompt_rewrite_formal',
@@ -109,6 +112,7 @@ const defaultPrompts = [
need_custom_text: "0",
define_response_lang: "1",
is_default: "1",
+ is_special: "0",
},
{
id: 'prompt_classify',
@@ -121,6 +125,7 @@ const defaultPrompts = [
need_custom_text: "0",
define_response_lang: "1",
is_default: "1",
+ is_special: "0",
},
{
id: 'prompt_summarize_this',
@@ -133,6 +138,7 @@ const defaultPrompts = [
need_custom_text: "0",
define_response_lang: "1",
is_default: "1",
+ is_special: "0",
},
{
id: 'prompt_translate_this',
@@ -145,6 +151,7 @@ const defaultPrompts = [
need_custom_text: "0",
define_response_lang: "0",
is_default: "1",
+ is_special: "0",
},
{
id: 'prompt_this',
@@ -157,16 +164,37 @@ const defaultPrompts = [
need_custom_text: "0",
define_response_lang: "0",
is_default: "1",
+ is_special: "0",
+ },
+];
+
+const specialPrompts = [
+ {
+ id: 'prompt_add_tags',
+ name: "__MSG_prompt_add_tags__",
+ text: "prompt_add_tags_full_text",
+ type: "1",
+ action: "0",
+ need_selected: "0",
+ need_signature: "0",
+ need_custom_text: "0",
+ define_response_lang: "0",
+ is_default: "1",
+ is_special: "1",
},
];
-export async function getPrompts(onlyEnabled = false){
+export async function getPrompts(onlyEnabled = false, includeSpecial = false){
const _defaultPrompts = await getDefaultPrompts_withProps();
// console.log('>>>>>>>>>>>> getPrompts _defaultPrompts: ' + JSON.stringify(_defaultPrompts));
const customPrompts = await getCustomPrompts();
// console.log('>>>>>>>>>>>> getPrompts customPrompts: ' + JSON.stringify(customPrompts));
- let output = _defaultPrompts.concat(customPrompts);
+ const specialPrompts = await getSpecialPrompts();
+ let output = specialPrompts.concat(_defaultPrompts).concat(customPrompts);
+ if(!includeSpecial){
+ output = output.filter(obj => obj.is_special != 1); // we do not want special prompts
+ }
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
@@ -270,7 +298,20 @@ export async function setDefaultPromptsProperties(prompts) {
await browser.storage.local.set({_default_prompts_properties: default_prompts_properties});
}
-
export async function setCustomPrompts(prompts) {
await browser.storage.local.set({_custom_prompt: prompts});
}
+
+export async function getSpecialPrompts(){
+ let prefs = await browser.storage.local.get({_special_prompts: null});
+ if(prefs._special_prompts === null){
+ return specialPrompts;
+ } else {
+ return prefs._special_prompts;
+ }
+}
+
+export async function setSpecialPrompts(prompts) {
+ // console.log(">>>>>>>>>>>> setSpecialPrompts prompts: " + JSON.stringify(prompts));
+ await browser.storage.local.set({_special_prompts: prompts});
+}
\ No newline at end of file
diff --git a/js/mzta-utils.js b/js/mzta-utils.js
index e7974246..d7f1b300 100644
--- a/js/mzta-utils.js
+++ b/js/mzta-utils.js
@@ -176,6 +176,72 @@ export function generateCallID(length = 10) {
return result;
}
+export async function getTagsList(){
+ let messageTags = {};
+ if(await isThunderbird128OrGreater()) {
+ messageTags = await browser.messages.tags.list();
+ } else {
+ messageTags = await browser.messages.listTags();
+ }
+ const output = messageTags.map(tag => tag.tag).join(', ');
+
+ const output2 = messageTags.reduce((acc, messageTag) => {
+ acc[messageTag.key] = {
+ tag: messageTag.tag,
+ color: messageTag.color,
+ key: messageTag.key,
+ ordinal: messageTag.ordinal,
+ };
+ return acc;
+ }, {});
+
+ return [output, output2]; // Return the list of tags and the list of tags objects as an array
+}
+
+export async function createTag(tag) {
+ let prefs_tag = await browser.storage.sync.get({add_tags_first_uppercase: true});
+ if(prefs_tag.add_tags_first_uppercase) tag = tag.toLowerCase().charAt(0).toUpperCase() + tag.toLowerCase().slice(1);
+ try {
+ if(await isThunderbird128OrGreater()) {
+ return browser.messages.tags.create('ta-'+tag.toLowerCase(), tag, generateHexColorForTag());
+ }else{
+ return browser.messages.createTag('ta-'+tag.toLowerCase(), tag, generateHexColorForTag());
+ }
+ } catch (error) {
+ console.error('[ThunderAI] Error creating tag:', error);
+ }
+}
+
+export async function assignTagsToMessage(messageId, tags) {
+ tags = tags.map(tag => `ta-${tag.toLowerCase()}`);
+ let msg_prop = await browser.messages.get(messageId);
+ tags = tags.concat(msg_prop.tags || []);
+ try {
+ return browser.messages.update(messageId, {tags: tags});
+ } catch (error) {
+ console.error('[ThunderAI] Error assigning tag [messageId: ', messageId, ' - tag: ', tag, ']:', error);
+ }
+}
+
+function generateHexColorForTag() {
+ const red = Math.floor(Math.random() * 256);
+ const green = Math.floor(Math.random() * 256);
+ const blue = Math.floor(Math.random() * 256);
+
+ const hexColor = `#${red.toString(16).padStart(2, '0')}${green.toString(16).padStart(2, '0')}${blue.toString(16).padStart(2, '0')}`;
+
+ return hexColor;
+}
+
+export async function transformTagsLabels(labels, tags_list) {
+ console.log(">>>>>>>>> transformTagsLabels labels: " + labels);
+ console.log(">>>>>>>>> transformTagsLabels tags_list: " + tags_list);
+ let output = [];
+ for(let label of labels) {
+ output.push(tags_list[label].tag);
+ }
+ return output;
+}
// The following methods are a modified version derived from https://github.com/ali-raheem/Aify/blob/13ff87583bc520fb80f555ab90a90c5c9df797a7/plugin/content_scripts/compose.js
diff --git a/js/special_commands/mzta-add-tags.js b/js/special_commands/mzta-add-tags.js
new file mode 100644
index 00000000..7cfa422d
--- /dev/null
+++ b/js/special_commands/mzta-add-tags.js
@@ -0,0 +1,114 @@
+/*
+ * 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 .
+ */
+ // Call the API to get the tags
+
+ import { taLogger } from '../mzta-logger.js';
+
+
+ export class mzta_specialCommand_AddTags {
+
+ prompt = "";
+ worker = null;
+ llm = "";
+ full_message = "";
+ logger = null;
+ do_debug = false;
+
+ constructor(prompt, llm, do_debug = false) {
+ this.prompt = prompt;
+ this.llm = llm;
+ this.logger = new taLogger('mzta_specialCommand_AddTags', do_debug);
+ this.do_debug = do_debug;
+ switch (this.llm) {
+ case "chatgpt_api":
+ this.worker = new Worker(new URL('../workers/model-worker-openai.js', import.meta.url), { type: 'module' });
+ break;
+ case "ollama_api":
+ this.worker = new Worker(new URL('../workers/model-worker-ollama.js', import.meta.url), { type: 'module' });
+ break;
+ case "openai_comp_api":
+ this.worker = new Worker(new URL('../workers/model-worker-openai_comp.js', import.meta.url), { type: 'module' });
+ break;
+ }
+ }
+
+ async initWorker() {
+ switch (this.llm) {
+ case "chatgpt_api":
+ let prefs_api = await browser.storage.sync.get({chatgpt_api_key: '', chatgpt_model: ''});
+ this.worker.postMessage({ type: 'init', chatgpt_api_key: prefs_api.chatgpt_api_key, chatgpt_model: prefs_api.chatgpt_model, do_debug: this.do_debug, i18nStrings: ''});
+ break;
+ case "ollama_api": {
+ let prefs_api = await browser.storage.sync.get({ollama_host: '', ollama_model: ''});
+ this.worker.postMessage({ type: 'init', ollama_host: prefs_api.ollama_host, ollama_model: prefs_api.ollama_model, do_debug: this.do_debug, i18nStrings: ''});
+ 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_use_v1: true, openai_comp_chat_name: '', do_debug: false});
+ console.log(">>>>>>>>>>>> [ThunderAI] prefs_api: " + JSON.stringify(prefs_api));
+ this.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, openai_comp_use_v1: prefs_api.openai_comp_use_v1, do_debug: this.do_debug, i18nStrings: ''});
+ break;
+ }
+ }
+ }
+
+ sendPrompt(){
+ return new Promise((resolve, reject) => {
+ // Event listeners for worker messages
+ this.worker.onmessage = (event) => {
+ const { type, payload } = event.data;
+ // console.log(`>>>>>>>>>>>> [Worker Message Received] type: ${type}`, payload);
+ switch (type) {
+ case 'messageSent':
+ break;
+ case 'newToken':
+ this.full_message += payload.token;
+ break;
+ case 'tokensDone':
+ console.log(">>>>>>>>>>>> [ThunderAI] tokensDone: " + this.full_message);
+ resolve(this.full_message); // Resolve the promise with the full message
+ break;
+ case 'error':
+ console.error('[ThunderAI] Error from API worker:', payload);
+ reject(new Error(`[ThunderAI] Error from API worker: ${payload}`)); // Use a single error object
+ break;
+ default:
+ console.error('[ThunderAI] Unknown event type from API worker:', type);
+ }
+ };
+
+ this.worker.onerror = (error) => {
+ console.error('[ThunderAI] Worker Error Details:');
+ console.error('Message:', error.message);
+ console.error('Filename:', error.filename);
+ console.error('Line Number:', error.lineno);
+ console.error('Column Number:', error.colno);
+ console.error('Event:', error);
+ reject(error);
+ };
+
+ try {
+ // console.log('[ThunderAI] Sending prompt to worker:', this.prompt);
+ this.worker.postMessage({ type: 'chatMessage', message: this.prompt });
+ } catch (error) {
+ console.error('[ThunderAI] Failed to send message to worker:', error);
+ reject(error);
+ }
+ });
+ }
+ }
\ No newline at end of file
diff --git a/api_webchat/model-worker-ollama.js b/js/workers/model-worker-ollama.js
similarity index 98%
rename from api_webchat/model-worker-ollama.js
rename to js/workers/model-worker-ollama.js
index 98354fae..fb74bac6 100644
--- a/api_webchat/model-worker-ollama.js
+++ b/js/workers/model-worker-ollama.js
@@ -20,8 +20,8 @@
* The original code has been released under the Apache License, Version 2.0.
*/
-import { Ollama } from '../js/api/ollama.js';
-import { taLogger } from '../js/mzta-logger.js';
+import { Ollama } from '../api/ollama.js';
+import { taLogger } from '../mzta-logger.js';
let ollama_host = null;
let ollama_model = '';
diff --git a/api_webchat/model-worker-openai.js b/js/workers/model-worker-openai.js
similarity index 98%
rename from api_webchat/model-worker-openai.js
rename to js/workers/model-worker-openai.js
index f467bd98..3f364682 100644
--- a/api_webchat/model-worker-openai.js
+++ b/js/workers/model-worker-openai.js
@@ -20,8 +20,8 @@
* The original code has been released under the Apache License, Version 2.0.
*/
-import { OpenAI } from '../js/api/openai.js';
-import { taLogger } from '../js/mzta-logger.js';
+import { OpenAI } from '../api/openai.js';
+import { taLogger } from '../mzta-logger.js';
let chatgpt_api_key = null;
let chatgpt_model = '';
diff --git a/api_webchat/model-worker-openai_comp.js b/js/workers/model-worker-openai_comp.js
similarity index 98%
rename from api_webchat/model-worker-openai_comp.js
rename to js/workers/model-worker-openai_comp.js
index b63de5d7..e2e775d4 100644
--- a/api_webchat/model-worker-openai_comp.js
+++ b/js/workers/model-worker-openai_comp.js
@@ -20,8 +20,8 @@
* 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';
+import { OpenAIComp } from '../api/openai_comp.js';
+import { taLogger } from '../mzta-logger.js';
let openai_comp_host = null;
let openai_comp_model = '';
diff --git a/manifest.json b/manifest.json
index c931b144..c2f42fa4 100644
--- a/manifest.json
+++ b/manifest.json
@@ -31,6 +31,9 @@
"activeTab",
"accountsRead",
"downloads",
+ "messagesTagsList",
+ "messagesTags",
+ "messagesUpdate",
"https://*.chatgpt.com/*"
],
"background": {
diff --git a/mzta-background.js b/mzta-background.js
index 7ae5b4ea..8aca3e94 100644
--- a/mzta-background.js
+++ b/mzta-background.js
@@ -20,7 +20,7 @@ import { mzta_script } from './js/mzta-chatgpt.js';
import { prefs_default } from './options/mzta-options-default.js';
import { mzta_Menus } from './js/mzta-menus.js';
import { taLogger } from './js/mzta-logger.js';
-import { getCurrentIdentity, getOriginalBody, replaceBody, setBody, i18nConditionalGet, generateCallID, migrateCustomPromptsStorage, migrateDefaultPromptsPropStorage, getGPTWebModelString } from './js/mzta-utils.js';
+import { getCurrentIdentity, getOriginalBody, replaceBody, setBody, i18nConditionalGet, generateCallID, migrateCustomPromptsStorage, migrateDefaultPromptsPropStorage, getGPTWebModelString, getTagsList, createTag, assignTagsToMessage } from './js/mzta-utils.js';
await migrateCustomPromptsStorage();
await migrateDefaultPromptsPropStorage();
@@ -28,8 +28,8 @@ await migrateDefaultPromptsPropStorage();
var original_html = '';
var modified_html = '';
-let prefs_debug = await browser.storage.sync.get({do_debug: false});
-let taLog = new taLogger("mzta-background",prefs_debug.do_debug);
+let prefs_init = await browser.storage.sync.get({do_debug: false, add_tags: false, connection_type: 'chatgpt_web'});
+let taLog = new taLogger("mzta-background",prefs_init.do_debug);
browser.composeScripts.register({
js: [{file: "/js/mzta-compose-script.js"}]
@@ -210,14 +210,16 @@ messenger.runtime.onMessage.addListener((message, sender, sendResponse) => {
return _reloadBody(message.tabId);
break;
case 'reload_menus':
- menus.reload();
- taLog.log("Reloading menus");
- return Promise.resolve(true);
+ async function _reload_menus() {
+ menus.reload(prefs_init.add_tags && (prefs_init.connection_type !== "chatgpt_web"));
+ taLog.log("Reloading menus");
+ return true;
+ }
+ return _reload_menus();
break;
case 'shortcut_do_prompt':
taLog.log("Executing shortcut, promptId: " + message.promptId);
- menus.executeMenuAction(message.promptId);
- return Promise.resolve(true);
+ return menus.executeMenuAction(message.promptId);
break;
case 'popup_menu_ready':
async function _popup_menu_ready() {
@@ -230,6 +232,26 @@ messenger.runtime.onMessage.addListener((message, sender, sendResponse) => {
}
return _popup_menu_ready();
break;
+ case 'assign_tags':
+ async function _assign_tags() {
+ let all_tags_list = await getTagsList();
+ all_tags_list = all_tags_list[1];
+ console.log(">>>>>>>>>>>>>>> all_tags_list: " + JSON.stringify(all_tags_list));
+ taLog.log("assign_tags data: " + JSON.stringify(message));
+ let new_tags = [];
+ for (const tag of message.tags) {
+ console.log(">>>>>>>>>>>>>>> tag: " + tag);
+ if (!all_tags_list.hasOwnProperty("ta-" + tag.toLowerCase())) {
+ taLog.log("Creating tag: " + tag);
+ await createTag(tag);
+ }
+ new_tags.push(tag);
+ }
+ await assignTagsToMessage(message.messageId, new_tags);
+ taLog.log("Assigned tags: " + JSON.stringify(new_tags));
+ }
+ return _assign_tags();
+ break;
default:
break;
}
@@ -384,7 +406,7 @@ async function openChatGPT(promptText, action, curr_tabId, prompt_name = '', do_
let rand_call_id3 = '_ollama_' + generateCallID();
- const listener3 = async (message, sender, sendResponse) => {
+ const listener3 = (message, sender, sendResponse) => {
function handleOllamaApi(createdTab3) {
taLog.log("Ollama API window ready.");
@@ -440,7 +462,7 @@ async function openChatGPT(promptText, action, curr_tabId, prompt_name = '', do_
let rand_call_id4 = '_openai_comp_api_' + generateCallID();
- const listener4 = async (message, sender, sendResponse) => {
+ const listener4 = (message, sender, sendResponse) => {
function handleOpenAICompApi(createdTab) {
let mailMessageId4 = -1;
@@ -497,6 +519,22 @@ function checkScreenDimensions(prefs){
return prefs;
}
+// Register the listener for storage changes
+browser.storage.onChanged.addListener((changes, areaName) => {
+ // Check if the change happened in the 'sync' storage area
+ if (areaName === 'sync') {
+ // Check if 'add_tags' has changed
+ if (changes.add_tags) {
+ menus.reload(changes.add_tags.newValue && (prefs_init.connection_type !== "chatgpt_web"));
+ }
+
+ // Check if 'connection_type' has changed
+ if (changes.connection_type) {
+ menus.reload(prefs_init.add_tags && (changes.connection_type.newValue !== "chatgpt_web"));
+ }
+ }
+});
+
// Menus handling
-const menus = new mzta_Menus(openChatGPT, prefs_debug.do_debug);
-menus.loadMenus();
\ No newline at end of file
+const menus = new mzta_Menus(openChatGPT, prefs_init.do_debug);
+menus.loadMenus(prefs_init.add_tags && (prefs_init.connection_type !== "chatgpt_web"));
\ No newline at end of file
diff --git a/options/mzta-options-default.js b/options/mzta-options-default.js
index b2f29484..35089c32 100644
--- a/options/mzta-options-default.js
+++ b/options/mzta-options-default.js
@@ -37,4 +37,8 @@ export const prefs_default = {
dynamic_menu_order_alphabet: true,
placeholders_use_default_value: false,
max_prompt_length: 30000, // max string length for prompt
+ add_tags: false,
+ add_tags_maxnum: 3,
+ add_tags_hide_exclusions: false,
+ add_tags_first_uppercase: true,
}
\ No newline at end of file
diff --git a/options/mzta-options.css b/options/mzta-options.css
index 013c39ec..8044eb8d 100644
--- a/options/mzta-options.css
+++ b/options/mzta-options.css
@@ -166,10 +166,31 @@ input.option-input[type="text"]{
border-radius: 4px;
}
+#mzta_top_msg div{
+ color: white;
+ font-style: italic;
+ background-color: rgb(3, 0, 180);
+ border-radius: 4px;
+ padding: 3px;
+ font-weight: bold;
+ margin-bottom: 5px;
+ text-align: center;
+}
+
+#mzta_top_msg div a{
+ background-color: white;
+ padding:0px 3px;
+ border-radius: 4px;
+}
+
.conntype_chatgpt_web_option{
cursor: pointer;
}
+.btn_small{
+ font-size: 0.8em;
+}
+
@media (prefers-color-scheme: dark) {
body {
background-color: rgb(35, 34, 43);
diff --git a/options/mzta-options.html b/options/mzta-options.html
index 99f90516..c633f3da 100644
--- a/options/mzta-options.html
+++ b/options/mzta-options.html
@@ -8,6 +8,9 @@