Merge pull request #239 from micz/v3.1.0

v3.1.0
This commit is contained in:
Mic 2025-01-27 21:49:49 +01:00 committed by GitHub
commit a29e7ded13
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
49 changed files with 2006 additions and 163 deletions

View file

@ -5,12 +5,22 @@
<h2>Version 3.1.0 - 27/01/2025</h2>
<ul>
<li><i>[ChatGPT API][Ollama API][OpenAI Comp API][Gemini API]</i> Added a special prompt to get calendar events data from emails [<a href="https://github.com/micz/ThunderAI/issues/182">#182</a>]. To use this feature, you must install also the <a href="https://addons.thunderbird.net/it/thunderbird/addon/thunderai-sparks/">Sparks</a> add-on.</li>
<li>Added Google Gemini API support [<a href="https://github.com/micz/ThunderAI/issues/204">#204</a>, <a href="https://github.com/micz/ThunderAI/issues/217">#217</a>].</li>
<li>Added the <i>{%mail_typed_text%}</i> placeholder to get the text inserted before the quoted mail body when replying [<a href="https://github.com/micz/ThunderAI/issues/196">#196</a>].</li>
<li>Using the <i>{%mail_typed_text%}</i> placeholder the typed text inserted before the quoted mail body will be selected automatically to be replaced afterwards with the AI response [<a href="https://github.com/micz/ThunderAI/issues/229">#229</a>].</li>
<li>Added the <i>{%mail_datetime%}</i> placeholder to get the date and time of the email [<a href="https://github.com/micz/ThunderAI/issues/223">#223</a>].</li>
<li>Added the <i>{%current_datetime%}</i> data placeholder to get the current date and time [<a href="https://github.com/micz/ThunderAI/issues/224">#224</a>].</li>
<li>Added an info text about using the new <i>{%tags_full_list%}</i> placeholder in the "Add Tags Prompt" page [<a href="https://github.com/micz/ThunderAI/issues/215">#215</a>].</li>
</ul>
<h2>Version 3.0.0 - 05/01/2025</h2>
<ul>
<li><i>[ChatGPT API][Ollama API][OpenAI Comp API]</i> Added a special prompt to apply tags to emails [<a href="https://github.com/micz/ThunderAI/issues/183">#183</a>].</li>
<li>Default prompts text has been translated [<a href="https://github.com/micz/ThunderAI/issues/185">#185</a>].</li>
<li>Data placeholder <i>{%tags_full_list%}</i> for the full available tags list added [<a href="https://github.com/micz/ThunderAI/issues/197">#197</a>].</li>
<li>Data placeholder <i>{%tags_current_email%}</i> for the single mail tags list added [<a href="https://github.com/micz/ThunderAI/issues/198">#198</a>].</li>
<li>Added the <i>{%tags_full_list%}</i> data placeholder for the full available tags list added [<a href="https://github.com/micz/ThunderAI/issues/197">#197</a>].</li>
<li>Added the <i>{%tags_current_email%}</i> 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><i>[OpenAI Comp API]</i> Added a button in the options page to manually insert the model [<a href="https://github.com/micz/ThunderAI/issues/205">#205</a>].</li>
<li>Polish (pl) translation added, thanks to <a href="https://github.com/neexpl">neexpl</a>.</li>

View file

@ -1,8 +1,8 @@
# ![ThunderAI icon](images/icon-32px.png "ThunderAI") ThunderAI
ThunderAI is a Thunderbird Addon that uses the capabilities of ChatGPT or Ollama to enhance email management.
ThunderAI is a Thunderbird Addon that uses the capabilities of ChatGPT | Google Gemini | Ollama to enhance email management.
It enables users to analyse, write, correct, assign tags and optimize their emails, facilitating more effective and professional communication.
It enables users to analyse, write, correct, assign tags, create calendar events and optimize their emails, facilitating more effective and professional communication.
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.
@ -23,6 +23,13 @@ In any custom prompt you can use additional **[data placeholders](https://micz.i
<br>
> [!TIP]
> **Using Google Gemini**
>
> Connect directly with Google Gemini API, using also the _System Instructions_ option if needed.
<br>
> [!TIP]
> **Using Ollama**
>
@ -71,7 +78,7 @@ Are you using this addon in your Thunderbird?
### Translations
- English (en-US): [Mic](https://github.com/micz/)
- French (fr): Generated automatically
- French (fr): Generated automatically, [Noam](https://github.com/noam-sc)
- German (de): Generated automatically
- Italian (it): [Mic](https://github.com/micz/)
- Polski (pl): [neexpl](https://github.com/neexpl)

View file

@ -888,11 +888,131 @@
"description": ""
},
"prefs_OptionText_add_tags_force_lang_Info": {
"message": "Wenn aktiviert, wird die Sprache der Tags auf die in den ThunderAI Optionen definierte Sprache festgelegt, falls angegeben.",
"description": ""
"message": "Wenn aktiviert, wird die Sprache der Tags auf die in den ThunderAI Optionen definierte Sprache festgelegt, falls angegeben.",
"description": ""
},
"prompt_add_tags_force_lang": {
"message": "Die Tags müssen in geschrieben werden",
"description": ""
"message": "Die Tags müssen in geschrieben werden",
"description": ""
},
"prefs_Connection_type_Google_Gemini_API": {
"message": "Google Gemini API",
"description": ""
},
"prefs_GoogleGemini_API_Key": {
"message": "API-Schlüssel",
"description": ""
},
"GoogleGemini_Models": {
"message": "Google Gemini API-Modelle",
"description": ""
},
"GoogleGemini_Models_Fetch": {
"message": "Google Gemini-Modelle-Liste aktualisieren",
"description": ""
},
"GoogleGemini_Models_Error_fetching": {
"message": "Fehler beim Abrufen der Google Gemini-Modelle",
"description": ""
},
"google_gemini_api_request_failed": {
"message": "Google Gemini API-Anfrage fehlgeschlagen",
"description": ""
},
"google_gemini_api_connecting": {
"message": "Versuch, eine Verbindung zu Google Gemini mit dem bereitgestellten API-Schlüssel herzustellen",
"description": ""
},
"google_gemini_empty_apikey": {
"message": "Sie haben keinen API-Schlüssel für die Google Gemini API hinzugefügt. Bitte fügen Sie einen auf der Optionsseite ein.",
"description": ""
},
"google_gemini_empty_model": {
"message": "Sie haben kein Modell für die Google Gemini API ausgewählt. Bitte wählen Sie eines auf der Optionsseite aus.",
"description": ""
},
"GoogleGemini_SystemInstruction": {
"message": "Systemanweisung",
"description": ""
},
"GoogleGemini_SystemInstruction_Info": {
"message": "Wenn Sie eine Systemanweisung festlegen, geben Sie dem Modell zusätzlichen Kontext, um die Aufgabe zu verstehen, individuellere Antworten zu liefern und spezifische Richtlinien für den zu sendenden Prompt einzuhalten.",
"description": ""
},
"ChatGPT_Developer_Messages": {
"message": "Entwicklernachrichten",
"description": ""
},
"ChatGPT_Developer_Messages_Info": {
"message": "Wenn Sie die Entwicklernachrichten festlegen, geben Sie dem Modell zusätzlichen Kontext, um die Aufgabe zu verstehen, individuellere Antworten zu liefern und spezifische Richtlinien für den zu sendenden Prompt einzuhalten.",
"description": ""
},
"prefs_OptionText_btnManagePrompts_infoline3": {
"message": "Sie können den Platzhalter {%tags_full_list%} im Prompt verwenden, um die verfügbaren Tags aufzulisten. Mit einem geeigneten Prompt könnten Sie dann erzwingen, dass die Tags nur aus der Liste der bereits vorhandenen ausgewählt werden.",
"description": ""
},
"placeholder_mail_typed_text": {
"message": "Eingegebener Text vor dem zitierten E-Mail-Text",
"description": ""
},
"prompt_get_calendar_event": {
"message": "Ein neues Kalenderevent hinzufügen",
"description": ""
},
"prompt_get_calendar_event_full_text": {
"message": "Extrahieren Sie alle relevanten Details, die erforderlich sind, um ein Kalenderevent aus dem folgenden Text zu erstellen. Die extrahierten Informationen sollten Folgendes enthalten:\n- Ereignistitel\n- Startdatum und -uhrzeit (einschließlich Zeitzone, falls angegeben)\n- Enddatum und -uhrzeit (einschließlich Zeitzone, falls angegeben)\n- Ganztägig (falls erwähnt)\nStellen Sie sicher, dass die Daten klar und konsistent formatiert sind, sodass sie direkt für die Erstellung eines Kalenderevents verwendet werden können. Falls relative Zeitangaben enthalten sind, beachten Sie, dass Datum und Uhrzeit der E-Mail \"{%mail_datetime%}\" sind. Berechnen Sie das Startdatum und die Startzeit basierend auf diesem Bezugspunkt. Falls das berechnete Startdatum und die Startzeit vor \"{%current_datetime%}\" liegen, berechnen Sie Startdatum und -zeit erneut, wobei Sie \"{%current_datetime%}\" als Grundlage verwenden. Falls keine Dauer angegeben ist, setzen Sie sie auf eine Stunde. Falls Sie eine oder mehrere der erforderlichen Informationen nicht erhalten können, antworten Sie mit einem leeren String.\nErstellen Sie eine Antwort ausschließlich im JSON-Format. Fügen Sie keinen zusätzlichen Text oder Erklärungen hinzu; geben Sie nur das JSON an. Verwenden Sie folgendes Format:\n{\n \"startDate\": \"YYYYMMDDTHHMMSS\",\n \"endDate\": \"YYYYMMDDTHHMMSS\",\n \"summary\": \"Zusammenfassung des Kalenderevents hier\",\n \"forceAllDay\": false\n}\nHier ist der Text: \"{%selected_text%}\"",
"description": ""
},
"prefs_OptionText_get_calendar_event": {
"message": "Ein neues Kalenderevent aus ausgewähltem Text hinzufügen",
"description": ""
},
"prefs_OptionText_get_calendar_event_Info": {
"message": "Wenn aktiviert, wird ein Element im Menü hinzugefügt, um Kalenderevent-Informationen aus ausgewähltem Text zu erhalten.",
"description": ""
},
"prefs_OptionText_btnManageCalendarEventInfo": {
"message": "Kalendereinstellungen verwalten",
"description": ""
},
"GetCalendarEvent_PageTitle": {
"message": "Kalendereinstellungen verwalten",
"description": ""
},
"GetCalendarEvent_info_default": {
"message": "Auf dieser Seite können Sie den Standard-Prompt ändern, der verwendet wird, um ein Kalenderevent aus ausgewähltem Text zu erhalten.",
"description": ""
},
"GetCalendarEvent_prompt_text_title": {
"message": "Aktueller Prompt-Text",
"description": ""
},
"prefs_OptionText_GetCalendarEvent_infoline2": {
"message": "Sie können den Prompt nach Belieben ändern, aber die von der KI erhaltene Antwort muss im JSON-Format sein, wie im Standard-Prompt angegeben!",
"description": ""
},
"prefs_OptionText_get_calendar_event_Sparks_not_present": {
"message": "Um die Kalenderevent-Funktion zu nutzen, installieren Sie bitte das ThunderAI Sparks-Addon.",
"description": ""
},
"prefs_OptionText_download_now": {
"message": "Laden Sie jetzt ThunderAI Sparks herunter!",
"description": ""
},
"placeholder_mail_datetime": {
"message": "Datum und Uhrzeit der E-Mail",
"description": ""
},
"placeholder_current_datetime": {
"message": "Das aktuelle Datum und die aktuelle Uhrzeit",
"description": ""
},
"calendar_getting_data_error": {
"message": "Fehler beim Abrufen der Kalenderevent-Daten",
"description": ""
},
"calendar_opening_dialog_error": {
"message": "Fehler beim Öffnen des Kalenderevent-Dialogs",
"description": ""
}
}

View file

@ -894,5 +894,125 @@
"prompt_add_tags_force_lang": {
"message": "The tags must be written in",
"description": ""
},
"prefs_Connection_type_Google_Gemini_API": {
"message": "Google Gemini API",
"description": ""
},
"prefs_GoogleGemini_API_Key": {
"message": "API Key",
"description": ""
},
"GoogleGemini_Models": {
"message": "Google Gemini API Models",
"description": ""
},
"GoogleGemini_Models_Fetch": {
"message": "Update Google Gemini Models list",
"description": ""
},
"GoogleGemini_Models_Error_fetching": {
"message": "Error trying to fetch Google Gemini models",
"description": ""
},
"google_gemini_api_request_failed": {
"message": "Google Gemini API request failed",
"description": ""
},
"google_gemini_api_connecting": {
"message": "Attempting to connect to Google Gemini using the API key provided",
"description": ""
},
"google_gemini_empty_apikey": {
"message": "You've not added an API Key for the Google Gemini API. Please insert one in the options page.",
"description": ""
},
"google_gemini_empty_model": {
"message": "You've not choosen a model for the Google Gemini API. Please choose one in the options page.",
"description": ""
},
"GoogleGemini_SystemInstruction": {
"message": "System Instruction",
"description": ""
},
"GoogleGemini_SystemInstruction_Info": {
"message": "When you set a system instruction, you give the model additional context to understand the task, provide more customized responses, and adhere to specific guidelines over the prompt that will be sent.",
"description": ""
},
"ChatGPT_Developer_Messages": {
"message": "Developer Messages",
"description": ""
},
"ChatGPT_Developer_Messages_Info": {
"message": "When you set the developer messages, you give the model additional context to understand the task, provide more customized responses, and adhere to specific guidelines over the prompt that will be sent.",
"description": ""
},
"prefs_OptionText_btnManagePrompts_infoline3": {
"message": "You can use the {%tags_full_list%} data placeholder in the prompt to list the available tags. With an appropriate prompt, you could then force the tags to be chosen only from the list of those already existing.",
"description": ""
},
"placeholder_mail_typed_text": {
"message": "Typed text before the quoted mail body",
"description": ""
},
"prompt_get_calendar_event": {
"message": "Add a new calendar event",
"description": ""
},
"prompt_get_calendar_event_full_text": {
"message": "Extract all relevant details required to generate a calendar event from the following text. The extracted information should include:\n- Event Title\n- Start Date and Time (including timezone, if specified)\n- End Date and Time (including timezone, if specified)\n- Full day (if mentioned)\nEnsure the data is formatted clearly and consistently so that it can be directly used for creating a calendar event.\nIf there are relative time references, consider that the date and time of the email are \"{%mail_datetime%}\". Calculate the start date and time based on this reference. If the calculated start date and time are earlier than \"{%current_datetime%}\", recalculate the start date and time using \"{%current_datetime%}\" as the base.\nIf the duration is not specified, set it to one hour.\nIf you're not able to get one or more of the required information, please respond with an empty string.\nGenerate a response in JSON format only. Do not include any additional text or explanations; provide only the JSON. Here is the format to be used:\n{\n\"startDate\": \"YYYYMMDDTHHMMSS\",\n\"endDate\": \"YYYYMMDDTHHMMSS\",\n\"summary\": \"Calendar event summary here\",\n\"forceAllDay\": false\n}\nHere's the text:\"{%selected_text%}\"",
"description": ""
},
"prefs_OptionText_get_calendar_event": {
"message": "Add a new calendar event from selected text",
"description": ""
},
"prefs_OptionText_get_calendar_event_Info": {
"message": "If checked, an item will be included in the menu to get a calendar event information from selected text.",
"description": ""
},
"prefs_OptionText_btnManageCalendarEventInfo": {
"message": "Manage calendar events settings",
"description": ""
},
"GetCalendarEvent_PageTitle": {
"message": "Manage Calendar Events Settings",
"description": ""
},
"GetCalendarEvent_info_default": {
"message": "In this page you can modify the default prompt used to get a calendar event from selected text.",
"description": ""
},
"GetCalendarEvent_prompt_text_title": {
"message": "Current prompt text",
"description": ""
},
"prefs_OptionText_GetCalendarEvent_infoline2": {
"message": "You can change the prompt as you wish, but the response received from the AI must be in JSON format as specified in the default prompt!",
"description": ""
},
"prefs_OptionText_get_calendar_event_Sparks_not_present": {
"message": "To use the calendar event feature, please install the ThunderAI Sparks addon.",
"description": ""
},
"prefs_OptionText_download_now": {
"message": "Download ThunderAI Sparks now!",
"description": ""
},
"placeholder_mail_datetime": {
"message": "The date and time of the email",
"description": ""
},
"placeholder_current_datetime": {
"message": "The current date and time",
"description": ""
},
"calendar_getting_data_error": {
"message": "Error getting calendar event data",
"description": ""
},
"calendar_opening_dialog_error": {
"message": "Error opening the calendar event dialog",
"description": ""
}
}

View file

@ -340,7 +340,7 @@
"description": ""
},
"prefsDonation_1": {
"message": "Aimez-vous cet addon ?",
"message": "Aimez-vous cette extension ?",
"description": ""
},
"prefsDonation_2": {
@ -352,7 +352,7 @@
"description": ""
},
"TranslateText": {
"message": "Voulez-vous aider à traduire cet addon ?",
"message": "Voulez-vous aider à traduire cette extension ?",
"description": ""
},
"TranslateLink": {
@ -364,27 +364,27 @@
"description": ""
},
"customPrompts_Import": {
"message": "Importer de nouvelles prompts",
"message": "Importer de nouveaux prompts",
"description": ""
},
"importPrompts_confirmText": {
"message": "Vous êtes sur le point d'importer de nouvelles prompts.",
"message": "Vous êtes sur le point d'importer de nouveaux prompts.",
"description": ""
},
"customPrompts_start_import": {
"message": "Début de l'importation des prompts personnalisées...",
"message": "Début de l'importation des prompts personnalisés...",
"description": ""
},
"customPrompts_import_completed": {
"message": "Importation des prompts personnalisées terminée ! Vous devez cliquer sur le bouton 'Enregistrer tout' pour enregistrer vos modifications.",
"message": "Importation des prompts personnalisés terminée ! Vous devez cliquer sur le bouton 'Enregistrer tout' pour enregistrer vos modifications.",
"description": ""
},
"importPrompts_invalidFile": {
"message": "Le fichier que vous essayez d'importer n'est pas un fichier de prompts personnalisées valide.",
"message": "Le fichier que vous essayez d'importer n'est pas un fichier de prompts personnalisés valide.",
"description": ""
},
"importPrompts_invalidPrompts": {
"message": "Le fichier que vous essayez d'importer ne contient aucune prompt valide.",
"message": "Le fichier que vous essayez d'importer ne contient aucun prompt valide.",
"description": ""
},
"currently_used_prompt": {
@ -560,11 +560,11 @@
"description": ""
},
"SearchPrompt": {
"message": "Rechercher des invites",
"message": "Rechercher des prompts",
"description": ""
},
"prefs_OptionText_dynamic_menu_force_enter": {
"message": "Menu : Envoi immédiat de l'invite",
"message": "Menu : Envoi immédiat du prompt",
"description": ""
},
"prefs_OptionText_dynamic_menu_force_enter_info": {
@ -576,7 +576,7 @@
"description": ""
},
"prefs_OptionText_dynamic_menu_order_alphabet_info": {
"message": "Si cette option est cochée, les invites dans le menu seront triées par ordre alphabétique.",
"message": "Si cette option est cochée, les prompts dans le menu seront triés par ordre alphabétique.",
"description": ""
},
"prefs_OptionText_chatgpt_win_dims_info": {
@ -644,7 +644,7 @@
"description": ""
},
"prefs_OptionText_max_prompt_length": {
"message": "Longueur maximale de l'invite",
"message": "Longueur maximale du prompt",
"description": ""
},
"prefs_OptionText_max_prompt_length_Info": {
@ -684,7 +684,7 @@
"description": ""
},
"prefs_OptionText_btnManagePrompts_infoline2": {
"message": "Vous pouvez modifier l'invite comme vous le souhaitez, mais la réponse de l'IA doit être une liste de tags séparés par des virgules !",
"message": "Vous pouvez modifier le prompt comme vous le souhaitez, mais la réponse de l'IA doit être une liste de tags séparés par des virgules !",
"description": ""
},
"prefs_OptionText_openai_comp_use_v1": {
@ -804,7 +804,7 @@
"description": ""
},
"AddTags_prompt_text_title": {
"message": "Texte de l'invite actuelle",
"message": "Texte du prompt actuel",
"description": ""
},
"AddTags_excl_list_title": {
@ -820,7 +820,7 @@
"description": ""
},
"addtags_info_additional_statements": {
"message": "Cette déclaration sera ajoutée à la fin de l'invite :",
"message": "Cette phrase sera ajoutée à la fin du prompt :",
"description": ""
},
"reset_default": {
@ -888,11 +888,131 @@
"description": ""
},
"prefs_OptionText_add_tags_force_lang_Info": {
"message": "Si cette option est cochée, la langue des tags sera forcée pour correspondre à celle définie dans les options du ThunderAI, si spécifiée.",
"description": ""
"message": "Si cette option est cochée, la langue des tags sera forcée pour correspondre à celle définie dans les options du ThunderAI, si spécifiée.",
"description": ""
},
"prompt_add_tags_force_lang": {
"message": "Les tags doivent être écrits en",
"description": ""
"message": "Les tags doivent être écrits en",
"description": ""
},
"prefs_Connection_type_Google_Gemini_API": {
"message": "API Google Gemini",
"description": ""
},
"prefs_GoogleGemini_API_Key": {
"message": "Clé API",
"description": ""
},
"GoogleGemini_Models": {
"message": "Modèles d'API Google Gemini",
"description": ""
},
"GoogleGemini_Models_Fetch": {
"message": "Mettre à jour la liste des modèles Google Gemini",
"description": ""
},
"GoogleGemini_Models_Error_fetching": {
"message": "Erreur lors de la tentative de récupération des modèles Google Gemini",
"description": ""
},
"google_gemini_api_request_failed": {
"message": "La requête API Google Gemini a échoué",
"description": ""
},
"google_gemini_api_connecting": {
"message": "Tentative de connexion à Google Gemini en utilisant la clé API fournie",
"description": ""
},
"google_gemini_empty_apikey": {
"message": "Vous n'avez pas ajouté de clé API pour l'API Google Gemini. Veuillez en insérer une dans la page des options.",
"description": ""
},
"google_gemini_empty_model": {
"message": "Vous n'avez pas choisi de modèle pour l'API Google Gemini. Veuillez en choisir un dans la page des options.",
"description": ""
},
"GoogleGemini_SystemInstruction": {
"message": "Instruction système",
"description": ""
},
"GoogleGemini_SystemInstruction_Info": {
"message": "Lorsque vous définissez une instruction système, vous fournissez au modèle un contexte supplémentaire pour comprendre la tâche, fournir des réponses plus personnalisées et respecter des directives spécifiques pour l'invite qui sera envoyée.",
"description": ""
},
"ChatGPT_Developer_Messages": {
"message": "Messages des développeurs",
"description": ""
},
"ChatGPT_Developer_Messages_Info": {
"message": "Lorsque vous définissez les messages des développeurs, vous fournissez au modèle un contexte supplémentaire pour comprendre la tâche, fournir des réponses plus personnalisées et respecter des directives spécifiques pour l'invite qui sera envoyée.",
"description": ""
},
"prefs_OptionText_btnManagePrompts_infoline3": {
"message": "Vous pouvez utiliser le placeholder de données {%tags_full_list%} dans l'invite pour lister les balises disponibles. Avec une invite appropriée, vous pourriez ensuite forcer le choix des balises uniquement parmi celles déjà existantes.",
"description": ""
},
"placeholder_mail_typed_text": {
"message": "Texte saisi avant le corps du mail cité",
"description": ""
},
"prompt_get_calendar_event": {
"message": "Ajouter un nouvel événement au calendrier",
"description": ""
},
"prompt_get_calendar_event_full_text": {
"message": "Extrayez tous les détails pertinents nécessaires pour générer un événement de calendrier à partir du texte suivant. Les informations extraites doivent inclure :\n- Titre de l'événement\n- Date et heure de début (y compris le fuseau horaire, si spécifié)\n- Date et heure de fin (y compris le fuseau horaire, si spécifié)\n- Journée entière (si mentionnée) Assurez-vous que les données sont formatées clairement et de manière cohérente afin qu'elles puissent être utilisées directement pour créer un événement de calendrier.\nS'il y a des références temporelles relatives, considérez que la date et l'heure de l'email sont \"{%mail_datetime%}\".\nCalculez la date et l'heure de début sur cette base.\nSi la date et l'heure de début calculées sont antérieures à \"{%current_datetime%}\", recalculez-les en utilisant \"{%current_datetime%}\" comme base.\nSi la durée n'est pas spécifiée, définissez-la sur une heure.\nSi vous n'êtes pas en mesure d'obtenir une ou plusieurs des informations requises, répondez avec une chaîne vide.\nGénérez une réponse uniquement au format JSON.\nN'incluez aucun texte ou explication supplémentaire ; fournissez uniquement le JSON. Voici le format à utiliser :\n{\n\"startDate\": \"YYYYMMDDTHHMMSS\",\n\"endDate\": \"YYYYMMDDTHHMMSS\",\n\"summary\": \"Résumé de l'événement du calendrier ici\",\n\"forceAllDay\": false\n}\nVoici le texte : \"{%selected_text%}\"",
"description": ""
},
"prefs_OptionText_get_calendar_event": {
"message": "Ajouter un nouvel événement au calendrier à partir du texte sélectionné",
"description": ""
},
"prefs_OptionText_get_calendar_event_Info": {
"message": "Si coché, un élément sera inclus dans le menu pour obtenir des informations sur un événement de calendrier à partir du texte sélectionné.",
"description": ""
},
"prefs_OptionText_btnManageCalendarEventInfo": {
"message": "Gérer les paramètres des événements du calendrier",
"description": ""
},
"GetCalendarEvent_PageTitle": {
"message": "Gérer les paramètres des événements du calendrier",
"description": ""
},
"GetCalendarEvent_info_default": {
"message": "Sur cette page, vous pouvez modifier l'invite par défaut utilisée pour obtenir un événement de calendrier à partir du texte sélectionné.",
"description": ""
},
"GetCalendarEvent_prompt_text_title": {
"message": "Texte du prompt actuel",
"description": ""
},
"prefs_OptionText_GetCalendarEvent_infoline2": {
"message": "Vous pouvez modifier le prompt comme vous le souhaitez, mais la réponse reçue de l'IA doit être au format JSON tel que spécifié dans l'invite par défaut !",
"description": ""
},
"prefs_OptionText_get_calendar_event_Sparks_not_present": {
"message": "Pour utiliser la fonctionnalité d'événements de calendrier, veuillez installer l'addon ThunderAI Sparks.",
"description": ""
},
"prefs_OptionText_download_now": {
"message": "Téléchargez ThunderAI Sparks maintenant !",
"description": ""
},
"placeholder_mail_datetime": {
"message": "La date et l'heure de l'email",
"description": ""
},
"placeholder_current_datetime": {
"message": "La date et l'heure actuelles",
"description": ""
},
"calendar_getting_data_error": {
"message": "Erreur lors de l'obtention des données de l'événement du calendrier",
"description": ""
},
"calendar_opening_dialog_error": {
"message": "Erreur lors de l'ouverture de la boîte de dialogue de l'événement du calendrier",
"description": ""
}
}

View file

@ -888,11 +888,131 @@
"description": ""
},
"prefs_OptionText_add_tags_force_lang_Info": {
"message": "Se selezionato, la lingua dei tag sarà forzata a corrispondere a quella definita nella pagina delle opzioni di ThunderAI, se specificata.",
"description": ""
"message": "Se selezionato, la lingua dei tag sarà forzata a corrispondere a quella definita nella pagina delle opzioni di ThunderAI, se specificata.",
"description": ""
},
"prompt_add_tags_force_lang": {
"message": "I tag devono essere scritti in",
"description": ""
"message": "I tag devono essere scritti in",
"description": ""
},
"prefs_Connection_type_Google_Gemini_API": {
"message": "API Google Gemini",
"description": ""
},
"prefs_GoogleGemini_API_Key": {
"message": "Chiave API",
"description": ""
},
"GoogleGemini_Models": {
"message": "Modelli API Google Gemini",
"description": ""
},
"GoogleGemini_Models_Fetch": {
"message": "Aggiorna l'elenco dei modelli Google Gemini",
"description": ""
},
"GoogleGemini_Models_Error_fetching": {
"message": "Errore durante il tentativo di recuperare i modelli Google Gemini",
"description": ""
},
"google_gemini_api_request_failed": {
"message": "La richiesta all'API Google Gemini è fallita",
"description": ""
},
"google_gemini_api_connecting": {
"message": "Tentativo di connessione a Google Gemini utilizzando la chiave API fornita",
"description": ""
},
"google_gemini_empty_apikey": {
"message": "Non hai aggiunto una chiave API per l'API Google Gemini. Inseriscine una nella pagina delle opzioni.",
"description": ""
},
"google_gemini_empty_model": {
"message": "Non hai scelto un modello per l'API Google Gemini. Scegline uno nella pagina delle opzioni.",
"description": ""
},
"GoogleGemini_SystemInstruction": {
"message": "Istruzione di sistema",
"description": ""
},
"GoogleGemini_SystemInstruction_Info": {
"message": "Quando imposti un'istruzione di sistema, fornisci al modello un contesto aggiuntivo per comprendere il compito, fornire risposte più personalizzate e rispettare linee guida specifiche per l'invito che verrà inviato.",
"description": ""
},
"ChatGPT_Developer_Messages": {
"message": "Messaggi degli sviluppatori",
"description": ""
},
"ChatGPT_Developer_Messages_Info": {
"message": "Quando imposti i messaggi degli sviluppatori, fornisci al modello un contesto aggiuntivo per comprendere il compito, fornire risposte più personalizzate e rispettare linee guida specifiche per l'invito che verrà inviato.",
"description": ""
},
"prefs_OptionText_btnManagePrompts_infoline3": {
"message": "Puoi usare il segnaposto dati {%tags_full_list%} nell'invito per elencare i tag disponibili. Con un prompt appropriato, potresti forzare la scelta dei tag solo dall'elenco di quelli già esistenti.",
"description": ""
},
"placeholder_mail_typed_text": {
"message": "Testo digitato prima del corpo dell'email citata",
"description": ""
},
"prompt_get_calendar_event": {
"message": "Aggiungi un nuovo evento al calendario",
"description": ""
},
"prompt_get_calendar_event_full_text": {
"message": "Estrai tutti i dettagli rilevanti necessari per generare un evento del calendario dal seguente testo.\nLe informazioni estratte devono includere:\n- Titolo dell'evento\n- Data e ora di inizio (incluso il fuso orario, se specificato)\n- Data e ora di fine (incluso il fuso orario, se specificato)\n- Giornata intera (se menzionato)\nAssicurati che i dati siano formattati in modo chiaro e coerente in modo che possano essere utilizzati direttamente per creare un evento del calendario.\nSe ci sono riferimenti temporali relativi, considera che la data e l'ora dell'email sono \"{%mail_datetime%}\". Calcola la data e l'ora di inizio in base a questo riferimento. Se la data e l'ora di inizio calcolate sono precedenti a \"{%current_datetime%}\", ricalcolale utilizzando \"{%current_datetime%}\" come base.\nSe la durata non è specificata, impostala a un'ora.\nSe non riesci a ottenere una o più delle informazioni richieste, rispondi con una stringa vuota.\nGenera una risposta solo in formato JSON. Non includere testo o spiegazioni aggiuntive; fornisci solo il JSON.\nEcco il formato da utilizzare:\n{\n\"startDate\": \"YYYYMMDDTHHMMSS\",\n\"endDate\": \"YYYYMMDDTHHMMSS\",\n\"summary\": \"Riassunto evento calendario qui\",\n\"forceAllDay\": false\n}\nEcco il testo: \"{%selected_text%}\"",
"description": ""
},
"prefs_OptionText_get_calendar_event": {
"message": "Aggiungi un nuovo evento al calendario dal testo selezionato",
"description": ""
},
"prefs_OptionText_get_calendar_event_Info": {
"message": "Se selezionato, sarà inclusa una voce del menu per ottenere informazioni su un evento del calendario dal testo selezionato.",
"description": ""
},
"prefs_OptionText_btnManageCalendarEventInfo": {
"message": "Gestisci le impostazioni degli eventi del calendario",
"description": ""
},
"GetCalendarEvent_PageTitle": {
"message": "Gestisci le impostazioni degli eventi del calendario",
"description": ""
},
"GetCalendarEvent_info_default": {
"message": "In questa pagina puoi modificare il prompt predefinito utilizzato per ottenere un evento del calendario dal testo selezionato.",
"description": ""
},
"GetCalendarEvent_prompt_text_title": {
"message": "Testo attuale del prompt",
"description": ""
},
"prefs_OptionText_GetCalendarEvent_infoline2": {
"message": "Puoi modificare il prompt come preferisci, ma la risposta ricevuta dall'AI deve essere in formato JSON come specificato nel prompt predefinito!",
"description": ""
},
"prefs_OptionText_get_calendar_event_Sparks_not_present": {
"message": "Per utilizzare la funzionalità degli eventi del calendario, installa il componente aggiuntivo ThunderAI Sparks.",
"description": ""
},
"prefs_OptionText_download_now": {
"message": "Scarica subito ThunderAI Sparks!",
"description": ""
},
"placeholder_mail_datetime": {
"message": "La data e l'ora dell'email",
"description": ""
},
"placeholder_current_datetime": {
"message": "La data e l'ora attuali",
"description": ""
},
"calendar_getting_data_error": {
"message": "Errore durante il recupero dei dati dell'evento del calendario",
"description": ""
},
"calendar_opening_dialog_error": {
"message": "Errore durante l'apertura della finestra di dialogo dell'evento del calendario",
"description": ""
}
}

View file

@ -888,11 +888,131 @@
"description": ""
},
"prefs_OptionText_add_tags_force_lang_Info": {
"message": "Jeśli zaznaczone, język tagów zostanie wymuszony na zgodny z językiem określonym na stronie opcji ThunderAI, jeśli został zdefiniowany.",
"description": ""
"message": "Jeśli zaznaczone, język tagów zostanie wymuszony na zgodny z językiem określonym na stronie opcji ThunderAI, jeśli został zdefiniowany.",
"description": ""
},
"prompt_add_tags_force_lang": {
"message": "Tagi muszą być napisane w",
"description": ""
"message": "Tagi muszą być napisane w",
"description": ""
},
"GoogleGemini_Models_Fetch": {
"message": "Zaktualizuj listę modeli Google Gemini",
"description": ""
},
"GoogleGemini_Models_Error_fetching": {
"message": "Błąd podczas próby pobrania modeli Google Gemini",
"description": ""
},
"google_gemini_api_request_failed": {
"message": "Połączenie do interfejsu API Google Gemini nie powiodło się",
"description": ""
},
"google_gemini_api_connecting": {
"message": "Próba połączenia z Google Gemini przy użyciu dostarczonego klucza API",
"description": ""
},
"google_gemini_empty_apikey": {
"message": "Nie dodałeś klucza API dla API Google Gemini. Wstaw go na stronie opcji.",
"description": ""
},
"google_gemini_empty_model": {
"message": "Nie wybrałeś modelu interfejsu API Google Gemini. Wybierz jedną na stronie opcji.",
"description": ""
},
"GoogleGemini_SystemInstruction": {
"message": "Instrukcja systemowa",
"description": ""
},
"GoogleGemini_SystemInstruction_Info": {
"message": "Ustawiając instrukcję systemową, dajesz modelowi dodatkowy kontekst umożliwiający zrozumienie zadania, zapewnianie bardziej dostosowanych odpowiedzi i przestrzeganie określonych wytycznych dotyczących zapytania, które zostanie wysłane.",
"description": ""
},
"ChatGPT_Developer_Messages": {
"message": "Wiadomości programistów",
"description": ""
},
"ChatGPT_Developer_Messages_Info": {
"message": "Konfigurując komunikaty dla programistów, dajesz modelowi dodatkowy kontekst umożliwiający zrozumienie zadania, zapewnianie bardziej dostosowanych odpowiedzi i przestrzeganie określonych wytycznych dotyczących zapytania, które zostanie wysłane.",
"description": ""
},
"prefs_OptionText_btnManagePrompts_infoline3": {
"message": "Aby wyświetlić listę dostępnych tagów, możesz użyć obiektu danych {%tags_full_list%} w zapytaniu. Za pomocą odpowiedniego zapytania można następnie wymusić wybranie tagów tylko z listy już istniejących.",
"description": ""
},
"placeholder_mail_typed_text": {
"message": "Wpisano tekst przed cytowaną treścią wiadomości",
"description": ""
},
"prompt_get_calendar_event": {
"message": "Dodaj nowe wydarzenie w kalendarzu",
"description": ""
},
"prompt_get_calendar_event_full_text": {
"message": "Wyodrębnij wszystkie istotne szczegóły wymagane do wygenerowania wydarzenia w kalendarzu z poniższego tekstu.\nWyodrębnione informacje powinny obejmować:\n- Tytuł wydarzenia\n- Datę i godzinę rozpoczęcia (w tym strefę czasową, jeśli została określona)\n- Datę i godzinę zakończenia (w tym strefę czasową, jeśli została określona)\n- Cały dzień (jeśli jest podany) Upewnij się, że dane są sformatowane w sposób jasny i spójny, tak aby można go bezpośrednio wykorzystać do utworzenia wydarzenia w kalendarzu. Jeśli istnieją odniesienia do czasu względnego, pamiętaj, że data i godzina wysłania wiadomości e-mail to „{%mail_datetime%}”. Oblicz datę i godzinę rozpoczęcia na podstawie tego odniesienia. Jeśli obliczona data i godzina rozpoczęcia są wcześniejsze niż „{%current_datetime%}”, oblicz ponownie datę i godzinę rozpoczęcia, stosując jako podstawę „{%current_datetime%}”. Jeśli czas trwania nie jest określony, ustaw go na jedną godzinę. Jeśli nie możesz uzyskać co najmniej jednej z wymaganych informacji, w odpowiedzi wpisz pusty ciąg znaków. Wygeneruj odpowiedź tylko w formacie JSON. Nie dołączaj żadnego dodatkowego tekstu ani wyjaśnień; podaj tylko JSON. Oto format, którego należy użyć:\n{\n\"startDate\": \"YYYYMMDDTHHMMSS\",\n\"endDate\": \"YYYYMMDDTHHMMSS\",\n\"summary\": \"Tutaj podsumowanie wydarzenia w kalendarzu\",\n\"forceAllDay\": false\n}\nOto tekst:\"{%selected_text %}\"",
"description": ""
},
"prefs_OptionText_get_calendar_event": {
"message": "Dodaj nowe wydarzenie w kalendarzu z zaznaczonego tekstu",
"description": ""
},
"prefs_OptionText_get_calendar_event_Info": {
"message": "Jeśli zaznaczone, pozycja zostanie uwzględniona w menu, aby uzyskać informacje o wydarzeniu w kalendarzu z zaznaczonego tekstu.",
"description": ""
},
"prefs_OptionText_btnManageCalendarEventInfo": {
"message": "Zarządzaj ustawieniami wydarzeń w kalendarzu",
"description": ""
},
"GetCalendarEvent_PageTitle": {
"message": "Zarządzaj ustawieniami wydarzeń w kalendarzu",
"description": ""
},
"GetCalendarEvent_info_default": {
"message": "Na tej stronie możesz zmodyfikować domyślny monit używany do pobierania wydarzenia w kalendarzu z zaznaczonego tekstu.",
"description": ""
},
"GetCalendarEvent_prompt_text_title": {
"message": "Bieżący tekst zapytania",
"description": ""
},
"prefs_OptionText_GetCalendarEvent_infoline2": {
"message": "Możesz zmienić monit według własnego uznania, ale odpowiedź otrzymana od AI musi być w formacie JSON, jak określono w domyślnym zapytaniu!",
"description": ""
},
"prefs_OptionText_get_calendar_event_Sparks_not_present": {
"message": "Aby korzystać z funkcji wydarzeń w kalendarzu, zainstaluj dodatek ThunderAI Sparks.",
"description": ""
},
"prefs_OptionText_download_now": {
"message": "Pobierz teraz ThunderAI Sparks!",
"description": ""
},
"placeholder_mail_datetime": {
"message": "Data i godzina wysłania wiadomości e-mail",
"description": ""
},
"placeholder_current_datetime": {
"message": "Bieżąca data i godzina",
"description": ""
},
"calendar_getting_data_error": {
"message": "Błąd podczas pobierania wydarzeń w kalendarzu",
"description": ""
},
"calendar_opening_dialog_error": {
"message": "Błąd podczas otwierania okna wydarzenia w kalendarzu",
"description": ""
},
"GoogleGemini_Models": {
"message": "Modele Google Gemini",
"description": "Lista modeli interfejsu API Google Gemini"
},
"prefs_Connection_type_Google_Gemini_API": {
"message": "Interfejs API Google Gemini",
"description": "Typ połączenia: Google Gemini API"
},
"prefs_GoogleGemini_API_Key": {
"message": "Klucz API Google Gemini",
"description": "Klucz API dla interfejsu API Google Gemini"
}
}

View file

@ -888,11 +888,131 @@
"description": ""
},
"prefs_OptionText_add_tags_force_lang_Info": {
"message": "Se marcado, o idioma das tags será forçado a corresponder ao idioma definido na página de opções do ThunderAI, se especificado.",
"description": ""
"message": "Se marcado, o idioma das tags será forçado a corresponder ao idioma definido na página de opções do ThunderAI, se especificado.",
"description": ""
},
"prompt_add_tags_force_lang": {
"message": "As tags devem ser escritas em",
"description": ""
"message": "As tags devem ser escritas em",
"description": ""
},
"prefs_Connection_type_Google_Gemini_API": {
"message": "API Google Gemini",
"description": ""
},
"prefs_GoogleGemini_API_Key": {
"message": "Chave API",
"description": ""
},
"GoogleGemini_Models": {
"message": "Modelos da API Google Gemini",
"description": ""
},
"GoogleGemini_Models_Fetch": {
"message": "Atualizar a lista de modelos Google Gemini",
"description": ""
},
"GoogleGemini_Models_Error_fetching": {
"message": "Erro ao tentar buscar os modelos Google Gemini",
"description": ""
},
"google_gemini_api_request_failed": {
"message": "A solicitação para a API Google Gemini falhou",
"description": ""
},
"google_gemini_api_connecting": {
"message": "Tentando conectar ao Google Gemini usando a chave API fornecida",
"description": ""
},
"google_gemini_empty_apikey": {
"message": "Você não adicionou uma chave API para a API Google Gemini. Por favor, insira uma na página de opções.",
"description": ""
},
"google_gemini_empty_model": {
"message": "Você não escolheu um modelo para a API Google Gemini. Por favor, escolha um na página de opções.",
"description": ""
},
"GoogleGemini_SystemInstruction": {
"message": "Instrução do sistema",
"description": ""
},
"GoogleGemini_SystemInstruction_Info": {
"message": "Ao configurar uma instrução do sistema, você fornece ao modelo um contexto adicional para entender a tarefa, oferecer respostas mais personalizadas e aderir a diretrizes específicas para o prompt que será enviado.",
"description": ""
},
"ChatGPT_Developer_Messages": {
"message": "Mensagens do desenvolvedor",
"description": ""
},
"ChatGPT_Developer_Messages_Info": {
"message": "Ao configurar as mensagens do desenvolvedor, você fornece ao modelo um contexto adicional para entender a tarefa, oferecer respostas mais personalizadas e aderir a diretrizes específicas para o prompt que será enviado.",
"description": ""
},
"prefs_OptionText_btnManagePrompts_infoline3": {
"message": "Você pode usar o espaço reservado de dados {%tags_full_list%} no prompt para listar as tags disponíveis. Com um prompt apropriado, você pode forçar que as tags sejam escolhidas apenas da lista das que já existem.",
"description": ""
},
"placeholder_mail_typed_text": {
"message": "Texto digitado antes do corpo do e-mail citado",
"description": ""
},
"prompt_get_calendar_event": {
"message": "Adicionar um novo evento ao calendário",
"description": ""
},
"prompt_get_calendar_event_full_text": {
"message": "Extraia todos os detalhes relevantes necessários para gerar um evento de calendário a partir do texto a seguir.\nAs informações extraídas devem incluir:\n- Título do evento\n- Data e hora de início (incluindo fuso horário, se especificado)\n- Data e hora de término (incluindo fuso horário, se especificado)\n- Dia inteiro (se mencionado)\nCertifique-se de que os dados estejam formatados de maneira clara e consistente para que possam ser usados diretamente para criar um evento de calendário.\nSe houver referências de tempo relativas, considere que a data e a hora do e-mail são \"{%mail_datetime%}\".\nCalcule a data e a hora de início com base nessa referência. Se a data e a hora de início calculadas forem anteriores a \"{%current_datetime%}\", recalcule a data e a hora de início usando \"{%current_datetime%}\" como base.\nSe a duração não for especificada, defina-a como uma hora.\nSe você não conseguir obter uma ou mais informações necessárias, responda com uma string vazia.\nGere uma resposta apenas no formato JSON. Não inclua texto ou explicações adicionais; forneça apenas o JSON. Aqui está o formato a ser usado:\n{\n\"startDate\": \"YYYYMMDDTHHMMSS\",\n\"endDate\": \"YYYYMMDDTHHMMSS\",\n\"summary\": \"Resumo do evento do calendário aqui\",\n\"forceAllDay\": false\n}\nAqui está o texto: \"{%selected_text%}\"",
"description": ""
},
"prefs_OptionText_get_calendar_event": {
"message": "Adicionar um novo evento ao calendário a partir do texto selecionado",
"description": ""
},
"prefs_OptionText_get_calendar_event_Info": {
"message": "Se marcado, um item será incluído no menu para obter informações de um evento de calendário a partir do texto selecionado.",
"description": ""
},
"prefs_OptionText_btnManageCalendarEventInfo": {
"message": "Gerenciar configurações de eventos do calendário",
"description": ""
},
"GetCalendarEvent_PageTitle": {
"message": "Gerenciar configurações de eventos do calendário",
"description": ""
},
"GetCalendarEvent_info_default": {
"message": "Nesta página, você pode modificar o prompt padrão usado para obter um evento de calendário a partir do texto selecionado.",
"description": ""
},
"GetCalendarEvent_prompt_text_title": {
"message": "Texto do prompt atual",
"description": ""
},
"prefs_OptionText_GetCalendarEvent_infoline2": {
"message": "Você pode alterar o prompt como desejar, mas a resposta recebida da IA deve estar no formato JSON conforme especificado no prompt padrão!",
"description": ""
},
"prefs_OptionText_get_calendar_event_Sparks_not_present": {
"message": "Para usar o recurso de eventos de calendário, instale o addon ThunderAI Sparks.",
"description": ""
},
"prefs_OptionText_download_now": {
"message": "Baixe o ThunderAI Sparks agora!",
"description": ""
},
"placeholder_mail_datetime": {
"message": "A data e a hora do e-mail",
"description": ""
},
"placeholder_current_datetime": {
"message": "A data e a hora atuais",
"description": ""
},
"calendar_getting_data_error": {
"message": "Erro ao obter dados do evento de calendário",
"description": ""
},
"calendar_opening_dialog_error": {
"message": "Erro ao abrir a caixa de diálogo do evento de calendário",
"description": ""
}
}

View file

@ -1,6 +1,6 @@
/*
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
* Copyright (C) 2024 Mic (m@micz.it)
* Copyright (C) 2024 - 2025 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
@ -47,6 +47,9 @@ switch (llm) {
case "chatgpt_api":
worker = new Worker('../js/workers/model-worker-openai.js', { type: 'module' });
break;
case "google_gemini_api":
worker = new Worker('../js/workers/model-worker-google_gemini.js', { type: 'module' });
break;
case "ollama_api":
worker = new Worker('../js/workers/model-worker-ollama.js', { type: 'module' });
break;
@ -62,17 +65,30 @@ messageInput.init(worker);
messageInput.setMessagesArea(messagesArea);
switch (llm) {
case "chatgpt_api":
let prefs_api = await browser.storage.sync.get({chatgpt_api_key: '', chatgpt_model: '', do_debug: false});
case "chatgpt_api": {
let prefs_api = await browser.storage.sync.get({chatgpt_api_key: '', chatgpt_model: '', chatgpt_developer_messages:'', do_debug: false});
let i18nStrings = {};
i18nStrings["chatgpt_api_request_failed"] = browser.i18n.getMessage('chatgpt_api_request_failed');
i18nStrings["error_connection_interrupted"] = browser.i18n.getMessage('error_connection_interrupted');
messageInput.setModel(prefs_api.chatgpt_model);
messagesArea.setLLMName("ChatGPT");
worker.postMessage({ type: 'init', chatgpt_api_key: prefs_api.chatgpt_api_key, chatgpt_model: prefs_api.chatgpt_model, do_debug: prefs_api.do_debug, i18nStrings: i18nStrings});
worker.postMessage({ type: 'init', chatgpt_api_key: prefs_api.chatgpt_api_key, chatgpt_model: prefs_api.chatgpt_model, chatgpt_developer_messages: prefs_api.chatgpt_developer_messages, do_debug: prefs_api.do_debug, i18nStrings: i18nStrings});
messagesArea.appendUserMessage(browser.i18n.getMessage("chagpt_api_connecting") + " " +browser.i18n.getMessage("AndModel") + " \"" + prefs_api.chatgpt_model + "\"...", "info");
browser.runtime.sendMessage({command: "openai_api_ready_" + call_id, window_id: (await browser.windows.getCurrent()).id});
break;
}
case "google_gemini_api": {
let prefs_api = await browser.storage.sync.get({google_gemini_api_key: '', google_gemini_model: '', google_gemini_system_instruction: '', do_debug: false});
let i18nStrings = {};
i18nStrings["google_gemini_api_request_failed"] = browser.i18n.getMessage('google_gemini_api_request_failed');
i18nStrings["error_connection_interrupted"] = browser.i18n.getMessage('error_connection_interrupted');
messageInput.setModel(prefs_api.google_gemini_model);
messagesArea.setLLMName("Google Gemini");
worker.postMessage({ type: 'init', google_gemini_api_key: prefs_api.google_gemini_api_key, google_gemini_model: prefs_api.google_gemini_model, google_gemini_system_instruction: prefs_api.google_gemini_system_instruction, do_debug: prefs_api.do_debug, i18nStrings: i18nStrings});
messagesArea.appendUserMessage(browser.i18n.getMessage("google_gemini_api_connecting") + " " +browser.i18n.getMessage("AndModel") + " \"" + prefs_api.google_gemini_model + "\"...", "info");
browser.runtime.sendMessage({command: "google_gemini_api_ready_" + call_id, window_id: (await browser.windows.getCurrent()).id});
break;
}
case "ollama_api": {
let prefs_api = await browser.storage.sync.get({ollama_host: '', ollama_model: '', do_debug: false});
let i18nStrings = {};

View file

@ -1,6 +1,6 @@
/*
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
* Copyright (C) 2024 Mic (m@micz.it)
* Copyright (C) 2024 - 2025 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

View file

@ -1,6 +1,6 @@
/*
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
* Copyright (C) 2024 Mic (m@micz.it)
* Copyright (C) 2024 - 2025 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
@ -174,7 +174,7 @@ class MessagesArea extends HTMLElement {
if (isLastMessageFromUser) {
const header = document.createElement('h2');
header.textContent = "Chat GPT" + (type=='error' ? " - Error" : "");
header.textContent = this.llmName + (type=='error' ? " - Error" : "");
this.messages.appendChild(header);
}

109
js/api/google_gemini.js Normal file
View file

@ -0,0 +1,109 @@
/*
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
* Copyright (C) 2024 - 2025 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 class GoogleGemini {
apiKey = '';
model = '';
system_instruction = '';
stream = false;
constructor(apiKey, model, system_instruction, stream) {
this.apiKey = apiKey;
this.model = model;
this.system_instruction = system_instruction;
this.stream = stream;
}
fetchModels = async () => {
try{
const response = await fetch("https://generativelanguage.googleapis.com/v1beta/models?key=" + this.apiKey, {
method: "GET",
headers: {
"Content-Type": "application/json"
},
});
if (!response.ok) {
const errorDetail = await response.text();
let err_msg = "[ThunderAI] Google Gemini API request failed: " + response.status + " " + response.statusText + ", Detail: " + errorDetail;
console.error(err_msg);
let output = {};
output.ok = false;
output.error = errorDetail;
return output;
}
let output = {};
output.ok = true;
let output_response = await response.json();
console.log("[ThunderAI] Google Gemini API response: " + JSON.stringify(output_response));
output.response = output_response.models;
return output;
}catch (error) {
console.error("[ThunderAI] Google Gemini API request failed: " + error);
let output = {};
output.is_exception = true;
output.ok = false;
output.error = "Google Gemini API request failed: " + error;
return output;
}
}
fetchResponse = async (messages) => {
try {
let google_gemini_body = {
contents:messages
};
console.log("[ThunderAI] Google Gemini API system_instruction: " + JSON.stringify(this.system_instruction));
if(this.system_instruction !== '') {
google_gemini_body.system_instruction = {
parts:{
text: this.system_instruction
}
}
}
console.log("[ThunderAI] Google Gemini API request: " + JSON.stringify(google_gemini_body));
const response = await fetch("https://generativelanguage.googleapis.com/v1beta/models/" + this.model + ":" + (this.stream ? 'streamGenerateContent?alt=sse&' : 'generateContent?') + "key=" + this.apiKey, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(google_gemini_body),
});
return response;
}catch (error) {
console.error("[ThunderAI] Google Gemini API request failed: " + error);
let output = {};
output.is_exception = true;
output.ok = false;
output.error = "Google Gemini API request failed: " + error;
return output;
}
}
}

View file

@ -1,6 +1,6 @@
/*
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
* Copyright (C) 2024 Mic (m@micz.it)
* Copyright (C) 2024 - 2025 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

View file

@ -1,6 +1,6 @@
/*
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
* Copyright (C) 2024 Mic (m@micz.it)
* Copyright (C) 2024 - 2025 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
@ -23,11 +23,13 @@ export class OpenAI {
apiKey = '';
model = '';
developer_messages = '';
stream = false;
constructor(apiKey, model, stream) {
constructor(apiKey, model, developer_messages, stream) {
this.apiKey = apiKey;
this.model = model;
this.developer_messages = developer_messages;
this.stream = stream;
}
@ -69,6 +71,13 @@ export class OpenAI {
}
fetchResponse = async (messages, maxTokens = 0) => {
if(this.developer_messages !== ''){
messages.push({role: "developer", content: [{"type": "text", "text": this.developer_messages}]});
}
// console.log(">>>>>>>>>>> OpenAI API request: " + JSON.stringify(messages));
try {
const response = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",

View file

@ -1,6 +1,6 @@
/*
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
* Copyright (C) 2024 Mic (m@micz.it)
* Copyright (C) 2024 - 2025 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

View file

@ -1,6 +1,6 @@
/*
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
* Copyright (C) 2024 Mic (m@micz.it)
* Copyright (C) 2024 - 2025 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

View file

@ -1,6 +1,6 @@
/*
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
* Copyright (C) 2024 Mic (m@micz.it)
* Copyright (C) 2024 - 2025 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

View file

@ -1,6 +1,6 @@
/*
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
* Copyright (C) 2024 Mic (m@micz.it)
* Copyright (C) 2024 - 2025 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

View file

@ -1,6 +1,6 @@
/*
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
* Copyright (C) 2024 Mic (m@micz.it)
* Copyright (C) 2024 - 2025 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
@ -18,10 +18,11 @@
browser.runtime.onMessage.addListener((message) => {
switch (message.command) {
case "getSelectedText":
case "getSelectedText": {
return Promise.resolve(window.getSelection().toString());
}
case "replaceSelectedText":
case "replaceSelectedText": {
const selectedText = window.getSelection().toString();
if (selectedText === '') {
return Promise.resolve(false);
@ -37,9 +38,9 @@ switch (message.command) {
r.insertNode(doc.body);
browser.runtime.sendMessage({command: "compose_reloadBody", tabId: message.tabId});
return Promise.resolve(true);
break;
}
case "getText":
case "getText": {
let t = '';
const children = window.document.body.childNodes;
for (const node of children) {
@ -51,12 +52,56 @@ switch (message.command) {
t += node.textContent;
}
return Promise.resolve(t);
}
case "getTextOnly":
case "getTextOnly": {
return Promise.resolve(window.document.body.innerText);
}
case "getFullHtml":
case "getFullHtml": {
return Promise.resolve(window.document.body.innerHTML);
}
case "getOnlyTypedText": {
let t = '';
const children = window.document.body.childNodes;
const selection = window.getSelection();
let firstNode = null;
let lastNode = null;
for (const node of children) {
if (node instanceof Element) {
if (node.classList.contains('moz-cite-prefix')) {
break;
}
}
t += node.textContent;
// Track the first and last nodes for range
if (!firstNode) {
firstNode = node;
}
if(node.textContent.trim() != '') {
lastNode = node;
}
}
if(!lastNode) {
lastNode = firstNode;
}
//if(message.do_autoselect && selection.isCollapsed && firstNode && lastNode) {
if(message.do_autoselect && firstNode && lastNode) {
const range = document.createRange();
range.setStartBefore(firstNode);
range.setEndAfter(lastNode);
selection.removeAllRanges();
selection.addRange(range);
}
return Promise.resolve(t);
}
case 'sendAlert':
//console.log(">>>>>> message.curr_tab_type: " + JSON.stringify(message.curr_tab_type));
@ -212,12 +257,12 @@ switch (message.command) {
break;
case "getTags":
console.log(">>>>>>>>>>>>>> getTags: " + JSON.stringify(message.tags));
// 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));
// console.log(">>>>>>>>>>>>>>> addTags_getExclusionList prefs_excluded_tags: " + JSON.stringify(prefs_excluded_tags));
return prefs_excluded_tags.add_tags_exclusions;
}

View file

@ -1,6 +1,6 @@
/*
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
* Copyright (C) 2024 Mic (m@micz.it)
* Copyright (C) 2024 - 2025 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

View file

@ -1,6 +1,6 @@
/*
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
* Copyright (C) 2024 Mic (m@micz.it)
* Copyright (C) 2024 - 2025 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
@ -22,7 +22,7 @@ import { getPrompts } from './mzta-prompts.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';
import { mzta_specialCommand } from './mzta-special-commands.js';
export class mzta_Menus {
@ -51,7 +51,7 @@ export class mzta_Menus {
}
async initialize(also_special = false) {
async initialize(also_special = []) { // also_special is an array of active special prompts ids
this.allPrompts = [];
this.rootMenu = [];
this.shortcutMenu = [];
@ -63,7 +63,7 @@ export class mzta_Menus {
});
}
async reload(also_special = false) {
async reload(also_special = []) {
await browser.menus.removeAll().catch(error => {
console.error("[ThunderAI] ERROR removing the menus: ", error);
});
@ -76,18 +76,19 @@ export class mzta_Menus {
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 getMailBody = async (tabs, do_autoselect = false) => {
//const tabs = await browser.tabs.query({ active: true, currentWindow: true });
return {tabId: tabs[0].id,
selection: await browser.tabs.sendMessage(tabs[0].id, { command: "getSelectedText" }),
text: await browser.tabs.sendMessage(tabs[0].id, { command: "getTextOnly" }),
html: await browser.tabs.sendMessage(tabs[0].id, { command: "getFullHtml" }),
only_typed_text: await browser.tabs.sendMessage(tabs[0].id, { command: "getOnlyTypedText", do_autoselect: do_autoselect })
};
};
curr_menu_entry.act = async () => {
const tabs = await browser.tabs.query({ active: true, currentWindow: true });
const msg_text = await getMailBody(tabs);
const msg_text = await getMailBody(tabs, placeholdersUtils.hasPlaceholder(curr_prompt.text,'mail_typed_text'));
//check if a selection is needed
if(String(curr_prompt.need_selected) == "1" && (msg_text.selection==='')){
@ -99,8 +100,10 @@ export class mzta_Menus {
let body_text = '';
let selection_text = '';
let only_typed_text = '';
selection_text = msg_text.selection.replace(/\s+/g, ' ').trim();
body_text = msg_text.text.replace(/\s+/g, ' ').trim();
only_typed_text = msg_text.only_typed_text.replace(/\s+/g, ' ').trim();
//open chatgpt window
//console.log("Click menu item...");
let chatgpt_lang = '';
@ -150,6 +153,9 @@ export class mzta_Menus {
case 'mail_html_body':
finalSubs['mail_html_body'] = msg_text.html;
break;
case 'mail_typed_text':
finalSubs['mail_typed_text'] = only_typed_text;
break;
case 'mail_subject':
let mail_subject = await getMailSubject(tabs[0]);
finalSubs['mail_subject'] = mail_subject;
@ -169,6 +175,12 @@ export class mzta_Menus {
case 'junk_score':
finalSubs['junk_score'] = curr_message.junkScore;
break;
case 'mail_datetime':
finalSubs['mail_datetime'] = curr_message.date;
break;
case 'current_datetime':
finalSubs['current_datetime'] = new Date().toString();
break;
case 'tags_current_email':
let tags_current_email_array = await transformTagsLabels(curr_message.tags, tags_full_list[1]);
finalSubs['tags_current_email'] = tags_current_email_array.join(", ");
@ -206,7 +218,7 @@ export class mzta_Menus {
//browser.runtime.sendMessage({command: "chatgpt_open", prompt: fullPrompt, action: curr_prompt.action, tabId: tabs[0].id});
if(curr_prompt.is_special == '1'){ // Special prompts
switch(curr_prompt.id){
case 'prompt_add_tags': // Add tags to the email
case 'prompt_add_tags': { // Add tags to the email
let tags_current_email = '';
let prefs_at = await browser.storage.sync.get({add_tags_maxnum: 3, connection_type: '', add_tags_force_lang: true, default_chatgpt_lang: ''});
if((prefs_at.connection_type === '')||(prefs_at.connection_type === null)||(prefs_at.connection_type === undefined)||(prefs_at.connection_type === 'chatgpt_web')){
@ -224,7 +236,7 @@ export class mzta_Menus {
// TODO: use the current API, abort if using chatgpt web
// COMMENTED TO DO TESTS
// tags_current_email = "recipients, TEST, home, work, CAR, light";
let cmd_addTags = new mzta_specialCommand_AddTags(fullPrompt,prefs_at.connection_type,true);
let cmd_addTags = new mzta_specialCommand(fullPrompt,prefs_at.connection_type,true);
await cmd_addTags.initWorker();
try{
tags_current_email = await cmd_addTags.sendPrompt();
@ -238,7 +250,44 @@ export class mzta_Menus {
this.logger.log("tags_full_list: " + JSON.stringify(tags_full_list));
browser.tabs.sendMessage(tabs[0].id, {command: "getTags", tags: tags_current_email, messageId: curr_message.id});
return {ok:'1'};
break;
break; // Add tags to the email - END
}
case 'prompt_get_calendar_event': { // Get a calendar event info
let calendar_event_data = '';
let prefs_at = await browser.storage.sync.get({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 | GetCalendarEvent] Invalid connection type: " + prefs_at.connection_type);
return {ok:'0'};
}
/* We expect to receive from the AI a JSON object like this:
* {
* "startDate": "20250104T183000Z",
* "endDate": "20250104T193000Z",
* "summary": "ThunderAI Sparks",
* "forceAllDay": false
* }
*/
this.logger.log("fullPrompt: " + fullPrompt);
let cmd_GetCalendarEvent = new mzta_specialCommand(fullPrompt,prefs_at.connection_type,true);
await cmd_GetCalendarEvent.initWorker();
try{
calendar_event_data = await cmd_GetCalendarEvent.sendPrompt();
// console.log(">>>>>>>>>>> calendar_event_data: " + calendar_event_data);
}catch(err){
console.error("[ThunderAI] Error getting calendar event data: ", JSON.stringify(err));
browser.tabs.sendMessage(tabs[0].id, { command: "sendAlert", curr_tab_type: tabs[0].type, message: browser.i18n.getMessage("calendar_getting_data_error") + ": " + JSON.stringify(err) });
return {ok:'0'};
}
this.logger.log("calendar_event_data: " + calendar_event_data);
let result_openCalendarEventDialog = await browser.runtime.sendMessage('thunderai-sparks@micz.it',{action: "openCalendarEventDialog", calendar_event_data: calendar_event_data})
if(result_openCalendarEventDialog == 'ok'){
return {ok:'1'};
} else {
browser.tabs.sendMessage(tabs[0].id, { command: "sendAlert", curr_tab_type: tabs[0].type, message: browser.i18n.getMessage("calendar_opening_dialog_error") + ": " + result_openCalendarEventDialog.error });
return {ok:'0'};
}
break; // Get a calendar event info - END
}
default:
console.error("[ThunderAI] Unknown special prompt id: " + curr_prompt.id);
break;
@ -272,7 +321,7 @@ export class mzta_Menus {
this.shortcutMenu.push(curr_menu_entry);
}
async loadMenus(also_special = false) {
async loadMenus(also_special = []) {
await this.initialize(also_special);
await this.addMenu(this.rootMenu);
this.addClickListener();

View file

@ -1,6 +1,6 @@
/*
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
* Copyright (C) 2024 Mic (m@micz.it)
* Copyright (C) 2024 - 2025 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

View file

@ -1,6 +1,6 @@
/*
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
* Copyright (C) 2024 Mic (m@micz.it)
* Copyright (C) 2024 - 2025 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
@ -16,7 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* ================= PROMPTS PROPERTIES ========================================
/* ================= PLACEHOLDERS PROPERTIES ========================================
================ BASE PROPERTIES
@ -47,6 +47,13 @@ const defaultPlaceholders = [
type: 0,
is_default: "1",
},
{
id: 'mail_typed_text',
name: "__MSG_placeholder_mail_typed_text__",
default_value: "",
type: 2,
is_default: "1",
},
{
id: 'mail_subject',
name: "__MSG_placeholder_mail_subject__",
@ -96,6 +103,20 @@ const defaultPlaceholders = [
type: 0,
is_default: "1",
},
{
id: 'mail_datetime',
name: "__MSG_placeholder_mail_datetime__",
default_value: "",
type: 1,
is_default: "1",
},
{
id: 'current_datetime',
name: "__MSG_placeholder_current_datetime__",
default_value: "",
type: 0,
is_default: "1",
},
{
id: 'tags_current_email',
name: "__MSG_placeholder_tags_current_email__",

View file

@ -1,6 +1,6 @@
/*
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
* Copyright (C) 2024 Mic (m@micz.it)
* Copyright (C) 2024 - 2025 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
@ -182,18 +182,44 @@ const specialPrompts = [
is_default: "1",
is_special: "1",
},
{
id: 'prompt_get_calendar_event',
name: "__MSG_prompt_get_calendar_event__",
text: "prompt_get_calendar_event_full_text",
type: "1",
action: "0",
need_selected: "1",
need_signature: "0",
need_custom_text: "0",
define_response_lang: "0",
is_default: "1",
is_special: "1",
},
];
export async function getPrompts(onlyEnabled = false, includeSpecial = false){
export async function getPrompts(onlyEnabled = false, includeSpecial = []){ // includeSpecial is an array of active special prompts ids
const _defaultPrompts = await getDefaultPrompts_withProps();
// console.log('>>>>>>>>>>>> getPrompts _defaultPrompts: ' + JSON.stringify(_defaultPrompts));
const customPrompts = await getCustomPrompts();
// console.log('>>>>>>>>>>>> getPrompts customPrompts: ' + JSON.stringify(customPrompts));
const specialPrompts = await getSpecialPrompts();
let output = specialPrompts.concat(_defaultPrompts).concat(customPrompts);
if(!includeSpecial){
if(includeSpecial.length == 0){
output = output.filter(obj => obj.is_special != 1); // we do not want special prompts
}else{
// console.log(">>>>>>>>>> getPrompts includeSpecial: " + JSON.stringify(includeSpecial));
output = output.filter(obj => includeSpecial.includes(obj.id) || obj.is_special != 1);
// output = output.filter(obj => {
// const isIncluded = includeSpecial.includes(obj.id);
// const isNotSpecial = obj.is_special != 1;
// console.log(`>>>>>>>>>> Checking obj:`, obj);
// console.log(`>>>>>>>>>> isIncluded: ${isIncluded}`);
// console.log(`>>>>>>>>>> isNotSpecial: ${isNotSpecial}`);
// return isIncluded || isNotSpecial;
// });
}
if(onlyEnabled){
output = output.filter(obj => obj.enabled != 0);
@ -311,7 +337,21 @@ export async function getSpecialPrompts(){
})
return def_specPrompts;
} else {
return prefs._special_prompts;
let updatedPrompts = [...prefs._special_prompts];
specialPrompts.forEach((defaultPrompt) => {
if (!updatedPrompts.some((prompt) => prompt.id === defaultPrompt.id)) {
let newPrompt = { ...defaultPrompt };
newPrompt.text = browser.i18n.getMessage(newPrompt.text);
updatedPrompts.push(newPrompt);
}
});
if (updatedPrompts.length !== prefs._special_prompts.length) {
await browser.storage.local.set({ _special_prompts: updatedPrompts });
}
return updatedPrompts;
}
}

View file

@ -1,6 +1,6 @@
/*
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
* Copyright (C) 2024 Mic (m@micz.it)
* Copyright (C) 2024 - 2025 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
@ -15,12 +15,13 @@
* 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
// Call the API to use a special prompt
import { taLogger } from '../mzta-logger.js';
import { taLogger } from './mzta-logger.js';
export class mzta_specialCommand_AddTags {
export class mzta_specialCommand {
prompt = "";
worker = null;
@ -32,27 +33,36 @@
constructor(prompt, llm, do_debug = false) {
this.prompt = prompt;
this.llm = llm;
this.logger = new taLogger('mzta_specialCommand_AddTags', do_debug);
this.logger = new taLogger('mzta_specialCommand', 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' });
this.worker = new Worker(new URL('./workers/model-worker-openai.js', import.meta.url), { type: 'module' });
break;
case "google_gemini_api":
this.worker = new Worker(new URL('./workers/model-worker-google_gemini.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' });
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' });
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: ''});
case "chatgpt_api": {
let prefs_api = await browser.storage.sync.get({chatgpt_api_key: '', chatgpt_model: '', chatgpt_developer_messages: ''});
this.worker.postMessage({ type: 'init', chatgpt_api_key: prefs_api.chatgpt_api_key, chatgpt_model: prefs_api.chatgpt_model, chatgpt_developer_messages: prefs_api.chatgpt_developer_messages, do_debug: this.do_debug, i18nStrings: ''});
break;
}
case "google_gemini_api": {
let prefs_api = await browser.storage.sync.get({google_gemini_api_key: '', google_gemini_model: '', google_gemini_system_instruction: ''});
this.worker.postMessage({ type: 'init', google_gemini_api_key: prefs_api.google_gemini_api_key, google_gemini_model: prefs_api.google_gemini_model, google_gemini_system_instruction: prefs_api.google_gemini_system_instruction, 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: ''});

View file

@ -1,6 +1,6 @@
/*
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
* Copyright (C) 2024 Mic (m@micz.it)
* Copyright (C) 2024 - 2025 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

View file

@ -1,6 +1,6 @@
/*
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
* Copyright (C) 2024 Mic (m@micz.it)
* Copyright (C) 2024 - 2025 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
@ -254,8 +254,8 @@ function generateHexColorForTag() {
}
export async function transformTagsLabels(labels, tags_list) {
console.log(">>>>>>>>> transformTagsLabels labels: " + labels);
console.log(">>>>>>>>> transformTagsLabels tags_list: " + 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);
@ -263,6 +263,30 @@ export async function transformTagsLabels(labels, tags_list) {
return output;
}
export function getActiveSpecialPromptsIDs(addtags = false, get_calendar_event = false, is_chatgpt_web = false) {
let output = [];
// console.log(">>>>>>>>>> getActiveSpecialPromptsIDs addtags: " + addtags + " get_calendar_event: " + get_calendar_event + " is_chatgpt_web: " + is_chatgpt_web);
if(is_chatgpt_web){
return output;
}
if(addtags){
output.push('prompt_add_tags');
}
if(get_calendar_event){
output.push('prompt_get_calendar_event');
}
// console.log(">>>>>>>>>> getActiveSpecialPromptsIDs output: " + JSON.stringify(output));
return output;
}
export async function checkSparksPresence() {
try {
return (await browser.runtime.sendMessage('thunderai-sparks@micz.it',{action: "checkPresence"}) === 'ok');
} catch (error) {
return false;
}
}
// The following methods are a modified version derived from https://github.com/ali-raheem/Aify/blob/13ff87583bc520fb80f555ab90a90c5c9df797a7/plugin/content_scripts/compose.js

View file

@ -1,6 +1,6 @@
/*
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
* Copyright (C) 2024 Mic (m@micz.it)
* Copyright (C) 2024 - 2025 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

View file

@ -1,6 +1,6 @@
/*
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
* Copyright (C) 2024 Mic (m@micz.it)
* Copyright (C) 2024 - 2025 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

View file

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

View file

@ -1,6 +1,6 @@
/*
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
* Copyright (C) 2024 Mic (m@micz.it)
* Copyright (C) 2024 - 2025 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

View file

@ -1,6 +1,6 @@
/*
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
* Copyright (C) 2024 Mic (m@micz.it)
* Copyright (C) 2024 - 2025 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
@ -38,7 +38,7 @@ self.onmessage = async function(event) {
if (event.data.type === 'init') {
chatgpt_api_key = event.data.chatgpt_api_key;
chatgpt_model = event.data.chatgpt_model;
openai = new OpenAI(chatgpt_api_key, chatgpt_model, true);
openai = new OpenAI(chatgpt_api_key, chatgpt_model, event.data.chatgpt_developer_messages, true);
do_debug = event.data.do_debug;
i18nStrings = event.data.i18nStrings;
taLog = new taLogger('model-worker-openai', do_debug);

View file

@ -1,6 +1,6 @@
/*
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
* Copyright (C) 2024 Mic (m@micz.it)
* Copyright (C) 2024 - 2025 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

View file

@ -2,7 +2,7 @@
"manifest_version": 2,
"name": "ThunderAI",
"description": "__MSG_extensionDescription__",
"version": "3.0.0",
"version": "3.1.0",
"author": "Mic (m@micz.it)",
"homepage_url": "https://micz.it/thunderbird-addon-thunderai/",
"browser_specific_settings": {

View file

@ -1,6 +1,6 @@
/*
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
* Copyright (C) 2024 Mic (m@micz.it)
* Copyright (C) 2024 - 2025 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
@ -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, getTagsList, createTag, assignTagsToMessage, checkIfTagExists } from './js/mzta-utils.js';
import { getCurrentIdentity, getOriginalBody, replaceBody, setBody, i18nConditionalGet, generateCallID, migrateCustomPromptsStorage, migrateDefaultPromptsPropStorage, getGPTWebModelString, getTagsList, createTag, assignTagsToMessage, checkIfTagExists, getActiveSpecialPromptsIDs, checkSparksPresence } from './js/mzta-utils.js';
await migrateCustomPromptsStorage();
await migrateDefaultPromptsPropStorage();
@ -28,9 +28,11 @@ await migrateDefaultPromptsPropStorage();
var original_html = '';
var modified_html = '';
let prefs_init = await browser.storage.sync.get({do_debug: false, add_tags: true, connection_type: 'chatgpt_web'});
let prefs_init = await browser.storage.sync.get({do_debug: false, add_tags: true, get_calendar_event: true, connection_type: 'chatgpt_web'});
let taLog = new taLogger("mzta-background",prefs_init.do_debug);
let special_prompts_ids = getActiveSpecialPromptsIDs(prefs_init.add_tags, await doGetCalendarEvent(prefs_init.get_calendar_event), (prefs_init.connection_type === "chatgpt_web"));
browser.composeScripts.register({
js: [{file: "/js/mzta-compose-script.js"}]
});
@ -211,8 +213,11 @@ messenger.runtime.onMessage.addListener((message, sender, sendResponse) => {
break;
case 'reload_menus':
async function _reload_menus() {
let prefs_reload = await browser.storage.sync.get({add_tags: true, connection_type: 'chatgpt_web'});
menus.reload(prefs_reload.add_tags && (prefs_reload.connection_type !== "chatgpt_web"));
let prefs_reload = await browser.storage.sync.get({add_tags: true, get_calendar_event: false, connection_type: 'chatgpt_web'});
doGetCalendarEvent(prefs_reload.get_calendar_event).then(calendarEvent => {
const special_prompts_ids = getActiveSpecialPromptsIDs(prefs_reload.add_tags, calendarEvent, (prefs_reload.connection_type === "chatgpt_web"));
menus.reload(special_prompts_ids);
});
taLog.log("Reloading menus");
return true;
}
@ -237,11 +242,11 @@ messenger.runtime.onMessage.addListener((message, sender, sendResponse) => {
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));
// 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);
// console.log(">>>>>>>>>>>>>>> tag: " + tag);
if (!checkIfTagExists(tag, all_tags_list)) {
taLog.log("Creating tag: " + tag);
await createTag(tag);
@ -400,6 +405,56 @@ async function openChatGPT(promptText, action, curr_tabId, prompt_name = '', do_
break; // chatgpt_api - END
case 'google_gemini_api':
// We are using the Google Gemini API
let rand_call_id5 = '_google_gemini_' + generateCallID();
const listener5 = (message, sender, sendResponse) => {
function handleChatGptApi(createdTab) {
let mailMessageId5 = -1;
if(mailMessage) mailMessageId5 = mailMessage.id;
// check if the config is present, or give a message error
if (prefs.google_gemini_api_key == '') {
browser.tabs.sendMessage(createdTab.id, { command: "api_error", error: browser.i18n.getMessage('google_gemini_empty_apikey')});
return;
}
if (prefs.google_gemini_model == '') {
browser.tabs.sendMessage(createdTab.id, { command: "api_error", error: browser.i18n.getMessage('google_gemini_empty_model')});
return;
}
//console.log(">>>>>>>>>> sender: " + JSON.stringify(sender));
browser.tabs.sendMessage(createdTab.id, { command: "api_send", prompt: promptText, action: action, tabId: curr_tabId, mailMessageId: mailMessageId5, do_custom_text: do_custom_text});
taLog.log('[Google Gemini] Connection succeded!');
browser.runtime.onMessage.removeListener(listener5);
}
if (message.command === "google_gemini_api_ready_"+rand_call_id5) {
return handleChatGptApi(sender.tab);
}
return false;
}
browser.runtime.onMessage.addListener(listener5);
let win_options5 = {
url: browser.runtime.getURL('api_webchat/index.html?llm='+prefs.connection_type+'&call_id='+rand_call_id5+'&ph_def_val='+(prefs.placeholders_use_default_value?'1':'0')),
type: "popup",
}
taLog.log("[google_gemini_api] prefs.chatgpt_win_width: " + prefs.chatgpt_win_width + ", prefs.chatgpt_win_height: " + prefs.chatgpt_win_height);
if((prefs.chatgpt_win_width != '') && (prefs.chatgpt_win_height != '') && (prefs.chatgpt_win_width != 0) && (prefs.chatgpt_win_height != 0)){
win_options5.width = prefs.chatgpt_win_width,
win_options5.height = prefs.chatgpt_win_height
}
await browser.windows.create(win_options5);
break; // google_gemini_api - END
case 'ollama_api':
// We are using the Ollama API
@ -506,7 +561,10 @@ async function openChatGPT(promptText, action, curr_tabId, prompt_name = '', do_
await browser.windows.create(win_options4);
break; // openai_comp_api
break; // openai_comp_api - END
default:
taLog.error("Unknown API connection type: " + prefs.connection_type);
break;
}
}
@ -520,23 +578,59 @@ 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
//console.log(">>>>>>>>>>>>> changes: " + JSON.stringify(changes));
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"));
}
async function doGetCalendarEvent(get_calendar_event) {
if(get_calendar_event) {
return await checkSparksPresence();
} else {
return false;
}
});
}
async function reload_pref_init(){
prefs_init = await await browser.storage.sync.get({do_debug: false, add_tags: true, get_calendar_event: true, connection_type: 'chatgpt_web'});
}
// Register the listener for storage changes
function setupStorageChangeListener() {
browser.storage.onChanged.addListener((changes, areaName) => {
// Check if the change happened in the 'sync' storage area
if (areaName === 'sync') {
// Process 'add_tags' changes
if (changes.add_tags) {
const newTags = changes.add_tags.newValue;
doGetCalendarEvent(prefs_init.get_calendar_event).then(calendarEvent => {
const special_prompts_ids = getActiveSpecialPromptsIDs(newTags, calendarEvent, (prefs_init.connection_type === "chatgpt_web"));
menus.reload(special_prompts_ids);
});
}
// Process 'get_calendar_event' changes
if (changes.get_calendar_event) {
const newCalendarEvent = changes.get_calendar_event.newValue;
doGetCalendarEvent(newCalendarEvent).then(calendarEvent => {
const special_prompts_ids = getActiveSpecialPromptsIDs(prefs_init.add_tags, calendarEvent, (prefs_init.connection_type === "chatgpt_web"));
menus.reload(special_prompts_ids);
});
}
// Process 'connection_type' changes
if (changes.connection_type) {
const newConnectionType = changes.connection_type.newValue;
doGetCalendarEvent(prefs_init.get_calendar_event).then(calendarEvent => {
const special_prompts_ids = getActiveSpecialPromptsIDs(prefs_init.add_tags, calendarEvent, (newConnectionType === "chatgpt_web"));
menus.reload(special_prompts_ids);
});
}
reload_pref_init();
}
});
}
// Call the function to set up the listener
setupStorageChangeListener();
// Menus handling
const menus = new mzta_Menus(openChatGPT, prefs_init.do_debug);
menus.loadMenus(prefs_init.add_tags && (prefs_init.connection_type !== "chatgpt_web"));
menus.loadMenus(special_prompts_ids);

View file

@ -1,6 +1,6 @@
/*
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
* Copyright (C) 2024 Mic (m@micz.it)
* Copyright (C) 2024 - 2025 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
@ -21,11 +21,12 @@ export const prefs_default = {
chatgpt_win_height: 800,
chatgpt_win_width: 700,
default_chatgpt_lang: '',
connection_type: 'chatgpt_web', //Other values: 'chatgpt_api', 'ollama_api', 'openai_comp_api'
connection_type: 'chatgpt_web', //Other values: 'chatgpt_api', 'ollama_api', 'openai_comp_api', 'google_gemini_api'
chatgpt_web_model: '',
chatgpt_web_tempchat: false,
chatgpt_api_key: '',
chatgpt_model: '',
chatgpt_developer_messages: '',
ollama_host: '',
ollama_model: '',
openai_comp_host: '', // For OpenAI Compatible API as LM-Studio
@ -33,6 +34,9 @@ export const prefs_default = {
openai_comp_api_key: '',
openai_comp_use_v1: true,
openai_comp_chat_name: 'OpenAI Comp',
google_gemini_api_key: '',
google_gemini_model: '',
google_gemini_system_instruction: '',
dynamic_menu_force_enter: false,
dynamic_menu_order_alphabet: true,
placeholders_use_default_value: false,
@ -42,4 +46,5 @@ export const prefs_default = {
add_tags_hide_exclusions: false,
add_tags_first_uppercase: true,
add_tags_force_lang: true,
get_calendar_event: true,
}

View file

@ -104,11 +104,31 @@ tr.conntype_openai_comp_api, tr.conntype_openai_comp_api2{
background-color: rgb(213, 169, 238);
}
tr.conntype_google_gemini_api, tr.conntype_google_gemini_api2{
background-color: rgb(233, 238, 169);
}
#chatgpt_model_fetch_loading{
display: none;
font-style: italic;
}
#google_gemini_model_fetch_loading{
display: none;
font-style: italic;
}
#no_sparks td{
text-align: center;
padding: 1em !important;
background: #bbf7f1;
}
textarea.option-textarea{
width: -moz-available;
height: 10em;
}
.api_key-container {
position: relative;
width: 100%;
@ -218,6 +238,10 @@ input.option-input[type="text"]{
background-color: rgb(89, 20, 129);
}
tr.conntype_google_gemini_api, tr.conntype_google_gemini_api2{
background-color: rgb(72, 77, 3);
}
.api_key-container .toggle-icon img {
filter: invert(1);
}
@ -226,4 +250,7 @@ input.option-input[type="text"]{
background-color: rgb(2, 0, 100);
}
#no_sparks td{
background: #3b514f;
}
}

View file

@ -109,6 +109,7 @@
<select id="connection_type" name="connection_type" class="option-input">
<option value="chatgpt_web">__MSG_prefs_Connection_type_ChatGPT_Web__</option>
<option value="chatgpt_api">__MSG_prefs_Connection_type_ChatGPT_API__</option>
<option value="google_gemini_api">__MSG_prefs_Connection_type_Google_Gemini_API__</option>
<option value="ollama_api">__MSG_prefs_Connection_type_Ollama_API__</option>
<option value="openai_comp_api">__MSG_prefs_Connection_type_OpenAI_Comp_API__</option>
</select>
@ -174,6 +175,59 @@
</label>
</td>
</tr>
<tr class="conntype_chatgpt_api">
<td>
<label>
<span>__MSG_ChatGPT_Developer_Messages__</span>
</label>
</td>
<td>
<label>
<textarea id="chatgpt_developer_messages" name="chatgpt_developer_messages" class="option-input option-textarea"></textarea>
<br>__MSG_ChatGPT_Developer_Messages_Info__
</label>
</td>
</tr>
<tr class="conntype_google_gemini_api">
<td><label>
<span>__MSG_prefs_GoogleGemini_API_Key__</span>
</label></td>
<td>
<div class="api_key-container">
<label>
<input type="password" id="google_gemini_api_key" name="google_gemini_api_key" class="option-input"/>
</label>
<span class="toggle-icon" id="toggle_google_gemini_api_key"><img src="../images/pwd-show.png" id="pwd-icon_google_gemini_api_key"></span>
</div>
</td>
</tr>
<tr class="conntype_google_gemini_api">
<td>
<label>
<span>__MSG_GoogleGemini_Models__</span>
</label>
</td>
<td>
<button id="btnUpdateGoogleGeminiModels">__MSG_GoogleGemini_Models_Fetch__</button> <span id="google_gemini_model_fetch_loading">__MSG_Loading__</span><br>
<label>
<select id="google_gemini_model" name="google_gemini_model" class="option-input">
</select>
</label>
</td>
</tr>
<tr class="conntype_google_gemini_api">
<td>
<label>
<span>__MSG_GoogleGemini_SystemInstruction__</span>
</label>
</td>
<td>
<label>
<textarea id="google_gemini_system_instruction" name="google_gemini_system_instruction" class="option-input option-textarea"></textarea>
<br>__MSG_GoogleGemini_SystemInstruction_Info__
</label>
</td>
</tr>
<tr class="conntype_ollama_api">
<td><label>
<span>__MSG_prefs_API_Host__</span>
@ -280,6 +334,22 @@
</label>
</td>
</tr>
<tr class="get_calendar_event_tr">
<td><span>__MSG_prefs_OptionText_get_calendar_event__</span>
<br><button id="btnManageCalendarEventInfo" class="btn_small">__MSG_prefs_OptionText_btnManageCalendarEventInfo__</button></td>
<td>
<label>
<input type="checkbox" id="get_calendar_event" name="get_calendar_event" class="option-input" />
__MSG_prefs_OptionText_get_calendar_event_Info__
</label>
</td>
</tr>
<tr class="get_calendar_event_tr" id="no_sparks">
<td colspan="2">
<span>__MSG_prefs_OptionText_get_calendar_event_Sparks_not_present__</span>
<br><a href="https://addons.thunderbird.net/it/thunderbird/addon/thunderai-sparks">__MSG_prefs_OptionText_download_now__</a>
</td>
</tr>
<tr>
<td style="width:20em"><label>
<span>__MSG_Debug__</span>

View file

@ -1,6 +1,6 @@
/*
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
* Copyright (C) 2024 Mic (m@micz.it)
* Copyright (C) 2024 - 2025 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
@ -22,6 +22,8 @@ import { taLogger } from '../js/mzta-logger.js';
import { OpenAI } from '../js/api/openai.js';
import { Ollama } from '../js/api/ollama.js';
import { OpenAIComp } from '../js/api/openai_comp.js'
import { GoogleGemini } from '../js/api/google_gemini.js';
import { checkSparksPresence } from '../js/mzta-utils.js';
let taLog = new taLogger("mzta-options",true);
@ -44,7 +46,9 @@ function saveOptions(e) {
default:
if (element.tagName === 'SELECT') {
options[element.id] = element.value;
}else{
} else if (element.tagName === 'TEXTAREA') {
options[element.id] = element.value.trim();
} else {
console.error("[ThunderAI] Unhandled input type:", element.type);
}
}
@ -55,7 +59,7 @@ function saveOptions(e) {
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]));
taLog.log("Options restoring " + element.id + " = " + (element.id=="chatgpt_api_key" || element.id=="openai_comp_api_key" || element.id=="google_gemini_api_key" ? "****************" : result[element.id]));
switch (element.type) {
case 'checkbox':
element.checked = result[element.id] || false;
@ -81,6 +85,8 @@ async function restoreOptions() {
if (element.value === '') {
element.selectedIndex = -1;
}
} else if (element.tagName === 'TEXTAREA') {
element.value = result[element.id];
}else{
console.error("[ThunderAI] Unhandled input type:", element.type);
}
@ -95,16 +101,19 @@ async function restoreOptions() {
function showConnectionOptions() {
disable_MaxPromptLength();
disable_AddTags();
disable_GetCalendarEvent();
let chatgpt_web_display = 'table-row';
let chatgpt_api_display = 'none';
let ollama_api_display = 'none';
let openai_comp_api_display = 'none';
let google_gemini_api_display = 'none';
let conntype_select = document.getElementById("connection_type");
let parent = conntype_select.parentElement.parentElement.parentElement;
parent.classList.toggle("conntype_chatgpt_web", (conntype_select.value === "chatgpt_web"));
parent.classList.toggle("conntype_chatgpt_api", (conntype_select.value === "chatgpt_api"));
parent.classList.toggle("conntype_ollama_api", (conntype_select.value === "ollama_api"));
parent.classList.toggle("conntype_openai_comp_api", (conntype_select.value === "openai_comp_api"));
parent.classList.toggle("conntype_google_gemini_api", (conntype_select.value === "google_gemini_api"));
if (conntype_select.value === "chatgpt_web") {
chatgpt_web_display = 'table-row';
}else{
@ -125,6 +134,11 @@ function showConnectionOptions() {
}else{
openai_comp_api_display = 'none';
}
if (conntype_select.value === "google_gemini_api") {
google_gemini_api_display = 'table-row';
}else{
google_gemini_api_display = 'none';
}
document.querySelectorAll(".conntype_chatgpt_web").forEach(element => {
element.style.display = chatgpt_web_display;
});
@ -137,6 +151,9 @@ function showConnectionOptions() {
document.querySelectorAll(".conntype_openai_comp_api").forEach(element => {
element.style.display = openai_comp_api_display;
});
document.querySelectorAll(".conntype_google_gemini_api").forEach(element => {
element.style.display = google_gemini_api_display;
});
}
function warn_ChatGPT_APIKeyEmpty() {
@ -161,6 +178,28 @@ function warn_ChatGPT_APIKeyEmpty() {
}
}
function warn_GoogleGemini_APIKeyEmpty() {
let apiKeyInput = document.getElementById('google_gemini_api_key');
let btnFetchGoogleGeminiModels = document.getElementById('btnUpdateGoogleGeminiModels');
let modelGoogleGemini = document.getElementById('google_gemini_model');
if(apiKeyInput.value === ''){
apiKeyInput.style.border = '2px solid red';
btnFetchGoogleGeminiModels.disabled = true;
modelGoogleGemini.disabled = true;
modelGoogleGemini.selectedIndex = -1;
modelGoogleGemini.style.border = '';
}else{
apiKeyInput.style.border = '';
btnFetchGoogleGeminiModels.disabled = false;
modelGoogleGemini.disabled = false;
if((modelGoogleGemini.selectedIndex === -1)||(modelGoogleGemini.value === '')){
modelGoogleGemini.style.border = '2px solid red';
}else{
modelGoogleGemini.style.border = '';
}
}
}
function warn_Ollama_HostEmpty() {
let hostInput = document.getElementById('ollama_host');
let btnFetchOllamaModels = document.getElementById('btnUpdateOllamaModels');
@ -215,12 +254,27 @@ function disable_MaxPromptLength(){
function disable_AddTags(){
let add_tags = document.getElementById('add_tags');
let conntype_select = document.getElementById("connection_type");
add_tags.disabled = (conntype_select.value === "chatgpt_web");
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';
});
}
async function disable_GetCalendarEvent(){
let get_calendar_event = document.getElementById('get_calendar_event');
let no_sparks_tr = document.getElementById('no_sparks');
let is_spark_present = await checkSparksPresence()
let conntype_select = document.getElementById("connection_type");
get_calendar_event.disabled = (conntype_select.value === "chatgpt_web") || !is_spark_present;
let get_calendar_event_tr_elements = document.querySelectorAll('.get_calendar_event_tr');
get_calendar_event_tr_elements.forEach(get_calendar_event_tr => {
get_calendar_event_tr.style.display = get_calendar_event.disabled ? 'none' : 'table-row';
});
no_sparks_tr.style.display = is_spark_present ? 'none' : 'table-row';
}
document.addEventListener('DOMContentLoaded', async () => {
await restoreOptions();
@ -249,6 +303,13 @@ document.addEventListener('DOMContentLoaded', async () => {
addtags_info_btn.disabled = event.target.checked ? '' : 'disabled';
});
addtags_info_btn.disabled = addtags_el.checked ? '' : 'disabled';
let get_calendar_event_el = document.getElementById('get_calendar_event');
let get_calendar_event_info_btn = document.getElementById('btnManageCalendarEventInfo');
get_calendar_event_el.addEventListener('click', (event) => {
get_calendar_event_info_btn.disabled = event.target.checked ? '' : 'disabled';
});
get_calendar_event_info_btn.disabled = get_calendar_event_el.checked ? '' : 'disabled';
document.getElementById('btnManagePrompts').addEventListener('click', () => {
// check if the tab is already there
@ -276,6 +337,19 @@ document.addEventListener('DOMContentLoaded', async () => {
})
});
document.getElementById('btnManageCalendarEventInfo').addEventListener('click', () => {
// check if the tab is already there
browser.tabs.query({url: browser.runtime.getURL('../pages/get-calendar-event/mzta-get-calendar-event.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/get-calendar-event/mzta-get-calendar-event.html')});
}
})
});
document.getElementById('btnOpenAICompForceModel').addEventListener('click', () => {
let modelName = prompt(browser.i18n.getMessage('OpenAIComp_force_model_ask')).trim();
if ((modelName !== null) && (modelName !== undefined) && (modelName !== '')) {
@ -294,11 +368,15 @@ document.addEventListener('DOMContentLoaded', async () => {
conntype_select.addEventListener("change", warn_ChatGPT_APIKeyEmpty);
conntype_select.addEventListener("change", warn_Ollama_HostEmpty);
conntype_select.addEventListener("change", warn_OpenAIComp_HostEmpty);
conntype_select.addEventListener("change", warn_GoogleGemini_APIKeyEmpty);
conntype_select.addEventListener("change", disable_AddTags);
conntype_select.addEventListener("change", disable_GetCalendarEvent);
document.getElementById("chatgpt_api_key").addEventListener("change", warn_ChatGPT_APIKeyEmpty);
document.getElementById("ollama_host").addEventListener("change", warn_Ollama_HostEmpty);
document.getElementById("openai_comp_host").addEventListener("change", warn_OpenAIComp_HostEmpty);
document.getElementById("google_gemini_api_key").addEventListener("change", warn_GoogleGemini_APIKeyEmpty);
let prefs = await browser.storage.sync.get({chatgpt_model: '', ollama_model: '', openai_comp_model: ''});
let prefs = await browser.storage.sync.get({chatgpt_model: '', ollama_model: '', openai_comp_model: '', google_gemini_model: ''});
// OpenAI API ChatGPT model fetching
let select_chatgpt_model = document.getElementById('chatgpt_model');
@ -340,6 +418,46 @@ document.addEventListener('DOMContentLoaded', async () => {
warn_ChatGPT_APIKeyEmpty();
});
// Google Gemini API ChatGPT model fetching
let select_google_gemini_model = document.getElementById('google_gemini_model');
const google_gemini_option = document.createElement('option');
google_gemini_option.value = prefs.google_gemini_model;
google_gemini_option.text = prefs.google_gemini_model;
select_google_gemini_model.appendChild(google_gemini_option);
select_google_gemini_model.addEventListener("change", warn_GoogleGemini_APIKeyEmpty);
document.getElementById('btnUpdateGoogleGeminiModels').addEventListener('click', async () => {
document.getElementById('google_gemini_model_fetch_loading').style.display = 'inline';
let google_gemini = new GoogleGemini(document.getElementById("google_gemini_api_key").value, '', true);
google_gemini.fetchModels().then((data) => {
if(!data.ok){
let errorDetail;
try {
errorDetail = JSON.parse(data.error);
errorDetail = errorDetail.error.message;
} catch (e) {
errorDetail = data.error;
}
document.getElementById('google_gemini_model_fetch_loading').style.display = 'none';
console.error("[ThunderAI] " + browser.i18n.getMessage("GoogleGemini_Models_Error_fetching"));
alert(browser.i18n.getMessage("GoogleGemini_Models_Error_fetching")+": " + errorDetail);
return;
}
taLog.log("GoogleGemini models: " + JSON.stringify(data));
data.response.forEach(model => {
if (!Array.from(select_google_gemini_model.options).some(option => option.value === model.name.substring(model.name.lastIndexOf("/") + 1))) {
const option = document.createElement('option');
option.value = model.name.substring(model.name.lastIndexOf("/") + 1);
option.text = model.displayName;
select_google_gemini_model.appendChild(option);
}
});
document.getElementById('google_gemini_model_fetch_loading').style.display = 'none';
});
warn_GoogleGemini_APIKeyEmpty();
});
// Ollama API Model fetching
let select_ollama_model = document.getElementById('ollama_model');
const ollama_option = document.createElement('option');
@ -442,8 +560,10 @@ select_openai_comp_model.addEventListener("change", warn_OpenAIComp_HostEmpty);
warn_ChatGPT_APIKeyEmpty();
warn_Ollama_HostEmpty();
warn_OpenAIComp_HostEmpty();
warn_GoogleGemini_APIKeyEmpty();
disable_MaxPromptLength();
disable_AddTags();
disable_GetCalendarEvent();
const passwordField_chatgpt_api_key = document.getElementById('chatgpt_api_key');
const toggleIcon_chatgpt_api_key = document.getElementById('toggle_chatgpt_api_key');
@ -456,6 +576,17 @@ select_openai_comp_model.addEventListener("change", warn_OpenAIComp_HostEmpty);
icon_img_chatgpt_api_key.src = type === 'password' ? "../images/pwd-show.png" : "../images/pwd-hide.png";
});
const passwordField_google_gemini_api_key = document.getElementById('google_gemini_api_key');
const toggleIcon_google_gemini_api_key = document.getElementById('toggle_google_gemini_api_key');
const icon_img_google_gemini_api_key = document.getElementById('pwd-icon_google_gemini_api_key');
toggleIcon_google_gemini_api_key.addEventListener('click', () => {
const type = passwordField_google_gemini_api_key.getAttribute('type') === 'password' ? 'text' : 'password';
passwordField_google_gemini_api_key.setAttribute('type', type);
icon_img_google_gemini_api_key.src = type === 'password' ? "../images/pwd-show.png" : "../images/pwd-hide.png";
});
const passwordField_openai_comp_api_key = document.getElementById('openai_comp_api_key');
const toggleIcon_openai_comp_api_key = document.getElementById('toggle_openai_comp_api_key');
const icon_img_openai_comp_api_key = document.getElementById('pwd-icon_openai_comp_api_key');

View file

@ -7,12 +7,22 @@
<body>
<div id="miczBackPrefs"><a href="mzta-options.html">__MSG_backToOptionsText__</a></div>
<div id="miczRelNotes"><h1>ThunderAI Release Notes</h1>
<h2>Version 3.1.0 - 27/01/2025</h2>
<ul>
<li><i>[ChatGPT API][Ollama API][OpenAI Comp API][Gemini API]</i> Added a special prompt to get calendar events data from emails [<a href="https://github.com/micz/ThunderAI/issues/182">#182</a>]. To use this feature, you must install also the <a href="https://addons.thunderbird.net/it/thunderbird/addon/thunderai-sparks/">Sparks</a> add-on.</li>
<li>Added Google Gemini API support [<a href="https://github.com/micz/ThunderAI/issues/204">#204</a>, <a href="https://github.com/micz/ThunderAI/issues/217">#217</a>].</li>
<li>Added the <i>{%mail_typed_text%}</i> placeholder to get the text inserted before the quoted mail body when replying [<a href="https://github.com/micz/ThunderAI/issues/196">#196</a>].</li>
<li>Using the <i>{%mail_typed_text%}</i> placeholder the typed text inserted before the quoted mail body will be selected automatically to be replaced afterwards with the AI response [<a href="https://github.com/micz/ThunderAI/issues/229">#229</a>].</li>
<li>Added the <i>{%mail_datetime%}</i> placeholder to get the date and time of the email [<a href="https://github.com/micz/ThunderAI/issues/223">#223</a>].</li>
<li>Added the <i>{%current_datetime%}</i> data placeholder to get the current date and time [<a href="https://github.com/micz/ThunderAI/issues/224">#224</a>].</li>
<li>Added an info text about using the new <i>{%tags_full_list%}</i> placeholder in the "Add Tags Prompt" page [<a href="https://github.com/micz/ThunderAI/issues/215">#215</a>].</li>
</ul>
<h2>Version 3.0.0 - 05/01/2025</h2>
<ul>
<li><i>[ChatGPT API][Ollama API][OpenAI Comp API]</i> Added a special prompt to apply tags to emails [<a href="https://github.com/micz/ThunderAI/issues/183">#183</a>].</li>
<li>Default prompts text has been translated [<a href="https://github.com/micz/ThunderAI/issues/185">#185</a>].</li>
<li>Data placeholder <i>{%tags_full_list%}</i> for the full available tags list added [<a href="https://github.com/micz/ThunderAI/issues/197">#197</a>].</li>
<li>Data placeholder <i>{%tags_current_email%}</i> for the single mail tags list added [<a href="https://github.com/micz/ThunderAI/issues/198">#198</a>].</li>
<li>Added the <i>{%tags_full_list%}</i> placeholder for the full available tags list added [<a href="https://github.com/micz/ThunderAI/issues/197">#197</a>].</li>
<li>Added the <i>{%tags_current_email%}</i> 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><i>[OpenAI Comp API]</i> Added a button in the options page to manually insert the model [<a href="https://github.com/micz/ThunderAI/issues/205">#205</a>].</li>

View file

@ -1,6 +1,6 @@
/*
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
* Copyright (C) 2024 Mic (m@micz.it)
* Copyright (C) 2024 - 2025 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

View file

@ -53,7 +53,8 @@
<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>__MSG_prefs_OptionText_btnManagePrompts_infoline3__
<br><b>__MSG_prefs_OptionText_btnManagePrompts_infoline2__</b></span>
<br>
<div class="autocomplete-container">
<textarea id="addtags_prompt_text" rows="15"></textarea>

View file

@ -1,6 +1,6 @@
/*
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
* Copyright (C) 2024 Mic (m@micz.it)
* Copyright (C) 2024 - 2025 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
@ -79,7 +79,7 @@ document.addEventListener('DOMContentLoaded', async () => {
updateAdditionalPromptStatements();
autocompleteSuggestions = (await getPlaceholders(true)).filter(p => !(p.id === 'selected_text' || p.id === 'additional_text')).map(p => ({command: '{%'+p.id+'%}', type: p.type}));
autocompleteSuggestions = (await getPlaceholders(true)).filter(p => !(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');

View file

@ -1,6 +1,6 @@
/*
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
* Copyright (C) 2024 Mic (m@micz.it)
* Copyright (C) 2024 - 2025 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

View file

@ -0,0 +1,87 @@
#get_calendar_event_prompt_container{
width: 90%;
margin: 20px auto;
}
#get_calendar_event_prompt_text{
width: 100%;
}
.infoline{
font-size: 0.8em;
font-style: italic;
}
.btn_div{
width: 100%;
display: flex;
justify-content: space-between;
}
.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,30 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ThunderAI - __MSG_GetCalendarEvent_PageTitle__</title>
<link rel="stylesheet" href="mzta-get-calendar-event.css">
<link rel="icon" href="../../images/icon-16px.png">
</head>
<body>
<div>
<h1 class="page_title">__MSG_GetCalendarEvent_PageTitle__</h1>
<p>__MSG_GetCalendarEvent_info_default__</p>
</div>
<i>Work in progress...</i>
<div id="get_calendar_event_container">
<span class="section_title">__MSG_GetCalendarEvent_prompt_text_title__</span><span id="get_calendar_event_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_GetCalendarEvent_infoline2__</b></span>
<br>
<div class="autocomplete-container">
<textarea id="get_calendar_event_prompt_text" rows="15"></textarea>
<ul class="autocomplete-list hidden"></ul>
</div>
<div id="get_calendar_event_info_additional_statements"></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>
<script src="mzta-get-calendar-event.js" type="module"></script>
<script src="../../js/mzta-i18n.js"></script>
</body>
</html>

View file

@ -0,0 +1,149 @@
/*
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
* Copyright (C) 2024 - 2025 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";
let autocompleteSuggestions = [];
let taLog = new taLogger("mzta-get-calendar-event-page",true);
document.addEventListener('DOMContentLoaded', async () => {
i18n.updateDocument();
await restoreOptions();
document.querySelectorAll(".option-input").forEach(element => {
element.addEventListener("change", saveOptions);
});
let get_calendar_event_textarea = document.getElementById('get_calendar_event_prompt_text');
let get_calendar_event_save_btn = document.getElementById('btn_save_prompt');
let get_calendar_event_reset_btn = document.getElementById('btn_reset_prompt');
let specialPrompts = await getSpecialPrompts();
let get_calendar_event_prompt = specialPrompts.find(prompt => prompt.id === 'prompt_get_calendar_event');
get_calendar_event_textarea.addEventListener('input', (event) => {
get_calendar_event_reset_btn.disabled = (event.target.value === browser.i18n.getMessage('prompt_get_calendar_event_full_text'));
get_calendar_event_save_btn.disabled = (event.target.value === get_calendar_event_prompt.text);
if(get_calendar_event_save_btn.disabled){
document.getElementById('get_calendar_event_prompt_unsaved').classList.add('hidden');
} else {
document.getElementById('get_calendar_event_prompt_unsaved').classList.remove('hidden');
}
});
get_calendar_event_reset_btn.addEventListener('click', () => {
get_calendar_event_textarea.value = browser.i18n.getMessage('prompt_get_calendar_event_full_text');
get_calendar_event_reset_btn.disabled = true;
let event = new Event('input', { bubbles: true, cancelable: true });
get_calendar_event_textarea.dispatchEvent(event);
});
get_calendar_event_save_btn.addEventListener('click', () => {
specialPrompts.find(prompt => prompt.id === 'prompt_get_calendar_event').text = get_calendar_event_textarea.value;
setSpecialPrompts(specialPrompts);
get_calendar_event_save_btn.disabled = true;
document.getElementById('get_calendar_event_prompt_unsaved').classList.add('hidden');
browser.runtime.sendMessage({command: "reload_menus"});
});
if(get_calendar_event_prompt.text === 'prompt_get_calendar_event_full_text'){
get_calendar_event_prompt.text = browser.i18n.getMessage(get_calendar_event_prompt.text);
}
get_calendar_event_textarea.value = get_calendar_event_prompt.text;
get_calendar_event_reset_btn.disabled = (get_calendar_event_textarea.value === browser.i18n.getMessage('prompt_get_calendar_event_full_text'));
autocompleteSuggestions = (await getPlaceholders(true)).filter(p => !(p.id === 'additional_text')).map(p => ({command: '{%'+p.id+'%}', type: p.type}));
textareaAutocomplete(get_calendar_event_textarea, autocompleteSuggestions, 1); // type_value = 1, only when reading an email
});
// 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

@ -64,7 +64,7 @@ body {
background-color: #e9e9e9;
}
.prompt_add_tags{
.special_prompt{
background-color: #cfe9ff;
}
@ -102,7 +102,7 @@ body {
background-color: #444444;
}
.prompt_add_tags{
.special_prompt{
background-color: #353542;
}
}

View file

@ -1,6 +1,6 @@
/*
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
* Copyright (C) 2024 Mic (m@micz.it)
* Copyright (C) 2024 - 2025 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
@ -17,15 +17,18 @@
*/
import { taLogger } from "../js/mzta-logger.js";
import { checkSparksPresence } from "../js/mzta-utils.js";
let menuSendImmediately = false;
let taLog = console;
let connection_type = 'chatgpt_web';
let add_tags = false;
let get_calendar_event = false;
let tabType;
let num_special_menu_items = 0;
document.addEventListener('DOMContentLoaded', async () => {
let prefs = await browser.storage.sync.get({do_debug: false, dynamic_menu_force_enter: false, add_tags: false, connection_type: 'chatgpt_web'});
let prefs = await browser.storage.sync.get({do_debug: false, dynamic_menu_force_enter: false, add_tags: true, get_calendar_event: true, connection_type: 'chatgpt_web'});
taLog = new taLogger("mzta-popup",prefs.do_debug);
i18n.updateDocument();
let reponse = await browser.runtime.sendMessage({command: "popup_menu_ready"});
@ -40,6 +43,7 @@ document.addEventListener('DOMContentLoaded', async () => {
menuSendImmediately = prefs.dynamic_menu_force_enter;
connection_type = prefs.connection_type;
add_tags = prefs.add_tags;
get_calendar_event = prefs.get_calendar_event;
searchPrompt(active_prompts, tabId, tabType);
i18n.updateDocument();
}, { once: true });
@ -95,20 +99,56 @@ async function searchPrompt(allPrompts, tabId, tabType){
// Prepend numbers to the first 10 items
// If add_tags is true and connection_type is not 'chatgpt_web' reserve 0 position for prompt_add_tags
// If add_tags is true and connection_type is not 'chatgpt_web' reserve 0 position for prompt_add_tags and 1 for prompt_get_calendar_event (0, if no prompt_add_tags is disabled)
let max_num_el = 10
let first_num_el = 0;
if(checkDoAddTags()){
max_num_el = 9;
first_num_el = 1;
filteredData = ensurePromptAddTagsFirst(filteredData);
if (!filteredData[0].numberPrepended) {
filteredData[0].numberPrepended = 'true';
filteredData[0].label = '0. ' + filteredData[0].label;
let do_add_tags = checkDoAddTags();
let do_get_calendar_event = checkDoCalendarEvent();
// console.log(">>>>>>>>>>> do_add_tags: " + do_add_tags);
// console.log(">>>>>>>>>>> do_get_calendar_event: " + do_get_calendar_event);
// console.log(">>>>>>>>>>> filteredData: " + JSON.stringify(filteredData));
if(do_add_tags){
num_special_menu_items++;
}
if(do_get_calendar_event){
num_special_menu_items++;
}
// console.log(">>>>>>>>>>>> num_special_menu_items: " + num_special_menu_items);
if(num_special_menu_items > 0){
max_num_el -= num_special_menu_items;
first_num_el = num_special_menu_items;
// console.log(">>>>>>>>>>>>> max_num_el: " + max_num_el);
// console.log(">>>>>>>>>>>>> first_num_el: " + first_num_el);
if(do_add_tags){
filteredData = ensurePromptAddTagsFirst(filteredData);
if (!filteredData[0].numberPrepended) {
filteredData[0].numberPrepended = 'true';
filteredData[0].label = '0. ' + filteredData[0].label;
}
}
if(do_get_calendar_event){
filteredData = ensurePromptGetCalendarEventFirst(filteredData, do_add_tags);
let gce_curr_pos = do_add_tags ? 1 : 0;
if (!filteredData[gce_curr_pos].numberPrepended) {
filteredData[gce_curr_pos].numberPrepended = 'true';
filteredData[gce_curr_pos].label = gce_curr_pos + '. ' + filteredData[gce_curr_pos].label;
}
}
}
// if(checkDoAddTags()){
// max_num_el = 9;
// first_num_el = 1;
// filteredData = ensurePromptAddTagsFirst(filteredData);
// if (!filteredData[0].numberPrepended) {
// 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';
let number = (index + first_num_el).toString();
// Check if the number is already prepended to avoid duplication
if (!item.numberPrepended) {
item.label = `${number}. ${item.label}`;
@ -118,21 +158,14 @@ 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';
if((item.id === 'prompt_add_tags')||(item.id === 'prompt_get_calendar_event')){
itemDiv.className += ' special_prompt';
}
// Add a mousedown event to select the item
@ -262,7 +295,7 @@ async function sendPrompt(prompt_id, tabId){
document.getElementById('mzta_search_input').style.display = 'none';
document.getElementById('mzta_sending_prompt').style.display = 'block';
let response = await browser.runtime.sendMessage({command: "shortcut_do_prompt", tabId: tabId, promptId: prompt_id});
console.log(">>>>>>>>>>>>>>>>> response: " + JSON.stringify(response));
// console.log(">>>>>>>>>>>>>>>>> response: " + JSON.stringify(response));
if(response.ok == '1'){
window.close();
}
@ -293,6 +326,10 @@ function checkDoAddTags(){
return add_tags && (connection_type !== "chatgpt_web" && tabType !== 'messageCompose');
}
function checkDoCalendarEvent(){
return get_calendar_event && (connection_type !== "chatgpt_web" && tabType !== 'messageCompose') && checkSparksPresence();
}
function ensurePromptAddTagsFirst(arr) {
// Find the index of the object with id "prompt_add_tags"
const index = arr.findIndex(item => item.id === "prompt_add_tags");
@ -305,5 +342,22 @@ function ensurePromptAddTagsFirst(arr) {
arr.unshift(promptAddTags);
}
return arr;
}
function ensurePromptGetCalendarEventFirst(arr, do_add_tags) {
// Find the index of the object with id "prompt_get_calendar_event"
const index = arr.findIndex(item => item.id === "prompt_get_calendar_event");
// If found and needs repositioning
if (index !== -1 && (do_add_tags ? index !== 1 : index !== 0)) {
// Remove it from its current position
const [promptAddTags] = arr.splice(index, 1);
// Add it to the specified position
const targetPosition = do_add_tags ? 1 : 0;
arr.splice(targetPosition, 0, promptAddTags);
}
return arr;
}