Merge pull request #203 from micz/add_tags

Add tags to emails
This commit is contained in:
Mic 2024-12-18 20:32:11 +01:00 committed by GitHub
commit 3c053d55d9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 2355 additions and 250 deletions

View file

@ -8,6 +8,13 @@
<h2>Version 2.4.0 - ??/??/2024</h2>
<ul>
<li>Default prompts text has been translated [<a href="https://github.com/micz/ThunderAI/issues/185">#185</a>].</li>
<li>Data placeholder for the full available tags list added [<a href="https://github.com/micz/ThunderAI/issues/197">#197</a>].</li>
<li>Data placeholder for the single mail tags list added [<a href="https://github.com/micz/ThunderAI/issues/198">#198</a>].</li>
<li>User <a href="https://forms.gle/1qK2wcbuhaRzhwyt9">survey link</a> added [<a href="https://github.com/micz/ThunderAI/issues/202">#202</a>].</li>
<li>Polish (pl) translation added, thanks to <a href="https://github.com/neexpl">neexpl</a>.</li>
<li>The Custom Prompts tab has now an icon.</li>
<li>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 [<a href="https://github.com/micz/ThunderAI/issues/201">#201</a>].</li>
<li>Minor bugs fixed.</li>
<li>...</li>
</ul>
<h2>Version 2.3.4 - 06/12/2024</h2>

View file

@ -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/)**!
<br>
@ -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**!
<br>
> [!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
<br>
### Miscellaneous
- <a href="https://github.com/KudoAI/chatgpt.js">chatgpt.js</a> for providing methods to interact with the ChatGPT web frontend
- <a href="https://github.com/ali-raheem/Aify">Aify</a> for inspiration
- <a href="https://github.com/boxabirds">Julian Harris</a> for his project <a href="https://github.com/boxabirds/chatgpt-frontend-nobuild">chatgpt-frontend-nobuild</a>, that has been used as a starting point for the API Web Interface
<br>
@ -93,3 +86,12 @@ Are you using this addon in your Thunderbird?
- <a href="https://loading.io">loading.io</a> for the dynamic menu loading SVG
- [Fluent Design System](https://www.iconfinder.com/fluent-designsystem) for the Custom Prompts table sorting icons
- [JessiGue](https://www.flaticon.com/authors/jessigue) for the show/hide icon for api key fields
<br>
### Miscellaneous
- <a href="https://github.com/KudoAI/chatgpt.js">chatgpt.js</a> for providing methods to interact with the ChatGPT web frontend
- <a href="https://github.com/boxabirds">Julian Harris</a> for his project <a href="https://github.com/boxabirds/chatgpt-frontend-nobuild">chatgpt-frontend-nobuild</a>, that has been used as a starting point for the API Web Interface
- <a href="https://github.com/ali-raheem/Aify">Aify</a> for inspiration

View file

@ -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": {

View file

@ -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": ""
}
}

View file

@ -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": {

View file

@ -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": {

702
_locales/pl/messages.json Normal file
View file

@ -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<63><65>...",
"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": ""
}
}

View file

@ -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": {

View file

@ -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;
}

17
images/exclude-tag.svg Normal file
View file

@ -0,0 +1,17 @@
<svg width="128" height="128" style="enable-background:new 0 0 128 128;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="64" x2="64" y1="18.6667" y2="124.8554">
<stop offset="0" style="stop-color:#FF5252"/>
<stop offset="0.324" style="stop-color:#F33B3B"/>
<stop offset="1" style="stop-color:#D50000"/>
</linearGradient>
<circle cx="64" cy="64" r="60" style="fill:url(#SVGID_1_);"/>
<g style="opacity:0.2;">
<path d="M64,7c31.43,0,57,25.57,57,57s-25.57,57-57,57S7,95.43,7,64S32.57,7,64,7 M64,4 C30.86,4,4,30.86,4,64s26.86,60,60,60s60-26.86,60-60S97.14,4,64,4L64,4z" style="fill:#424242;"/>
</g>
<g style="opacity:0.2;">
<path d="M104,56c2.21,0,4,1.79,4,4v8c0,2.21-1.79,4-4,4H24c-2.21,0-4-1.79-4-4v-8c0-2.21,1.79-4,4-4H104 M104,53H24c-3.86,0-7,3.14-7,7v8c0,3.86,3.14,7,7,7h80c3.86,0,7-3.14,7-7v-8C111,56.14,107.86,53,104,53L104,53z" style="fill:#424242;"/>
</g>
<path d="M104,72H24c-2.21,0-4-1.79-4-4v-8c0-2.21,1.79-4,4-4h80c2.21,0,4,1.79,4,4v8 C108,70.21,106.21,72,104,72z" style="fill:#FAFAFA;"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
// 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});
}

View file

@ -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;
}
}
});

View file

@ -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;
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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);
}
});
}

View file

@ -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",
},
];

View file

@ -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});
}

View file

@ -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

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
// 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);
}
});
}
}

View file

@ -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 = '';

View file

@ -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 = '';

View file

@ -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 = '';

View file

@ -31,6 +31,9 @@
"activeTab",
"accountsRead",
"downloads",
"messagesTagsList",
"messagesTags",
"messagesUpdate",
"https://*.chatgpt.com/*"
],
"background": {

View file

@ -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();
const menus = new mzta_Menus(openChatGPT, prefs_init.do_debug);
menus.loadMenus(prefs_init.add_tags && (prefs_init.connection_type !== "chatgpt_web"));

View file

@ -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,
}

View file

@ -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);

View file

@ -8,6 +8,9 @@
<div id="miczRelNotes"><a href="mzta-release-notes.html">__MSG_prefs_OptionText_release_notes__</a></div>
<div id="miczStatusPage" class="conntype_chatgpt_web"><a href="https://micz.it/thunderbird-addon-thunderai/status/">__MSG_prefs_status_page__</a></div>
<table id="miczPrefs">
<tr id="mzta_top_msg">
<td colspan="2"><div>__MSG_prefs_SurveyLinkText__<br><a href="https://forms.gle/1qK2wcbuhaRzhwyt9">__MSG_prefs_SurveyLinkText2__</a></div></td>
</tr>
<tr id="owl_warning">
<td colspan="2"><div>__MSG_prefs_OptionText_owl_warning__ <a href="https://micz.it/thunderbird-addon-thunderai/owl/">__MSG_customPrompts_managePrompts_help__</a></div></td>
</tr>
@ -266,6 +269,16 @@
</label>
</td>
</tr>
<tr class="add_tags_tr">
<td><span>__MSG_prefs_OptionText_add_tags__</span>
<br><button id="btnManageTagsInfo" class="btn_small">__MSG_prefs_OptionText_btnManageTagsInfo__</button></td>
<td>
<label>
<input type="checkbox" id="add_tags" name="add_tags" class="option-input" />
__MSG_prefs_OptionText_add_tags_Info__
</label>
</td>
</tr>
<tr>
<td style="width:20em"><label>
<span>__MSG_Debug__</span>

View file

@ -94,6 +94,7 @@ async function restoreOptions() {
function showConnectionOptions() {
disable_MaxPromptLength();
disable_AddTags();
let chatgpt_web_display = 'table-row';
let chatgpt_api_display = 'none';
let ollama_api_display = 'none';
@ -212,6 +213,14 @@ function disable_MaxPromptLength(){
maxPromptLength_tr.style.display = (maxPromptLength.disabled) ? 'none' : 'table-row';
}
function disable_AddTags(){
let add_tags = document.getElementById('add_tags');
let add_tags_tr_elements = document.querySelectorAll('.add_tags_tr');
add_tags_tr_elements.forEach(add_tags_tr => {
add_tags_tr.style.display = (add_tags.disabled) ? 'none' : 'table-row';
});
}
document.addEventListener('DOMContentLoaded', async () => {
await restoreOptions();
@ -233,16 +242,36 @@ document.addEventListener('DOMContentLoaded', async () => {
el.dispatchEvent(new Event('change'), { bubbles: true });
});
});
let addtags_el = document.getElementById('add_tags');
let addtags_info_btn = document.getElementById('btnManageTagsInfo');
addtags_el.addEventListener('click', (event) => {
addtags_info_btn.disabled = event.target.checked ? '' : 'disabled';
});
addtags_info_btn.disabled = addtags_el.checked ? '' : 'disabled';
document.getElementById('btnManagePrompts').addEventListener('click', () => {
// check if the tab is already there
browser.tabs.query({url: browser.runtime.getURL('../customprompts/mzta-custom-prompts.html')}).then((tabs) => {
browser.tabs.query({url: browser.runtime.getURL('../pages/customprompts/mzta-custom-prompts.html')}).then((tabs) => {
if (tabs.length > 0) {
// if the tab is already there, focus it
browser.tabs.update(tabs[0].id, {active: true});
} else {
// if the tab is not there, create it
browser.tabs.create({url: browser.runtime.getURL('../customprompts/mzta-custom-prompts.html')});
browser.tabs.create({url: browser.runtime.getURL('../pages/customprompts/mzta-custom-prompts.html')});
}
})
});
document.getElementById('btnManageTagsInfo').addEventListener('click', () => {
// check if the tab is already there
browser.tabs.query({url: browser.runtime.getURL('../pages/addtags/mzta-add-tags.html')}).then((tabs) => {
if (tabs.length > 0) {
// if the tab is already there, focus it
browser.tabs.update(tabs[0].id, {active: true});
} else {
// if the tab is not there, create it
browser.tabs.create({url: browser.runtime.getURL('../pages/addtags/mzta-add-tags.html')});
}
})
});
@ -401,6 +430,7 @@ select_openai_comp_model.addEventListener("change", warn_OpenAIComp_HostEmpty);
warn_Ollama_HostEmpty();
warn_OpenAIComp_HostEmpty();
disable_MaxPromptLength();
disable_AddTags();
const passwordField_chatgpt_api_key = document.getElementById('chatgpt_api_key');
const toggleIcon_chatgpt_api_key = document.getElementById('toggle_chatgpt_api_key');

View file

@ -10,6 +10,13 @@
<h2>Version 2.4.0 - ??/??/2024</h2>
<ul>
<li>Default prompts text has been translated [<a href="https://github.com/micz/ThunderAI/issues/185">#185</a>].</li>
<li>Data placeholder for the full available tags list added [<a href="https://github.com/micz/ThunderAI/issues/197">#197</a>].</li>
<li>Data placeholder for the single mail tags list added [<a href="https://github.com/micz/ThunderAI/issues/198">#198</a>].</li>
<li>User <a href="https://forms.gle/1qK2wcbuhaRzhwyt9">survey link</a> added [<a href="https://github.com/micz/ThunderAI/issues/202">#202</a>].</li>
<li>Polish (pl) translation added, thanks to <a href="https://github.com/neexpl">neexpl</a>.</li>
<li>The Custom Prompts tab has now an icon.</li>
<li>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 [<a href="https://github.com/micz/ThunderAI/issues/201">#201</a>].</li>
<li>Minor bugs fixed.</li>
<li>...</li>
</ul>
<h2>Version 2.3.4 - 06/12/2024</h2>

View file

@ -0,0 +1,132 @@
#addtags_prompt_container{
width: 90%;
margin: 20px auto;
}
#addtags_prompt_text{
width: 100%;
}
.infoline{
font-size: 0.8em;
font-style: italic;
}
#addtags_excl_list_container{
width: 90%;
margin: 40px auto;
}
#addtags_excl_list{
width: 30em;
}
.btn_div{
width: 100%;
display: flex;
justify-content: space-between;
}
#addtags_info_limit_num{
display: none;
}
table#miczPrefs {
padding: 10px;
border-spacing: 0px;
width: 90%;
margin: 0px auto 40px auto;
border: 1px solid #ccc;
}
.addtags_table_title{
width: 90%;
margin: auto;
}
.section_title{
font-weight: bold;
}
table#miczPrefs td{
border-top: 1px solid #ccc;
border-bottom: 1px solid #ccc;
vertical-align: top;
padding: 2px;
}
table#miczPrefs tr:first-child td {
border-top: none;
}
table#miczPrefs tr:last-child td {
border-bottom: none;
}
.autocomplete-container {
position: relative;
}
.autocomplete-list {
position: absolute;
top: 100%;
left: 0;
right: 0;
background-color: white;
border: 1px solid #ccc;
z-index: 1000;
max-height: 200px;
overflow-y: auto;
padding: 0;
margin: 0;
list-style: none;
font-size: small;
}
.autocomplete-list li {
padding: 8px;
cursor: pointer;
}
.autocomplete-list li:hover {
background-color: #f0f0f0;
}
.autocomplete-list li.active {
background-color: #ddd;
}
.hidden {
display: none;
}
.unsaved{
color: red;
}
@media (prefers-color-scheme: dark) {
body {
background-color: #1C1B22;
color: rgb(251, 251, 254);
}
a:link { color: #409EFF; }
a:visited { color: #409EFF; }
a:hover { color: #66B1FF; }
a:active { color: #66B1FF; }
a:active { color: #66B1FF; }
.autocomplete-list {
background-color: #2E2F36;
border: 1px solid #2E2F36;
}
.autocomplete-list li:hover {
background-color: #4c4e58;
}
.autocomplete-list li.active {
background-color: #4c4e58;
}
}

View file

@ -0,0 +1,66 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ThunderAI - __MSG_AddTags_PageTitle__</title>
<link rel="stylesheet" href="mzta-add-tags.css">
<link rel="icon" href="../../images/icon-16px.png">
</head>
<body>
<div>
<h1 class="page_title">__MSG_AddTags_PageTitle__</h1>
<p>__MSG_AddTags_info_default__</p>
</div>
<div class="addtags_table_title section_title">__MSG_AddTags_prompt_prefs_title__</div>
<table id="miczPrefs">
<tr class="add_tags_tr">
<td><span>__MSG_prefs_OptionText_add_tags_maxnum__</span></td>
<td>
<label>
<input type="number" id="add_tags_maxnum" name="add_tags_maxnum" class="option-input" />
<br>__MSG_prefs_OptionText_add_tags_maxnum_Info__
</label>
</td>
</tr>
<tr class="add_tags_tr">
<td><span>__MSG_prefs_OptionText_add_tags_hide_exclusions__</span></td>
<td>
<label>
<input type="checkbox" id="add_tags_hide_exclusions" name="add_tags_hide_exclusions" class="option-input" />
__MSG_prefs_OptionText_add_tags_hide_exclusions_Info__
</label>
</td>
</tr>
<tr class="add_tags_tr">
<td><span>__MSG_prefs_OptionText_add_tags_first_uppercase__</span></td>
<td>
<label>
<input type="checkbox" id="add_tags_first_uppercase" name="add_tags_first_uppercase" class="option-input" />
__MSG_prefs_OptionText_add_tags_first_uppercase_Info__
</label>
</td>
</tr>
</table>
<div id="addtags_prompt_container">
<span class="section_title">__MSG_AddTags_prompt_text_title__</span><span id="addtags_prompt_unsaved" class="unsaved hidden"> __MSG_customPrompts_unsaved_changes__</span>
<br><span class="infoline">__MSG_prefs_OptionText_btnManagePrompts_infoline__ <a href="https://micz.it/thunderbird-addon-thunderai/data-placeholders/">__MSG_customPrompts_managePrompts_help__</a>
<br><b>__MSG_prefs_OptionText_btnManagePrompts_infoline2__</b></span>
<br>
<div class="autocomplete-container">
<textarea id="addtags_prompt_text" rows="15"></textarea>
<ul class="autocomplete-list hidden"></ul>
</div>
<div id="addtags_info_limit_num"></div>
<div class="btn_div"><button id="btn_reset_prompt" disabled>__MSG_reset_default__</button><button id="btn_save_prompt" disabled>__MSG_save__</button></div>
</div>
<div id="addtags_excl_list_container">
<span class="section_title">__MSG_AddTags_excl_list_title__</span><span id="excl_list_unsaved" class="unsaved hidden"> __MSG_customPrompts_unsaved_changes__</span>
<br><span class="infoline">__MSG_AddTags_excl_list_infoline__
<br>__MSG_AddTags_excl_list_infoline2__</span>
<br><textarea id="addtags_excl_list" rows="15"></textarea>
<div class="btn_div"><button id="btn_save_excl_list" disabled>__MSG_save__</button></div>
</div>
<script src="mzta-add-tags.js" type="module"></script>
<script src="../../js/mzta-i18n.js"></script>
</body>
</html>

View file

@ -0,0 +1,192 @@
/*
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
* Copyright (C) 2024 Mic (m@micz.it)
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { prefs_default } from '../../options/mzta-options-default.js';
import { taLogger } from '../../js/mzta-logger.js';
import { getSpecialPrompts, setSpecialPrompts } from "../../js/mzta-prompts.js";
import { getPlaceholders } from "../../js/mzta-placeholders.js";
import { textareaAutocomplete } from "../../js/mzta-placeholders-autocomplete.js";
import { addTags_getExclusionList, addTags_setExclusionList } from "../../js/mzta-addatags-exclusion-list.js";
let autocompleteSuggestions = [];
let taLog = new taLogger("mzta-addtags-page",true);
document.addEventListener('DOMContentLoaded', async () => {
i18n.updateDocument();
await restoreOptions();
document.querySelectorAll(".option-input").forEach(element => {
element.addEventListener("change", saveOptions);
});
let addtags_textarea = document.getElementById('addtags_prompt_text');
let addtags_save_btn = document.getElementById('btn_save_prompt');
let addtags_reset_btn = document.getElementById('btn_reset_prompt');
let specialPrompts = await getSpecialPrompts();
let addtags_prompt = specialPrompts.find(prompt => prompt.id === 'prompt_add_tags');
addtags_textarea.addEventListener('input', (event) => {
addtags_reset_btn.disabled = (event.target.value === browser.i18n.getMessage('prompt_add_tags_full_text'));
addtags_save_btn.disabled = (event.target.value === addtags_prompt.text);
if(addtags_save_btn.disabled){
document.getElementById('addtags_prompt_unsaved').classList.add('hidden');
} else {
document.getElementById('addtags_prompt_unsaved').classList.remove('hidden');
}
});
addtags_reset_btn.addEventListener('click', () => {
addtags_textarea.value = browser.i18n.getMessage('prompt_add_tags_full_text');
addtags_reset_btn.disabled = true;
let event = new Event('input', { bubbles: true, cancelable: true });
addtags_textarea.dispatchEvent(event);
});
addtags_save_btn.addEventListener('click', () => {
specialPrompts.find(prompt => prompt.id === 'prompt_add_tags').text = addtags_textarea.value;
setSpecialPrompts(specialPrompts);
addtags_save_btn.disabled = true;
document.getElementById('addtags_prompt_unsaved').classList.add('hidden');
browser.runtime.sendMessage({command: "reload_menus"});
});
if(addtags_prompt.text === 'prompt_add_tags_full_text'){
addtags_prompt.text = browser.i18n.getMessage(addtags_prompt.text);
}
addtags_textarea.value = addtags_prompt.text;
addtags_reset_btn.disabled = (addtags_textarea.value === browser.i18n.getMessage('prompt_add_tags_full_text'));
document.getElementById('add_tags_maxnum').addEventListener('change', updateMaxTagsNumDesc);
updateMaxTagsNumDesc();
autocompleteSuggestions = (await getPlaceholders(true)).filter(p => !(p.id === 'selected_text' || p.id === 'additional_text')).map(p => ({command: '{%'+p.id+'%}', type: p.type}));
textareaAutocomplete(addtags_textarea, autocompleteSuggestions, 1); // type_value = 1, only when reading an email
let excl_list_textarea = document.getElementById('addtags_excl_list');
let excl_list_save_btn = document.getElementById('btn_save_excl_list');
let excl_list_value = await addTags_getExclusionList();
let excl_list_string = excl_list_value.join('\n');
excl_list_textarea.value = excl_list_string;
excl_list_textarea.addEventListener('input', (event) => {
excl_list_save_btn.disabled = (event.target.value === excl_list_string);
if(excl_list_save_btn.disabled){
document.getElementById('excl_list_unsaved').classList.add('hidden');
} else {
document.getElementById('excl_list_unsaved').classList.remove('hidden');
}
});
excl_list_save_btn.addEventListener('click', () => {
let excl_array_new = excl_list_textarea.value.split(/[\n,]+/);
excl_array_new = Array.from(new Set(excl_array_new.map(item => item.trim().toLowerCase()))).sort();
addTags_setExclusionList(excl_array_new);
excl_list_save_btn.disabled = true;
excl_list_textarea.value = excl_array_new.join('\n');
document.getElementById('excl_list_unsaved').classList.add('hidden');
});
});
async function updateMaxTagsNumDesc(){
let prefs_maxt = await browser.storage.sync.get({add_tags_maxnum: 3});
let el_tag_limit = document.getElementById('addtags_info_limit_num');
if(prefs_maxt.add_tags_maxnum > 0){
el_tag_limit.textContent = browser.i18n.getMessage("addtags_info_limit_num") + " \"" + browser.i18n.getMessage("prompt_add_tags_maxnum") + " " + prefs_maxt.add_tags_maxnum +"\".";
el_tag_limit.style.display = 'block';
}else{
el_tag_limit.style.display = 'none';
}
}
// Methods to manage options, derived from: /options/mzta-options.js
function saveOptions(e) {
e.preventDefault();
let options = {};
let element = e.target;
switch (element.type) {
case 'checkbox':
options[element.id] = element.checked;
break;
case 'number':
options[element.id] = element.valueAsNumber;
break;
case 'text':
case 'password':
options[element.id] = element.value.trim();
break;
default:
if (element.tagName === 'SELECT') {
options[element.id] = element.value;
}else{
console.error("[ThunderAI] Unhandled input type:", element.type);
}
}
browser.storage.sync.set(options);
}
async function restoreOptions() {
function setCurrentChoice(result) {
document.querySelectorAll(".option-input").forEach(element => {
taLog.log("Options restoring " + element.id + " = " + (element.id=="chatgpt_api_key" || element.id=="openai_comp_api_key" ? "****************" : result[element.id]));
switch (element.type) {
case 'checkbox':
element.checked = result[element.id] || false;
break;
case 'number':
let default_number_value = 0;
if(element.id == 'chatgpt_win_height') default_number_value = prefs_default.chatgpt_win_height;
if(element.id == 'chatgpt_win_width') default_number_value = prefs_default.chatgpt_win_width;
element.value = result[element.id] ?? default_number_value;
break;
case 'text':
case 'password':
let default_text_value = '';
if(element.id == 'default_chatgpt_lang') default_text_value = prefs_default.default_chatgpt_lang;
element.value = result[element.id] || default_text_value;
break;
default:
if (element.tagName === 'SELECT') {
let default_select_value = '';
if(element.id == 'reply_type') default_select_value = 'reply_all';
if(element.id == 'connection_type') default_select_value = 'chatgpt_web';
element.value = result[element.id] || default_select_value;
if (element.value === '') {
element.selectedIndex = -1;
}
}else{
console.error("[ThunderAI] Unhandled input type:", element.type);
}
}
});
}
let getting = await browser.storage.sync.get(prefs_default);
setCurrentChoice(getting);
}

View file

@ -4,6 +4,7 @@
<meta charset="UTF-8">
<title>ThunderAI - __MSG_customPrompts_managePrompts__</title>
<link rel="stylesheet" href="mzta-custom-prompts.css">
<link rel="icon" href="../../images/icon-16px.png">
</head>
<body>
<div>
@ -98,6 +99,6 @@
<div class="storage_space">__MSG_StorageSpace__: <span id="storage_space"></span></div>
<script src="list.js"></script>
<script src="mzta-custom-prompts.js" type="module"></script>
<script src="../js/mzta-i18n.js"></script>
<script src="../../js/mzta-i18n.js"></script>
</body>
</html>

View file

@ -16,10 +16,11 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { getPrompts, setDefaultPromptsProperties, setCustomPrompts, preparePromptsForExport, preparePromptsForImport } from "../js/mzta-prompts.js";
import { isThunderbird128OrGreater, getCustomPromptsUsedSpace, sanitizeHtml } from "../js/mzta-utils.js";
import { taLogger } from "../js/mzta-logger.js";
import { getPlaceholders } from "../js/mzta-placeholders.js";
import { getPrompts, setDefaultPromptsProperties, setCustomPrompts, preparePromptsForExport, preparePromptsForImport } from "../../js/mzta-prompts.js";
import { isThunderbird128OrGreater, getCustomPromptsUsedSpace, sanitizeHtml } from "../../js/mzta-utils.js";
import { taLogger } from "../../js/mzta-logger.js";
import { getPlaceholders } from "../../js/mzta-placeholders.js";
import { textareaAutocomplete } from "../../js/mzta-placeholders-autocomplete.js";
var promptsList = null;
var somethingChanged = false;
@ -118,7 +119,15 @@ document.addEventListener('DOMContentLoaded', async () => {
// console.log('>>>>>>>>>>> suggestions: ' + JSON.stringify(suggestions));
textareas.forEach(textarea => textareaAutocomplete(textarea, autocompleteSuggestions));
textareas.forEach(textarea => {
textareaAutocomplete(textarea, autocompleteSuggestions);
textareas.forEach(textarea => {
textarea.addEventListener('input', (e) => {
checkPromptsConfigForPlaceholders(e.target);
});
textareaAutocomplete(textarea, autocompleteSuggestions);
});
});
// document.addEventListener('click', (e) => {
// // Check if the click was outside the textarea or suggestion list
@ -680,141 +689,29 @@ if(await isThunderbird128OrGreater()){
});
}
function textareaAutocomplete(textarea, suggestions) {
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]*$/);
checkPromptsConfigForPlaceholders(textarea);
if (match) {
const lastWord = match[0];
const tr = textarea.parentNode.parentNode.parentNode;
let 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);
}
});
}
function checkPromptsConfigForPlaceholders(textarea){
// check additional_text and selected_text placeholders presence and the corrispondent checkboxes
let tr_ancestor = textarea.closest('tr');
let need_custom_text_element = tr_ancestor.querySelector('.need_custom_text') || tr_ancestor.querySelector('.need_custom_text_new');
if(String(textarea.value).indexOf('{%additional_text%}') != -1){
let tr_ancestor = textarea.closest('tr');
let need_custom_text_element = tr_ancestor.querySelector('.need_custom_text') || tr_ancestor.querySelector('.need_custom_text_new');
if(!need_custom_text_element.checked){
need_custom_text_element.closest('.need_custom_text_span').style.border = '2px solid red';
}else{
need_custom_text_element.closest('.need_custom_text_span').style.border = '';
}
}else{
need_custom_text_element.closest('.need_custom_text_span').style.border = '';
}
let tr_ancestor2 = textarea.closest('tr');
let selected_text_element = tr_ancestor2.querySelector('.need_selected') || tr_ancestor2.querySelector('.need_selected_new');
if(String(textarea.value).indexOf('{%selected_text%}') != -1){
let tr_ancestor = textarea.closest('tr');
let selected_text_element = tr_ancestor.querySelector('.need_selected') || tr_ancestor.querySelector('.need_selected_new');
if(!selected_text_element.checked){
selected_text_element.closest('.need_selected_span').style.border = '2px solid red';
}else{
selected_text_element.closest('.need_selected_span').style.border = '';
}
}else{
selected_text_element.closest('.need_selected_span').style.border = '';
}
}

View file

@ -62,6 +62,10 @@ body {
.mzta_autocomplete-item-active {
background-color: #e9e9e9;
}
.prompt_add_tags{
background-color: #cfe9ff;
}
@media (prefers-color-scheme: dark) {
@ -96,4 +100,8 @@ body {
.mzta_autocomplete-item-active {
background-color: #444444;
}
.prompt_add_tags{
background-color: #353542;
}
}

View file

@ -16,26 +16,30 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { taStore } from "../js/mzta-store.js";
import { taLogger } from "../js/mzta-logger.js";
let menuSendImmediately = false;
let taLog = console;
let connection_type = 'chatgpt_web';
let add_tags = false;
let tabType;
document.addEventListener('DOMContentLoaded', async () => {
let prefs = await browser.storage.sync.get({do_debug: false, dynamic_menu_force_enter: false});
let prefs = await browser.storage.sync.get({do_debug: false, dynamic_menu_force_enter: false, add_tags: false, connection_type: 'chatgpt_web'});
taLog = new taLogger("mzta-popup",prefs.do_debug);
i18n.updateDocument();
let reponse = await browser.runtime.sendMessage({command: "popup_menu_ready"});
taLog.log("Preparing data to load the popup menu: " + JSON.stringify(reponse));
let tabId = reponse.lastShortcutTabId;
let tabType = reponse.lastShortcutTabType;
tabType = reponse.lastShortcutTabType;
let filtering = reponse.lastShortcutFiltering;
let _prompts_data = reponse.lastShortcutPromptsData;
taLog.log("_prompts_data: " + JSON.stringify(_prompts_data));
let active_prompts = filterPromptsForTab(_prompts_data, filtering);
taLog.log("active_prompts: " + JSON.stringify(active_prompts));
menuSendImmediately = prefs.dynamic_menu_force_enter;
connection_type = prefs.connection_type;
add_tags = prefs.add_tags;
searchPrompt(active_prompts, tabId, tabType);
i18n.updateDocument();
}, { once: true });
@ -78,7 +82,7 @@ async function searchPrompt(allPrompts, tabId, tabType){
*/
// Filter data based on the query
const filteredData = allPrompts.filter(item =>
let filteredData = allPrompts.filter(item =>
item.label.toLowerCase().includes(query)
);
taLog.log("filteredData: " + JSON.stringify(filteredData));
@ -89,9 +93,20 @@ async function searchPrompt(allPrompts, tabId, tabType){
return;
}
// Prepend numbers to the first 10 items
Array.from(filteredData).slice(0, 10).forEach((item, index) => {
const number = (index < 9) ? (index + 1).toString() : '0';
// If add_tags is true and connection_type is not 'chatgpt_web' reserve 0 position for prompt_add_tags
let max_num_el = 10
let first_num_el = 0;
if(checkDoAddTags()){
max_num_el = 9;
first_num_el = 1;
filteredData = ensurePromptAddTagsFirst(filteredData);
filteredData[0].numberPrepended = 'true';
filteredData[0].label = '0. ' + filteredData[0].label;
}
Array.from(filteredData).slice(first_num_el, max_num_el).forEach((item, index) => {
let number = (index < 9) ? (index + 1).toString() : '0';
// Check if the number is already prepended to avoid duplication
if (!item.numberPrepended) {
item.label = `${number}. ${item.label}`;
@ -101,12 +116,22 @@ async function searchPrompt(allPrompts, tabId, tabType){
// console.log(">>>>>>>>>>>>> filteredData: " + JSON.stringify(filteredData));
// add the prompt_add_tags if add_tags is true and connection_type is not 'chatgpt_web'
// if(checkDoAddTags()){
// let number = '0';
// let item = {label: `${number}. ${browser.i18n.getMessage('prompt_add_tags')}`, id: 'prompt_add_tags', numberPrepended: 'true'};
// filteredData.unshift(item);
// }
// Create a div for each filtered result
filteredData.forEach(item => {
const itemDiv = document.createElement('div');
itemDiv.classList.add('mzta_autocomplete-item');
itemDiv.textContent = item.label;
itemDiv.setAttribute('data-id', item.id);
if(item.id === 'prompt_add_tags'){
itemDiv.className += ' prompt_add_tags';
}
// Add a mousedown event to select the item
itemDiv.addEventListener('mousedown', function(e) { // Use mousedown instead of click
@ -141,8 +166,8 @@ async function searchPrompt(allPrompts, tabId, tabType){
});
// Add a keydown event listener to handle arrow navigation and selection
input.addEventListener('keydown', function (e) {
input.addEventListener('keydown', async function (e) {
const items = autocompleteList.getElementsByClassName('mzta_autocomplete-item');
if ((autocompleteList.style.display === 'none' || items.length === 0)
&& (e.key !== 'Enter')
@ -154,7 +179,11 @@ async function searchPrompt(allPrompts, tabId, tabType){
// Handle number key presses (1-9,0) to select the corresponding item directly
if (['1','2','3','4','5','6','7','8','9','0'].includes(e.key)) {
// Map '1' to index 0, '2' to 1, ..., '9' to 8, '0' to 9
const numIndex = (e.key === '0') ? 9 : parseInt(e.key, 10) - 1;
let numIndex = (e.key === '0') ? 9 : parseInt(e.key, 10) - 1;
if(checkDoAddTags()){
numIndex = parseInt(e.key, 10);
}
if (items[numIndex]) {
e.preventDefault(); // Prevent any default behavior
// Dispatch a select_prompt event to simulate a click/select action
@ -226,11 +255,15 @@ document.body.insertBefore(banner, document.body.firstChild);
}
function sendPrompt(prompt_id, tabId){
async function sendPrompt(prompt_id, tabId){
taLog.log("sendPrompt: " + prompt_id);
document.getElementById('mzta_search_input').style.display = 'none';
document.getElementById('mzta_sending_prompt').style.display = 'block';
browser.runtime.sendMessage({command: "shortcut_do_prompt", tabId: tabId, promptId: prompt_id});
let response = await browser.runtime.sendMessage({command: "shortcut_do_prompt", tabId: tabId, promptId: prompt_id});
console.log(">>>>>>>>>>>>>>>>> response: " + JSON.stringify(response));
if(response.ok == '1'){
window.close();
}
}
function filterPromptsForTab(prompts_data, filtering){
@ -252,4 +285,23 @@ function filterPromptsForTab(prompts_data, filtering){
// Filter the array based on the allowed types
return prompts_data.filter(prompt => allowedTypes.includes(prompt.type));
}
function checkDoAddTags(){
return add_tags && (connection_type !== "chatgpt_web" && tabType !== 'messageCompose');
}
function ensurePromptAddTagsFirst(arr) {
// Find the index of the object with id "prompt_add_tags"
const index = arr.findIndex(item => item.id === "prompt_add_tags");
// If found and not already the first element
if (index !== -1 && index !== 0) {
// Remove it from its current position
const [promptAddTags] = arr.splice(index, 1);
// Add it to the beginning of the array
arr.unshift(promptAddTags);
}
return arr;
}