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_prefs_OptionText_get_task__ +
+ + + + __MSG_prefs_OptionText_get_calendar_event_Sparks_not_present__ diff --git a/options/mzta-options.js b/options/mzta-options.js index ee575645..c4d61f80 100644 --- a/options/mzta-options.js +++ b/options/mzta-options.js @@ -22,7 +22,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, isThunderbird128OrGreater } from '../js/mzta-utils.js'; +import { checkSparksPresence, isThunderbird128OrGreater, openTab } from '../js/mzta-utils.js'; let taLog = new taLogger("mzta-options",true); let _isThunderbird128OrGreater = true; @@ -386,57 +386,32 @@ document.addEventListener('DOMContentLoaded', async () => { get_calendar_event_info_btn.disabled = event.target.checked ? '' : 'disabled'; }); get_calendar_event_info_btn.disabled = get_calendar_event_el.checked ? '' : 'disabled'; + + let get_task_el = document.getElementById('get_task'); + let get_task_info_btn = document.getElementById('btnManageTaskInfo'); + get_task_el.addEventListener('click', (event) => { + get_task_info_btn.disabled = event.target.checked ? '' : 'disabled'; + }); + get_task_info_btn.disabled = get_task_el.checked ? '' : 'disabled'; document.getElementById('btnManagePrompts').addEventListener('click', () => { - // check if the tab is already there - browser.tabs.query({url: browser.runtime.getURL('../pages/customprompts/mzta-custom-prompts.html')}).then((tabs) => { - if (tabs.length > 0) { - // if the tab is already there, focus it - browser.tabs.update(tabs[0].id, {active: true}); - } else { - // if the tab is not there, create it - browser.tabs.create({url: browser.runtime.getURL('../pages/customprompts/mzta-custom-prompts.html')}); - } - }) + openTab('../pages/customprompts/mzta-custom-prompts.html'); }); document.getElementById('btnManageTagsInfo').addEventListener('click', () => { - // check if the tab is already there - browser.tabs.query({url: browser.runtime.getURL('../pages/addtags/mzta-add-tags.html')}).then((tabs) => { - if (tabs.length > 0) { - // if the tab is already there, focus it - browser.tabs.update(tabs[0].id, {active: true}); - } else { - // if the tab is not there, create it - browser.tabs.create({url: browser.runtime.getURL('../pages/addtags/mzta-add-tags.html')}); - } - }) + openTab('../pages/addtags/mzta-add-tags.html'); }); document.getElementById('btnManageSpamFilterInfo').addEventListener('click', () => { - // check if the tab is already there - browser.tabs.query({url: browser.runtime.getURL('../pages/spamfilter/mzta-spamfilter.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/spamfilter/mzta-spamfilter.html')}); - } - }) + openTab('../pages/spamfilter/mzta-spamfilter.html'); }); 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')}); - } - }) + openTab('../pages/get-calendar-event/mzta-get-calendar-event.html'); + }); + + document.getElementById('btnManageTaskInfo').addEventListener('click', () => { + openTab('../pages/get-task/mzta-get-task.html'); }); document.getElementById('btnOpenAICompForceModel').addEventListener('click', () => { diff --git a/pages/get-task/mzta-get-task.css b/pages/get-task/mzta-get-task.css new file mode 100644 index 00000000..0ce83ec5 --- /dev/null +++ b/pages/get-task/mzta-get-task.css @@ -0,0 +1,119 @@ +#get_task_prompt_container{ + width: 90%; + margin: 20px auto; +} + +#get_task_prompt_text{ + width: 100%; +} + +.infoline{ + font-size: 0.8em; + font-style: italic; +} + +.btn_div{ + width: 100%; + display: flex; + justify-content: space-between; +} + +table#miczPrefs { + padding: 10px; + border-spacing: 0px; + width: 90%; + margin: 0px auto 40px auto; + border: 1px solid #ccc; +} + +.get_task_table_title{ + width: 90%; + margin: auto; +} + +.section_title{ + font-weight: bold; +} + +table#miczPrefs td{ + border-top: 1px solid #ccc; + border-bottom: 1px solid #ccc; + vertical-align: top; + padding: 2px; +} + +table#miczPrefs tr:first-child td { + border-top: none; +} + +table#miczPrefs tr:last-child td { + border-bottom: none; +} + +.autocomplete-container { + position: relative; +} + +.autocomplete-list { + position: absolute; + top: 100%; + left: 0; + right: 0; + background-color: white; + border: 1px solid #ccc; + z-index: 1000; + max-height: 200px; + overflow-y: auto; + padding: 0; + margin: 0; + list-style: none; + font-size: small; +} + +.autocomplete-list li { + padding: 8px; + cursor: pointer; +} + +.autocomplete-list li:hover { + background-color: #f0f0f0; +} + +.autocomplete-list li.active { + background-color: #ddd; +} + +.hidden { + display: none; +} + +.unsaved{ + color: red; +} + + +@media (prefers-color-scheme: dark) { + body { + background-color: #1C1B22; + color: rgb(251, 251, 254); + } + + a:link { color: #409EFF; } + a:visited { color: #409EFF; } + a:hover { color: #66B1FF; } + a:active { color: #66B1FF; } + a:active { color: #66B1FF; } + + .autocomplete-list { + background-color: #2E2F36; + border: 1px solid #2E2F36; + } + + .autocomplete-list li:hover { + background-color: #4c4e58; + } + + .autocomplete-list li.active { + background-color: #4c4e58; + } +} \ No newline at end of file diff --git a/pages/get-task/mzta-get-task.html b/pages/get-task/mzta-get-task.html new file mode 100644 index 00000000..98027d85 --- /dev/null +++ b/pages/get-task/mzta-get-task.html @@ -0,0 +1,73 @@ + + + + + ThunderAI - __MSG_GetTask_PageTitle__ + + + + +
+

__MSG_GetTask_PageTitle__

+

__MSG_GetTask_info_default__

+
+
__MSG_get_calendar_event_prompt_prefs_title__
+ + + + + +
__MSG_prefs_OptionText_calendar_enforce_timezone__ + +
+
+ __MSG_GetCalendarEvent_prompt_text_title__ +
__MSG_prefs_OptionText_btnManagePrompts_infoline__ __MSG_customPrompts_managePrompts_help__ +
__MSG_prefs_OptionText_GetCalendarEvent_infoline2__
+
+
+ + +
+
+
+
+ + + + \ No newline at end of file diff --git a/pages/get-task/mzta-get-task.js b/pages/get-task/mzta-get-task.js new file mode 100644 index 00000000..2b33e297 --- /dev/null +++ b/pages/get-task/mzta-get-task.js @@ -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 . + */ + +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_task_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_task'); + + get_calendar_event_textarea.addEventListener('input', (event) => { + get_calendar_event_reset_btn.disabled = (event.target.value === browser.i18n.getMessage('prompt_get_task_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_task_prompt_unsaved').classList.add('hidden'); + } else { + document.getElementById('get_task_prompt_unsaved').classList.remove('hidden'); + } + }); + + get_calendar_event_reset_btn.addEventListener('click', () => { + get_calendar_event_textarea.value = browser.i18n.getMessage('prompt_get_task_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_task').text = get_calendar_event_textarea.value; + setSpecialPrompts(specialPrompts); + get_calendar_event_save_btn.disabled = true; + document.getElementById('get_task_prompt_unsaved').classList.add('hidden'); + browser.runtime.sendMessage({command: "reload_menus"}); + }); + + if(get_calendar_event_prompt.text === 'prompt_get_task_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_task_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); +} \ No newline at end of file diff --git a/popup/mzta-popup.js b/popup/mzta-popup.js index 08e98b8d..f605bd23 100644 --- a/popup/mzta-popup.js +++ b/popup/mzta-popup.js @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +import { prefs_default } from "../options/mzta-options-default.js"; import { taLogger } from "../js/mzta-logger.js"; import { checkSparksPresence } from "../js/mzta-utils.js"; @@ -24,11 +25,13 @@ let taLog = console; let connection_type = 'chatgpt_web'; let add_tags = false; let get_calendar_event = false; +let get_task = false; +let _ok_sparks = 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: true, get_calendar_event: true, connection_type: 'chatgpt_web'}); + let prefs = await browser.storage.sync.get({do_debug: prefs_default.do_debug, dynamic_menu_force_enter: prefs_default.dynamic_menu_force_enter, 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}); taLog = new taLogger("mzta-popup",prefs.do_debug); i18n.updateDocument(); let reponse = await browser.runtime.sendMessage({command: "popup_menu_ready"}); @@ -44,6 +47,12 @@ document.addEventListener('DOMContentLoaded', async () => { connection_type = prefs.connection_type; add_tags = prefs.add_tags; get_calendar_event = prefs.get_calendar_event; + get_task = prefs.get_task; + _ok_sparks = await checkSparksPresence() == 1; + console.log(">>>>>>>>>>>>>>>>> add_tags: " + add_tags); + console.log(">>>>>>>>>>>>>>>>> get_calendar_event: " + get_calendar_event); + console.log(">>>>>>>>>>>>>>>>> get_task: " + get_task); + console.log(">>>>>>>>>>>>>>>>> _ok_sparks: " + _ok_sparks); searchPrompt(active_prompts, tabId, tabType); i18n.updateDocument(); @@ -116,18 +125,20 @@ async function searchPrompt(allPrompts, tabId, tabType){ let do_add_tags = checkDoAddTags(); let do_get_calendar_event = checkDoCalendarEvent(); + let do_get_task = checkDoTask(); - // console.log(">>>>>>>>>>> do_add_tags: " + do_add_tags); - // console.log(">>>>>>>>>>> do_get_calendar_event: " + do_get_calendar_event); - // console.log(">>>>>>>>>>> filteredData: " + JSON.stringify(filteredData)); + console.log(">>>>>>>>>>> do_add_tags: " + do_add_tags); + console.log(">>>>>>>>>>> do_get_calendar_event: " + do_get_calendar_event); + console.log(">>>>>>>>>>> do_get_task: " + do_get_task); + console.log(">>>>>>>>>>> filteredData: " + JSON.stringify(filteredData)); - num_special_menu_items = (do_add_tags ? 1 : 0) + (do_get_calendar_event ? 1 : 0); - // console.log(">>>>>>>>>>>> num_special_menu_items: " + num_special_menu_items); + num_special_menu_items = (do_add_tags ? 1 : 0) + (do_get_calendar_event ? 1 : 0) + (do_get_task ? 1 : 0); + // 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); + 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) { @@ -143,16 +154,18 @@ async function searchPrompt(allPrompts, tabId, tabType){ filteredData[gce_curr_pos].label = gce_curr_pos + '. ' + filteredData[gce_curr_pos].label; } } + if(do_get_task){ + filteredData = ensurePromptGetTaskFirst(filteredData, do_add_tags, do_get_calendar_event); + let gtask_curr_pos = (do_add_tags ? 1 : 0) + (do_get_calendar_event ? 1 : 0); + if (!filteredData[gtask_curr_pos].numberPrepended) { + filteredData[gtask_curr_pos].numberPrepended = 'true'; + filteredData[gtask_curr_pos].label = gtask_curr_pos + '. ' + filteredData[gtask_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; - // } - // } + + console.log(">>>>>>>>>>> filteredData after special items check: " + JSON.stringify(filteredData)); + Array.from(filteredData).slice(first_num_el, max_num_el).forEach((item, index) => { let number = (index + first_num_el).toString(); // Check if the number is already prepended to avoid duplication @@ -170,7 +183,7 @@ async function searchPrompt(allPrompts, tabId, tabType){ itemDiv.classList.add('mzta_autocomplete-item'); itemDiv.textContent = item.label; itemDiv.setAttribute('data-id', item.id); - if((item.id === 'prompt_add_tags')||(item.id === 'prompt_get_calendar_event')){ + if((item.id === 'prompt_add_tags')||(item.id === 'prompt_get_calendar_event')||(item.id === 'prompt_get_task')){ itemDiv.className += ' special_prompt'; } @@ -333,7 +346,11 @@ function checkDoAddTags(){ } function checkDoCalendarEvent(){ - return get_calendar_event && (connection_type !== "chatgpt_web" && tabType !== 'messageCompose') && (checkSparksPresence() == 1); + return get_calendar_event && (connection_type !== "chatgpt_web" && tabType !== 'messageCompose') && _ok_sparks; +} + +function checkDoTask(){ + return get_task && (connection_type !== "chatgpt_web" && tabType !== 'messageCompose') && _ok_sparks; } function ensurePromptAddTagsFirst(arr) { @@ -365,5 +382,24 @@ function ensurePromptGetCalendarEventFirst(arr, do_add_tags) { arr.splice(targetPosition, 0, promptAddTags); } + return arr; +} + +function ensurePromptGetTaskFirst(arr, do_add_tags, do_get_calendar_event) { + // Find the index of the object with id "prompt_get_task" + const index = arr.findIndex(item => item.id === "prompt_get_task"); + + // If found and needs repositioning + if (index !== -1 && ((do_get_calendar_event && do_add_tags) ? index !== 2 : (do_get_calendar_event ? index !== 1 : index !== 0))) { + // Remove it from its current position + const [promptGetTask] = arr.splice(index, 1); + + // Determine the target position to insert "prompt_get_task" after calendar + const targetPosition = do_add_tags && do_get_calendar_event ? 2 : (do_get_calendar_event ? 1 : 0); + + // Add it to the specified position + arr.splice(targetPosition, 0, promptGetTask); + } + return arr; } \ No newline at end of file