Merge pull request #227 from micz/calendar

Create Calendar Event
This commit is contained in:
Mic 2025-01-15 21:00:27 +01:00 committed by GitHub
commit 798e683674
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 677 additions and 75 deletions

View file

@ -7,8 +7,11 @@
<h2>Version 3.1.0 - ??/??/2025</h2>
<ul>
<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">#2174</a>].</li>
<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 <i>{%mail_typed_text%}</i> data 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>Added <i>{%mail_datetime%}</i> data placeholder to get the date and time of the email [<a href="https://github.com/micz/ThunderAI/issues/223">#223</a>].</li>
<li>Added <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>
<li>...</li>
</ul>

View file

@ -2,7 +2,7 @@
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.

View file

@ -954,5 +954,57 @@
"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": ""
}
}

View file

@ -228,12 +228,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;
}
@ -358,7 +358,7 @@ switch (message.command) {
// Parse the input string into labels
const words = inputString.split(',').map(word => word.trim()).filter(word => word !== '');
console.log(">>>>>>>>>>>>> words: " + JSON.stringify(words));
// console.log(">>>>>>>>>>>>> words: " + JSON.stringify(words));
if(words.length == 0){
const message = document.createElement('div');
@ -371,7 +371,7 @@ switch (message.command) {
let prefs_tags = await browser.storage.sync.get({add_tags_hide_exclusions: false});
let add_tags_exclusions_list = await addTags_getExclusionList();
console.log(">>>>>>>>>>>>> add_tags_exclusions_list: " + JSON.stringify(add_tags_exclusions_list));
// console.log(">>>>>>>>>>>>> add_tags_exclusions_list: " + JSON.stringify(add_tags_exclusions_list));
const words_final = words
.filter(word => word !== '')
@ -386,7 +386,7 @@ switch (message.command) {
};
});
console.log(">>>>>>>>>>>>> words_final: " + JSON.stringify(words_final));
// console.log(">>>>>>>>>>>>> words_final: " + JSON.stringify(words_final));
// Create the form
const form = document.createElement('form');
@ -505,7 +505,7 @@ switch (message.command) {
}
return createDialog(message.tags, (selected) => {
console.log('>>>>>>>>>>>> Selected tags:', selected);
// console.log('>>>>>>>>>>>> Selected tags:', selected);
browser.runtime.sendMessage({ command: "assign_tags", tags: selected, messageId: message.messageId });
});

View file

@ -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);
});
@ -175,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(", ");
@ -212,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')){
@ -230,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();
@ -241,10 +247,42 @@ export class mzta_Menus {
return {ok:'0'};
}
this.logger.log("tags_current_email: " + tags_current_email);
console.log(">>>>>>>>>>>> tags_full_list: " + JSON.stringify(tags_full_list));
// console.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: "Error getting calendar event data: " + JSON.stringify(err) });
return {ok:'0'};
}
this.logger.log("calendar_event_data: " + calendar_event_data);
browser.runtime.sendMessage('thunderai-sparks@micz.it',{action: "openCalendarEventDialog", calendar_event_data: calendar_event_data})
return {ok:'1'};
break; // Get a calendar event info - END
}
default:
console.error("[ThunderAI] Unknown special prompt id: " + curr_prompt.id);
break;
@ -278,7 +316,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

@ -16,7 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* ================= PROMPTS PROPERTIES ========================================
/* ================= PLACEHOLDERS PROPERTIES ========================================
================ BASE PROPERTIES
@ -103,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

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

@ -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,20 +33,20 @@
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' });
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;
}
}
@ -69,7 +70,7 @@
}
case "openai_comp_api": {
let prefs_api = await browser.storage.sync.get({openai_comp_host: '', openai_comp_model: '', openai_comp_api_key: '', openai_comp_use_v1: true, openai_comp_chat_name: '', do_debug: false});
console.log(">>>>>>>>>>>> [ThunderAI] prefs_api: " + JSON.stringify(prefs_api));
// console.log(">>>>>>>>>>>> [ThunderAI] prefs_api: " + JSON.stringify(prefs_api));
this.worker.postMessage({ type: 'init', openai_comp_host: prefs_api.openai_comp_host, openai_comp_model: prefs_api.openai_comp_model, openai_comp_api_key: prefs_api.openai_comp_api_key, openai_comp_use_v1: prefs_api.openai_comp_use_v1, do_debug: this.do_debug, i18nStrings: ''});
break;
}
@ -89,7 +90,7 @@
this.full_message += payload.token;
break;
case 'tokensDone':
console.log(">>>>>>>>>>>> [ThunderAI] tokensDone: " + this.full_message);
// console.log(">>>>>>>>>>>> [ThunderAI] tokensDone: " + this.full_message);
resolve(this.full_message); // Resolve the promise with the full message
break;
case 'error':

View file

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

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

View file

@ -20,7 +20,7 @@ import { mzta_script } from './js/mzta-chatgpt.js';
import { prefs_default } from './options/mzta-options-default.js';
import { mzta_Menus } from './js/mzta-menus.js';
import { taLogger } from './js/mzta-logger.js';
import { getCurrentIdentity, getOriginalBody, replaceBody, setBody, i18nConditionalGet, generateCallID, migrateCustomPromptsStorage, migrateDefaultPromptsPropStorage, getGPTWebModelString, 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;
}
@ -573,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

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

@ -118,6 +118,12 @@ tr.conntype_google_gemini_api, tr.conntype_google_gemini_api2{
font-style: italic;
}
#no_sparks td{
text-align: center;
padding: 1em !important;
background: #bbf7f1;
}
textarea.option-textarea{
width: -moz-available;
height: 10em;
@ -244,4 +250,7 @@ input.option-input[type="text"]{
background-color: rgb(2, 0, 100);
}
#no_sparks td{
background: #3b514f;
}
}

View file

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

@ -23,6 +23,7 @@ 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);
@ -100,6 +101,7 @@ 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';
@ -252,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();
@ -286,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
@ -313,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 !== '')) {
@ -332,6 +369,8 @@ document.addEventListener('DOMContentLoaded', async () => {
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);
@ -524,6 +563,7 @@ select_openai_comp_model.addEventListener("change", 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');

View file

@ -9,8 +9,11 @@
<div id="miczRelNotes"><h1>ThunderAI Release Notes</h1>
<h2>Version 3.1.0 - ??/??/2025</h2>
<ul>
<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">#2174</a>].</li>
<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 <i>{%mail_typed_text%}</i> data 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>Added <i>{%mail_datetime%}</i> data placeholder to get the date and time of the email [<a href="https://github.com/micz/ThunderAI/issues/223">#223</a>].</li>
<li>Added <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>
<li>...</li>
</ul>

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

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