Merge pull request #357 from micz/task

Handling Tasks
This commit is contained in:
Mic 2025-04-22 19:29:18 +02:00 committed by GitHub
commit 6f80126b8a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 647 additions and 99 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -374,6 +374,16 @@
</label>
</td>
</tr>
<tr class="get_task_tr">
<td><span>__MSG_prefs_OptionText_get_task__</span>
<br><button id="btnManageTaskInfo" class="btn_small">__MSG_prefs_OptionText_btnManageTaskInfo__</button></td>
<td>
<label>
<input type="checkbox" id="get_task" name="get_task" class="option-input" />
__MSG_prefs_OptionText_get_task_Info__
</label>
</td>
</tr>
<tr class="get_calendar_event_tr" id="no_sparks">
<td colspan="2">
<span id="no_sparks_text">__MSG_prefs_OptionText_get_calendar_event_Sparks_not_present__</span>

View file

@ -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', () => {

View file

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

View file

@ -0,0 +1,73 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ThunderAI - __MSG_GetTask_PageTitle__</title>
<link rel="stylesheet" href="mzta-get-task.css">
<link rel="icon" href="../../images/icon-16px.png">
</head>
<body>
<div>
<h1 class="page_title">__MSG_GetTask_PageTitle__</h1>
<p>__MSG_GetTask_info_default__</p>
</div>
<div class="get_calendar_event_table_title section_title">__MSG_get_calendar_event_prompt_prefs_title__</div>
<table id="miczPrefs">
<tr class="get_calendar_event_tr">
<td><span>__MSG_prefs_OptionText_calendar_enforce_timezone__</span></td>
<td>
<label>
<input type="checkbox" id="calendar_enforce_timezone" name="calendar_enforce_timezone" class="option-input" /> __MSG_prefs_OptionText_calendar_enforce_timezone_Info__
<br><label for="timezone">__MSG_Select_your_timezone__:</label>
<select id="calendar_timezone" name="calendar_timezone" class="option-input">
<option value=""></option>
<option value="Pacific/Midway">(UTC-11:00) Atol Midway</option>
<option value="Pacific/Honolulu">(UTC-10:00) Honolulu</option>
<option value="America/Anchorage">(UTC-09:00) Anchorage</option>
<option value="America/Los_Angeles">(UTC-08:00) Los Angeles</option>
<option value="America/Denver">(UTC-07:00) Denver</option>
<option value="America/Mexico_City">(UTC-06:00) Ciudad de Mexico</option>
<option value="America/New_York">(UTC-05:00) Nueva York</option>
<option value="America/Caracas">(UTC-04:00) Caracas</option>
<option value="America/Sao_Paulo">(UTC-03:00) Sao Paulo</option>
<option value="Atlantic/South_Georgia">(UTC-02:00) South Georgia</option>
<option value="Atlantic/Azores">(UTC-01:00) Azores</option>
<option value="Europe/London">(UTC+00:00) London</option>
<option value="Europe/Rome">(UTC+01:00) Roma</option>
<option value="Africa/Johannesburg">(UTC+02:00) Johannesburg</option>
<option value="Europe/Moscow">(UTC+03:00) Moscow</option>
<option value="Asia/Dubai">(UTC+04:00) Dubai</option>
<option value="Asia/Karachi">(UTC+05:00) Karachi</option>
<option value="Asia/Dhaka">(UTC+06:00) Dhaka</option>
<option value="Asia/Yangon">(UTC+06:30) Yangon</option>
<option value="Asia/Bangkok">(UTC+07:00) Bangkok</option>
<option value="Asia/Shanghai">(UTC+08:00) Shanghai</option>
<option value="Asia/Tokyo">(UTC+09:00) Tokyo</option>
<option value="Australia/Adelaide">(UTC+09:30) Adelaide</option>
<option value="Australia/Sydney">(UTC+10:00) Sydney</option>
<option value="Pacific/Noumea">(UTC+11:00) Noumea</option>
<option value="Pacific/Fiji">(UTC+12:00) Suva</option>
<option value="Pacific/Chatham">(UTC+12:45) Chatham Islands</option>
<option value="Pacific/Tongatapu">(UTC+13:00) Nuku'alofa</option>
<option value="Pacific/Kiritimati">(UTC+14:00) Kiritimati</option>
</select>
</label>
</td>
</tr>
</table>
<div id="get_task_container">
<span class="section_title">__MSG_GetCalendarEvent_prompt_text_title__</span><span id="get_task_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_task_prompt_text" rows="15"></textarea>
<ul class="autocomplete-list hidden"></ul>
</div>
<div id="get_task_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-task.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_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);
}

View file

@ -16,6 +16,7 @@
* 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 { 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;
}