diff --git a/README.md b/README.md index a9993453..42ef821e 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ThunderAI is a Thunderbird Addon that uses the capabilities of ChatGPT, Google Gemini or Ollama to enhance email management. -It enables users to analyse, write, correct, assign tags, create calendar events and optimize their emails, facilitating more effective and professional communication. +It enables users to analyse, write, correct, assign tags, create calendar events or tasks 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. diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 95c3d121..4eb596ab 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -999,18 +999,38 @@ "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)\n- Attendees\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.\nThese are the attendees: {%author%}, {%recipients%}, {%cc_list%}. If present, exclude my address: {%account_email_address%}.\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\"attendees\": [attendee1@example.com,attendee2@example.com,attendee3@example.com]\n}\nHere's the text:\"{%selected_text%}\"", "description": "" }, + "prompt_get_task": { + "message": "Add a new task", + "description": "" + }, + "prompt_get_task_full_text": { + "message": "Extract all relevant details required to generate a task from the following text. The extracted information should include:\n- Due Date and Time (including timezone, if specified)\n- Task summary\n- Initial Date and Time (including timezone, if specified)\nEnsure the data is formatted clearly and consistently so that it can be directly used for creating a task.\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 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\"InitialDate\": \"YYYYMMDDTHHMMSS\",\n\"dueDate\": \"YYYYMMDDTHHMMSS\",\n\"summary\": \"Task summary here\"\n}\nIf there are no information about the dates, remove them.\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.", + "message": "If checked, an item will be included in the menu to get calendar event information from selected text.", "description": "" }, "get_calendar_event_prompt_prefs_title": { "message": "Calendar Events Options", "description": "" }, + "prefs_OptionText_get_task": { + "message": "Add a new task from selected text", + "description": "" + }, + "prefs_OptionText_get_task_Info": { + "message": "If checked, an item will be included in the menu to get task information from selected text.", + "description": "" + }, + "get_task_prompt_prefs_title": { + "message": "Tasks Options", + "description": "" + }, "Select_your_timezone": { "message": "Select your timezone", "description": "" @@ -1020,7 +1040,7 @@ "description": "" }, "prefs_OptionText_calendar_enforce_timezone_Info": { - "message": "If checked, the specified timezone will be enforced for the calendar events.", + "message": "If checked, the specified timezone will be enforced for the calendar events and the tasks.", "description": "" }, "prefs_OptionText_btnManageCalendarEventInfo": { @@ -1044,11 +1064,23 @@ "description": "" }, "prefs_OptionText_get_calendar_event_Sparks_not_present": { - "message": "To use the calendar event feature, please install the ThunderAI Sparks addon.", + "message": "To use the calendar event and task features, please install the ThunderAI Sparks addon.", "description": "" }, "prefs_OptionText_get_calendar_event_Sparks_wrong_version": { - "message": "To use the calendar event feature, please install an updated version of ThunderAI Sparks addon.", + "message": "To use the calendar event and task features, please install an updated version of ThunderAI Sparks addon.", + "description": "" + }, + "GetTask_PageTitle": { + "message": "Manage Tasks Settings", + "description": "" + }, + "GetTask_info_default": { + "message": "In this page you can modify the default prompt used to get a task from selected text.", + "description": "" + }, + "prefs_OptionText_btnManageTaskInfo": { + "message": "Manage tasks settings", "description": "" }, "prefs_OptionText_download_now": { @@ -1071,8 +1103,16 @@ "message": "Error opening the calendar event dialog", "description": "" }, - "sparks_not_installed": { - "message": "ThunderAI Sparks not installed!", + "task_getting_data_error": { + "message": "Error getting task data", + "description": "" + }, + "task_opening_dialog_error": { + "message": "Error opening the task dialog", + "description": "" + }, + "no_valid_data_received": { + "message": "No valid data recevied from the AI.", "description": "" }, "prefs_OptionText_add_tags_auto": { diff --git a/js/mzta-menus.js b/js/mzta-menus.js index 89be4316..e93d0327 100644 --- a/js/mzta-menus.js +++ b/js/mzta-menus.js @@ -260,12 +260,86 @@ export class mzta_Menus { } }catch(err){ console.error("[ThunderAI] Error opening calendar event dialog: ", err); - browser.tabs.sendMessage(tabs[0].id, { command: "sendAlert", curr_tab_type: tabs[0].type, message: browser.i18n.getMessage("calendar_opening_dialog_error") + ": " + browser.i18n.getMessage("sparks_not_installed") }); + browser.tabs.sendMessage(tabs[0].id, { command: "sendAlert", curr_tab_type: tabs[0].type, message: browser.i18n.getMessage("calendar_opening_dialog_error") + ": " + browser.i18n.getMessage("no_valid_data_received") }); taWorkingStatus.stopWorking(); return {ok:'0'}; } break; // Get a calendar event info - END } + case 'prompt_get_task': { // Get a task info + let task_data = ''; + let prefs_at = await browser.storage.sync.get({connection_type: '', calendar_enforce_timezone: false, calendar_timezone: '',}); + if((prefs_at.connection_type === '')||(prefs_at.connection_type === null)||(prefs_at.connection_type === undefined)||(prefs_at.connection_type === 'chatgpt_web')){ + console.error("[ThunderAI | GetTask] Invalid connection type: " + prefs_at.connection_type); + taWorkingStatus.stopWorking(); + return {ok:'0'}; + } + /* We expect to receive from the AI a JSON object like this: + * { + * "InitialDate": "YYYYMMDDTHHMMSS", + * "dueDate": "YYYYMMDDTHHMMSS", + * "summary": "Task summary here" + * } + */ + this.logger.log("fullPrompt: " + fullPrompt); + let cmd_GetTask = new mzta_specialCommand(fullPrompt,prefs_at.connection_type,true); + await cmd_GetTask.initWorker(); + try{ + task_data = await cmd_GetTask.sendPrompt(); + // console.log(">>>>>>>>>>> task_data: " + task_data); + }catch(err){ + console.error("[ThunderAI] Error getting task data: ", err.message); + browser.tabs.sendMessage(tabs[0].id, { command: "sendAlert", curr_tab_type: tabs[0].type, message: browser.i18n.getMessage("task_getting_data_error") + ": " + err.message }); + taWorkingStatus.stopWorking(); + return {ok:'0'}; + } + let task_data_obj = {}; + try{ + task_data_obj = extractJsonObject(task_data); + if (!task_data_obj.dueDate || isNaN(Date.parse(task_data_obj.dueDate)) || task_data_obj.dueDate == '' ) { + delete task_data_obj.dueDate; + } + if (!task_data_obj.InitialDate || isNaN(Date.parse(task_data_obj.InitialDate)) || task_data_obj.InitialDate == '' ) { + delete task_data_obj.InitialDate; + } + }catch(err){ + console.error("[ThunderAI] Error extracting JSON object from task data: ", err.message); + browser.tabs.sendMessage(tabs[0].id, { command: "sendAlert", curr_tab_type: tabs[0].type, message: browser.i18n.getMessage("task_getting_data_error") + ": " + err.message }); + taWorkingStatus.stopWorking(); + return {ok:'0'}; + } + // Timezone management + task_data_obj.use_timezone = false; + if(prefs_at.calendar_enforce_timezone){ + task_data_obj.use_timezone = true; + task_data_obj.timezone = prefs_at.calendar_timezone; + } + let task_data_str = JSON.stringify(task_data_obj); + // Timezone management - END + this.logger.log("task_data: " + task_data); + this.logger.log("task_data_obj: " + task_data_str); + try{ + let result_openTaskDialog = await browser.runtime.sendMessage('thunderai-sparks@micz.it',{action: "openTaskDialog", task_data: task_data_str}) + if(result_openTaskDialog == 'ok'){ + taWorkingStatus.stopWorking(); + return {ok:'1'}; + } else { + let err = result_openTaskDialog.error; + if (err && typeof err === 'string' && err.startsWith('|>>')) { + result_openTaskDialog.error = browser.i18n.getMessage(result_openTaskDialog.error.substring(3)); + } + browser.tabs.sendMessage(tabs[0].id, { command: "sendAlert", curr_tab_type: tabs[0].type, message: browser.i18n.getMessage("task_opening_dialog_error") + ": " + result_openTaskDialog.error }); + taWorkingStatus.stopWorking(); + return {ok:'0'}; + } + }catch(err){ + console.error("[ThunderAI] Error opening task dialog: ", err); + browser.tabs.sendMessage(tabs[0].id, { command: "sendAlert", curr_tab_type: tabs[0].type, message: browser.i18n.getMessage("task_opening_dialog_error") + ": " + browser.i18n.getMessage("no_valid_data_received") }); + taWorkingStatus.stopWorking(); + return {ok:'0'}; + } + break; + } // Get a task info - END default: console.error("[ThunderAI] Unknown special prompt id: " + curr_prompt.id); taWorkingStatus.stopWorking(); diff --git a/js/mzta-placeholders.js b/js/mzta-placeholders.js index 363feee2..4dfb6d0e 100644 --- a/js/mzta-placeholders.js +++ b/js/mzta-placeholders.js @@ -197,7 +197,6 @@ export async function getPlaceholders(onlyEnabled = false){ output.sort((a, b) => a.id.localeCompare(b.id)); } // console.log('>>>>>>>>>>>> getPlaceholders output: ' + JSON.stringify(output)); - let prefs = await browser.storage.local.get({ add_tags: false }); return output; } diff --git a/js/mzta-prompts.js b/js/mzta-prompts.js index f5b2bb77..9ee7a1fa 100644 --- a/js/mzta-prompts.js +++ b/js/mzta-prompts.js @@ -223,6 +223,20 @@ const specialPrompts = [ is_default: "1", is_special: "1", }, + { + id: 'prompt_get_task', + name: "__MSG_prompt_get_task__", + text: "prompt_get_task_full_text", + type: "1", + action: "0", + need_selected: "1", + need_signature: "0", + need_custom_text: "0", + define_response_lang: "0", + use_diff_viewer: "0", + is_default: "1", + is_special: "1", + }, { id: 'prompt_spamfilter', name: "__MSG_prompt_spamfilter__", @@ -394,7 +408,7 @@ export async function getSpecialPrompts(){ updatedPrompts.push(newPrompt); } }); - + // console.log(">>>>>>>>>>>>> getSpecialPrompts updatedPrompts: " + JSON.stringify(updatedPrompts)); if (updatedPrompts.length !== prefs._special_prompts.length) { await browser.storage.local.set({ _special_prompts: updatedPrompts }); } diff --git a/js/mzta-utils.js b/js/mzta-utils.js index d314faf0..e4514a18 100644 --- a/js/mzta-utils.js +++ b/js/mzta-utils.js @@ -198,6 +198,19 @@ export function getGPTWebModelString(model) { } } +export function openTab(url){ + // check if the tab is already there + browser.tabs.query({url: browser.runtime.getURL(url)}).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(url)}); + } + }) +} + export function i18nConditionalGet(str) { // if we are getting a string that starts with '__MSG_' and ends with '__' we return the translated string // using the browser.i18n API @@ -332,18 +345,22 @@ export async function transformTagsLabels(labels, tags_list) { return output; } -export function getActiveSpecialPromptsIDs(addtags = false, get_calendar_event = false, is_chatgpt_web = false) { +export function getActiveSpecialPromptsIDs(args = {}) { + const { addtags = false, get_calendar_event = false, get_task = false, is_chatgpt_web = false } = args; let output = []; - // console.log(">>>>>>>>>> getActiveSpecialPromptsIDs addtags: " + addtags + " get_calendar_event: " + get_calendar_event + " is_chatgpt_web: " + is_chatgpt_web); - if(is_chatgpt_web){ + // console.log(">>>>>>>>>> getActiveSpecialPromptsIDs args: " + JSON.stringify(args)); + if (is_chatgpt_web) { return output; } - if(addtags){ + if (addtags) { output.push('prompt_add_tags'); } - if(get_calendar_event){ + if (get_calendar_event) { output.push('prompt_get_calendar_event'); } + if (get_task) { + output.push('prompt_get_task'); + } // console.log(">>>>>>>>>> getActiveSpecialPromptsIDs output: " + JSON.stringify(output)); return output; } diff --git a/mzta-background.js b/mzta-background.js index 1b17e355..deb9c72f 100644 --- a/mzta-background.js +++ b/mzta-background.js @@ -45,6 +45,7 @@ var original_html = ''; var modified_html = ''; let _process_incoming = false; +let _sparks_presence = false; let prefs_init = {}; await reload_pref_init(); @@ -52,7 +53,12 @@ await reload_pref_init(); let taLog = new taLogger("mzta-background",prefs_init.do_debug); taWorkingStatus.taLog = taLog; -let special_prompts_ids = getActiveSpecialPromptsIDs(prefs_init.add_tags, await doGetCalendarEvent(prefs_init.get_calendar_event), (prefs_init.connection_type === "chatgpt_web")); +let special_prompts_ids = getActiveSpecialPromptsIDs({ + addtags: prefs_init.add_tags, + get_calendar_event: doGetSparkFeature(prefs_init.get_calendar_event), + get_task: doGetSparkFeature(prefs_init.get_task), + is_chatgpt_web: (prefs_init.connection_type === "chatgpt_web") + }); browser.composeScripts.register({ js: [{file: "/js/mzta-compose-script.js"}] @@ -146,11 +152,16 @@ function preparePopupMenu(tab) { } async function _reload_menus() { - 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); - }); + let prefs_reload = await browser.storage.sync.get({add_tags: prefs_default.add_tags, get_calendar_event: prefs_default.get_calendar_event, get_task: prefs_default.get_task, connection_type: prefs_default.connection_type}); + let getCalendarEvent = doGetSparkFeature(prefs_reload.get_calendar_event); + let getTask = doGetSparkFeature(prefs_reload.get_task); + const special_prompts_ids = getActiveSpecialPromptsIDs({ + addtags: prefs_reload.add_tags, + get_calendar_event: getCalendarEvent, + get_task: getTask, + is_chatgpt_web: (prefs_reload.connection_type === "chatgpt_web") + }); + menus.reload(special_prompts_ids); taLog.log("Reloading menus"); return true; } @@ -202,7 +213,7 @@ messenger.runtime.onMessage.addListener((message, sender, sendResponse) => { async function _replyMessage(message) { const paragraphsHtmlString = message.text; //console.log(">>>>>>>>>>>> paragraphsHtmlString: " + paragraphsHtmlString); - let prefs = await browser.storage.sync.get({reply_type: 'reply_all'}); + let prefs = await browser.storage.sync.get({reply_type: prefs_default.reply_type}); //console.log('reply_type: ' + prefs.reply_type); let replyType = 'replyToAll'; if(prefs.reply_type === 'reply_sender'){ @@ -619,17 +630,18 @@ function checkScreenDimensions(prefs){ return prefs; } -async function doGetCalendarEvent(get_calendar_event) { - if(get_calendar_event) { - return (await checkSparksPresence() == 1); +function doGetSparkFeature(spark_feature_active) { + if(spark_feature_active) { + return (_sparks_presence == 1); } else { return false; } } async function reload_pref_init(){ - prefs_init = await browser.storage.sync.get({do_debug: false, add_tags: false, get_calendar_event: true, connection_type: 'chatgpt_web', add_tags_auto: false, add_tags_auto_force_existing: false, add_tags_auto_only_inbox: true, spamfilter: false, spamfilter_threshold: 70, dynamic_menu_force_enter: false, add_tags_context_menu: true, spamfilter_context_menu: true}); + prefs_init = await browser.storage.sync.get({do_debug: prefs_default.do_debug, add_tags: prefs_default.add_tags, get_calendar_event: prefs_default.get_calendar_event, get_task: prefs_default.get_task, connection_type: prefs_default.connection_type, add_tags_auto: prefs_default.add_tags_auto, add_tags_auto_force_existing: prefs_default.add_tags_auto_force_existing, add_tags_auto_only_inbox: prefs_default.add_tags_auto_only_inbox, spamfilter: prefs_default.spamfilter, spamfilter_threshold: prefs_default.spamfilter_threshold, dynamic_menu_force_enter: prefs_default.dynamic_menu_force_enter, add_tags_context_menu: prefs_default.add_tags_context_menu, spamfilter_context_menu: prefs_default.spamfilter_context_menu}); _process_incoming = prefs_init.add_tags_auto || prefs_init.spamfilter; + _sparks_presence = await checkSparksPresence(); } @@ -641,28 +653,57 @@ function setupStorageChangeListener() { // 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); - }); + let getCalendarEvent = doGetSparkFeature(prefs_init.get_calendar_event); + let getTask = doGetSparkFeature(prefs_init.get_task); + const special_prompts_ids = getActiveSpecialPromptsIDs({ + addtags: newTags, + get_calendar_event: getCalendarEvent, + get_task: getTask, + is_chatgpt_web: (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); - }); + let getCalendarEvent = doGetSparkFeature(newCalendarEvent); + let getTask = doGetSparkFeature(prefs_init.get_task); + const special_prompts_ids = getActiveSpecialPromptsIDs({ + addtags: prefs_init.add_tags, + get_calendar_event: getCalendarEvent, + get_task: getTask, + is_chatgpt_web: (prefs_init.connection_type === "chatgpt_web") + }); + menus.reload(special_prompts_ids); + } + + // Process 'get_task' changes + if (changes.get_task) { + const newTask = changes.get_task.newValue; + let getCalendarEvent = doGetSparkFeature(prefs_init.get_calendar_event); + let getTask = doGetSparkFeature(newTask); + const special_prompts_ids = getActiveSpecialPromptsIDs({ + addtags: prefs_init.add_tags, + get_calendar_event: getCalendarEvent, + get_task: getTask, + is_chatgpt_web: (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); - }); + let getCalendarEvent = doGetSparkFeature(prefs_init.get_calendar_event); + let getTask = doGetSparkFeature(prefs_init.get_task); + const special_prompts_ids = getActiveSpecialPromptsIDs({ + addtags: prefs_init.add_tags, + get_calendar_event: getCalendarEvent, + get_task: getTask, + is_chatgpt_web: (newConnectionType === "chatgpt_web") + }); + menus.reload(special_prompts_ids); if(newConnectionType === "chatgpt_web"){ removeContextMenu(contextMenuID_AddTags); @@ -821,7 +862,7 @@ async function processEmails(messages, addTagsAuto, spamFilter) { let tags_full_list = await getTagsList(); let chatgpt_lang = await taPromptUtils.getDefaultLang(curr_prompt_add_tags); specialFullPrompt_add_tags = await taPromptUtils.preparePrompt(curr_prompt_add_tags, message, chatgpt_lang, '', body_text, curr_fullMessage.headers.subject, msg_text, '', tags_full_list); - let prefs_aat = await browser.storage.sync.get({ add_tags_maxnum: 3, connection_type: '', add_tags_force_lang: true, default_chatgpt_lang: '', add_tags_auto_force_existing: false }); + let prefs_aat = await browser.storage.sync.get({ add_tags_maxnum: prefs_default.add_tags_maxnum, connection_type: prefs_default.connection_type, add_tags_force_lang: prefs_default.add_tags_force_lang, default_chatgpt_lang: prefs_default.default_chatgpt_lang, add_tags_auto_force_existing: prefs_default.add_tags_auto_force_existing }); specialFullPrompt_add_tags = taPromptUtils.finalizePrompt_add_tags(specialFullPrompt_add_tags, prefs_aat.add_tags_maxnum, prefs_aat.add_tags_force_lang, prefs_aat.default_chatgpt_lang); taLog.log("Special prompt: " + specialFullPrompt_add_tags); let cmd_addTags = new mzta_specialCommand(specialFullPrompt_add_tags, prefs_aat.connection_type, prefs_init.do_debug); diff --git a/options/mzta-options-default.js b/options/mzta-options-default.js index 573d2085..c4674218 100644 --- a/options/mzta-options-default.js +++ b/options/mzta-options-default.js @@ -52,6 +52,7 @@ export const prefs_default = { add_tags_auto_only_inbox: true, add_tags_context_menu: true, get_calendar_event: true, + get_task: true, calendar_enforce_timezone: false, calendar_timezone: '', spamfilter: false, diff --git a/options/mzta-options.html b/options/mzta-options.html index 30b8a790..08d987b1 100644 --- a/options/mzta-options.html +++ b/options/mzta-options.html @@ -374,6 +374,16 @@ +
__MSG_GetTask_info_default__
+| __MSG_prefs_OptionText_calendar_enforce_timezone__ | ++ + | +