diff --git a/_locales/en/messages.json b/_locales/en/messages.json index c7ceded5..a7a0102d 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1003,6 +1003,10 @@ "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": "" }, + "Summarize_prompt_prefs_title": { + "message": "Add Summarization Options", + "description": "" + }, "prompt_summarize": { "message": "Summarize this email or these emails", "description": "" @@ -1215,6 +1219,10 @@ "message": "Summarize mail", "description": "" }, + "prefs_OptionText_summarize_use_specific_integration_Info": { + "message": "If checked, the Model and API specified below will be used for summarizing email(s), regardless the one choosen in the ThunderAI options page.", + "description": "" + }, "prefs_OptionText_summarize_Info": { "message": "If checked, adds an option to context menu to summarize mail.", "description": "" diff --git a/pages/summarize/mzta-summarize.css b/pages/summarize/mzta-summarize.css index f4bbc091..26b08e10 100644 --- a/pages/summarize/mzta-summarize.css +++ b/pages/summarize/mzta-summarize.css @@ -16,6 +16,49 @@ font-style: italic; } +#account_selector_container{ + display: none; +} + +#account_selector_checkboxes{ + padding: 10px; + border: 1px solid gray; + width: 24em; + margin-bottom: 10px; + margin-top: 10px; +} + +.specific_integration{ + background-color: #dfeaff; +} + +table { + border-collapse: separate; + border-spacing: 0; +} + +.group td { + border-top: none; + border-bottom: none; +} + +.specific_integration_sub td:first-child { + border-left: 10px solid #dfeaff; +} + +.specific_integration_sub td:last-child { + border-right: 10px solid #dfeaff; +} + +#connection_ui_end td{ + height: 6px; + background-color: #dfeaff; +} + +#connection_ui_end{ + display: none; +} + .btn_div { width: 100%; display: flex; @@ -30,6 +73,11 @@ table#miczPrefs { border: 1px solid #ccc; } +.addtags_table_title{ + width: 90%; + margin: auto; +} + .section_title { font-weight: bold; } @@ -90,27 +138,21 @@ table#miczPrefs tr:last-child td { color: red; } +label:has(input[type="checkbox"]) { + cursor: pointer; +} + @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; - } + 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; @@ -124,4 +166,24 @@ table#miczPrefs tr:last-child td { .autocomplete-list li.active { background-color: #4c4e58; } + + .summarize_auto{ + background-color: #415c35; + } + + .specific_integration{ + background-color: #2E3A4F; + } + + .specific_integration_sub td:first-child { + border-left: 10px solid #2E3A4F; + } + + .specific_integration_sub td:last-child { + border-right: 10px solid #2E3A4F; + } + + #connection_ui_end td{ + background-color: #2E3A4F; + } } diff --git a/pages/summarize/mzta-summarize.html b/pages/summarize/mzta-summarize.html index 54405840..82679339 100644 --- a/pages/summarize/mzta-summarize.html +++ b/pages/summarize/mzta-summarize.html @@ -4,13 +4,29 @@ ThunderAI - __MSG_Summarize_PageTitle__ + -
-

__MSG_Summarize_PageTitle__

-

__MSG_Summarize_info_default__

-
+
+

__MSG_Summarize_PageTitle__

+

__MSG_Summarize_info_default__

+
+
__MSG_Summarize_prompt_prefs_title__
+ + + + + + + +
__MSG_prefs_OptionText_use_specific_integration__ + + +
diff --git a/pages/summarize/mzta-summarize.js b/pages/summarize/mzta-summarize.js index 45f7463f..20252fc4 100644 --- a/pages/summarize/mzta-summarize.js +++ b/pages/summarize/mzta-summarize.js @@ -16,29 +16,71 @@ * along with this program. If not, see . */ +import { prefs_default, integration_options_config } 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 { + getSpecialPrompts, + setSpecialPrompts +} from "../../js/mzta-prompts.js"; +import { + getPlaceholders, + mapPlaceholderToSuggestion +} from "../../js/mzta-placeholders.js"; import { textareaAutocomplete } from "../../js/mzta-placeholders-autocomplete.js"; +import { + getAccountsList, + normalizeStringList, + isAPIKeyValue +} from "../../js/mzta-utils.js"; +import { + initializeSpecificIntegrationUI +} from "../_lib/connection-ui.js"; let autocompleteSuggestions = []; let taLog = new taLogger("mzta-summarize-page", true); document.addEventListener("DOMContentLoaded", async () => { - i18n.updateDocument(); - - - const summarize_textarea = document.getElementById("summarize_prompt_text"); - const summarize_save_btn = document.getElementById("btn_save_prompt"); - const summarize_reset_btn = document.getElementById("btn_reset_prompt"); - const summarize_textarea_email_template = document.getElementById("summarize_email_template_text"); - const summarize_reset_email_template_btn = document.getElementById("btn_reset_email_template"); - const summarize_save_email_template_btn = document.getElementById("btn_save_email_template"); - let specialPrompts = await getSpecialPrompts(); let summarize_prompt = specialPrompts.find((prompt) => prompt.id === 'prompt_summarize'); let summarize_email_template = specialPrompts.find((prompt) => prompt.id === 'prompt_summarize_email_template'); + + if (summarize_prompt && summarize_prompt.api_type && summarize_prompt.api_type !== '') { + let update_prefs = {}; + update_prefs['summarize_connection_type'] = summarize_prompt.api_type; + + let integration = summarize_prompt.api_type.replace('_api', ''); + if (integration_options_config && integration_options_config[integration]) { + for (const key of Object.keys(integration_options_config[integration])) { + if (summarize_prompt[key] !== undefined) { + update_prefs[`summarize_${integration}_${key}`] = summarize_prompt[key]; + } + } + } + await browser.storage.sync.set(update_prefs); + } + + await initializeSpecificIntegrationUI({ + prefix: 'summarize', + promptId: 'prompt_summarize', + taLog: taLog, + restoreOptionsCallback: restoreOptions + }); + + i18n.updateDocument(); + + document.querySelectorAll(".option-input").forEach(element => { + element.addEventListener("change", saveOptions); + }); + let prefs_summarize = await browser.storage.sync.get({ summarize_enabled_accounts: [], connection_type: 'chatgpt_web' }); + + let summarize_textarea = document.getElementById("summarize_prompt_text"); + let summarize_save_btn = document.getElementById("btn_save_prompt"); + let summarize_reset_btn = document.getElementById("btn_reset_prompt"); + let summarize_textarea_email_template = document.getElementById("summarize_email_template_text"); + let summarize_reset_email_template_btn = document.getElementById("btn_reset_email_template"); + let summarize_save_email_template_btn = document.getElementById("btn_save_email_template"); + summarize_textarea.addEventListener("input", (event) => { summarize_reset_btn.disabled = (event.target.value === browser.i18n.getMessage('prompt_summarize_full_text')); @@ -84,16 +126,123 @@ document.addEventListener("DOMContentLoaded", async () => { if(summarize_email_template === 'prompt_summarize_email_template'){ summarize_email_template.text = browser.i18n.getMessage(summarize_email_template.text); } - summarize_textarea_email_template.value = summarize_email_template.text; summarize_reset_email_template_btn.disabled = (summarize_email_template.value === browser.i18n.getMessage("prompt_summarize_email_template")); - summarize_textarea.value = summarize_prompt.text; summarize_reset_btn.disabled = (summarize_textarea.value === browser.i18n.getMessage("prompt_summarize_full_text")); - + autocompleteSuggestions = (await getPlaceholders(true)) .filter((p) => p.id !== "additional_text") - .map((p) => ({ command: `{%${p.id}%}`, type: p.type })); + .map(mapPlaceholderToSuggestion); textareaAutocomplete(summarize_textarea, autocompleteSuggestions, 1); + textareaAutocomplete(summarize_textarea_email_template, autocompleteSuggestions, 1); + }); +// Methods to manage options, derived from: /options/mzta-options.js + +function saveOptions(e) { + e.preventDefault(); + let options = {}; + let element = e.target; + // console.log(">>>>>>>>>> Saving option: " + element.id + " = " + element.value); + 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; + case 'select-one': + // console.log(">>>>>>>>>> Saving option [select-one]: " + element.id + " = " + element.value); + options[element.id] = element.value; + break; + case 'textarea': + if(element.id === 'add_tags_auto_uselist_list') { + element.value = normalizeStringList(element.value, 1); + } + options[element.id] = normalizeStringList(element.value); + break; + default: + 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 + " = " + (isAPIKeyValue(element.id) ? "****************" : 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 'textarea': + case 'password': + let default_text_value = ''; + if(element.id == 'default_chatgpt_lang') default_text_value = prefs_default.default_chatgpt_lang; + if(element.id === 'add_tags_auto_uselist_list') { + result[element.id] = normalizeStringList(result[element.id], 1); + } + element.value = result[element.id] || default_text_value; + break; + default: + if (element.tagName === 'SELECT') { + let default_select_value = ''; + const restoreValue = result[element.id] || default_select_value; + // Check if option exists + let optionExists = Array.from(element.options).some(opt => opt.value === restoreValue); + // If it doesn't exist and restoreValue is not empty, create it + if (!optionExists && restoreValue !== '') { + let newOption = new Option(restoreValue, restoreValue); + element.add(newOption); + } + // Set value + element.value = restoreValue; + if (element.value === '') { + element.selectedIndex = -1; + } + }else{ + console.error("[ThunderAI] Unhandled input type:", element.type); + } + } + }); + } + + let getting = await browser.storage.sync.get(prefs_default); + + let specialPrompts = await getSpecialPrompts(); + let addtags_prompt = specialPrompts.find(prompt => prompt.id === 'prompt_add_tags'); + + if (addtags_prompt) { + if (addtags_prompt.api_type && addtags_prompt.api_type !== '') { + getting['add_tags_connection_type'] = addtags_prompt.api_type; + } else { + getting['add_tags_connection_type'] = getting['connection_type']; + } + for (const [integration, options] of Object.entries(integration_options_config)) { + for (const key of Object.keys(options)) { + const propName = `${integration}_${key}`; + if (addtags_prompt[propName] !== undefined && addtags_prompt[propName] !== '') { + getting[`add_tags_${propName}`] = addtags_prompt[propName]; + } else { + getting[`add_tags_${propName}`] = getting[propName]; + } + } + } + } + + setCurrentChoice(getting); +}