diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ba6520d..1f45844d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,13 @@

Version 2.4.0 - ??/??/2024

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 @@ + + + @@ -266,6 +269,16 @@ + + + +
__MSG_prefs_SurveyLinkText__
__MSG_prefs_SurveyLinkText2__
__MSG_prefs_OptionText_owl_warning__ __MSG_customPrompts_managePrompts_help__
__MSG_prefs_OptionText_add_tags__ +
+ +