From a9c8c1af0b15a2841546b4625d737961f23f7568 Mon Sep 17 00:00:00 2001 From: mic Date: Tue, 11 Feb 2025 22:04:32 +0100 Subject: [PATCH] spamfilter manage page added. see #231 --- _locales/en/messages.json | 26 ++++- pages/spamfilter/mzta-spamfilter.css | 87 +++++++++++++++ pages/spamfilter/mzta-spamfilter.html | 29 +++++ pages/spamfilter/mzta-spamfilter.js | 149 ++++++++++++++++++++++++++ 4 files changed, 290 insertions(+), 1 deletion(-) create mode 100644 pages/spamfilter/mzta-spamfilter.css create mode 100644 pages/spamfilter/mzta-spamfilter.html create mode 100644 pages/spamfilter/mzta-spamfilter.js diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 927f9da5..e8790e2e 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1060,7 +1060,31 @@ "description": "" }, "prefs_OptionText_btnManageSpamFilterInfo": { - "message": "Manage Spam Filter settings", + "message": "Manage spam filter settings", "description": "" + }, + "SpamFilter_PageTitle": { + "message": "Manage Spam Filter Settings", + "description": "" + }, + "SpamFilter_info_default": { + "message": "In this page you can modify the default prompt used to detect spam emails.", + "description": "" + }, + "SpamFilter_prompt_text_title": { + "message": "Current prompt text", + "description": "" + }, + "prefs_OptionText_spamfilter_infoline": { + "message": "You can change the prompt as you wish, but the response received from the AI must be in JSON format as specified in the default prompt!", + "description": "" + }, + "prompt_spamfilter" : { + "message" : "Detect spam emails", + "description" : "" + }, + "prompt_spamfilter_full_text" : { + "message" : "Analyze the following email and determine if it is spam or not. Consider factors such as suspicious keywords, excessive promotional language, misleading subject lines, requests for personal information, and unusual sender addresses.\nProvide a value from 0 (not spam) to 100 (spam) and an explaination of no more than 10 words.\nGenerate a response in JSON format only. Do not include any additional text or explanation; provide only the JSON. Here is the format to be used:\n{\n\"spamValue\": ,\n\"explanation\": \"Brief explanation of your reasoning\",\n}\nHere there are the mail information:\nSender: \"{%author%}\"\nSubject: \"{%mail_subject%}\"\nHtml body: \"{%mail_html_body%}\"", + "description" : "" } } \ No newline at end of file diff --git a/pages/spamfilter/mzta-spamfilter.css b/pages/spamfilter/mzta-spamfilter.css new file mode 100644 index 00000000..7d984c80 --- /dev/null +++ b/pages/spamfilter/mzta-spamfilter.css @@ -0,0 +1,87 @@ +#spamfilter_prompt_container{ + width: 90%; + margin: 20px auto; +} + +#spamfilter_prompt_text{ + width: 100%; +} + +.infoline{ + font-size: 0.8em; + font-style: italic; +} + +.btn_div{ + width: 100%; + display: flex; + justify-content: space-between; +} + +.autocomplete-container { + position: relative; +} + +.autocomplete-list { + position: absolute; + top: 100%; + left: 0; + right: 0; + background-color: white; + border: 1px solid #ccc; + z-index: 1000; + max-height: 200px; + overflow-y: auto; + padding: 0; + margin: 0; + list-style: none; + font-size: small; +} + +.autocomplete-list li { + padding: 8px; + cursor: pointer; +} + +.autocomplete-list li:hover { + background-color: #f0f0f0; +} + +.autocomplete-list li.active { + background-color: #ddd; +} + +.hidden { + display: none; +} + +.unsaved{ + color: red; +} + + +@media (prefers-color-scheme: dark) { + body { + background-color: #1C1B22; + color: rgb(251, 251, 254); + } + + a:link { color: #409EFF; } + a:visited { color: #409EFF; } + a:hover { color: #66B1FF; } + a:active { color: #66B1FF; } + a:active { color: #66B1FF; } + + .autocomplete-list { + background-color: #2E2F36; + border: 1px solid #2E2F36; + } + + .autocomplete-list li:hover { + background-color: #4c4e58; + } + + .autocomplete-list li.active { + background-color: #4c4e58; + } +} \ No newline at end of file diff --git a/pages/spamfilter/mzta-spamfilter.html b/pages/spamfilter/mzta-spamfilter.html new file mode 100644 index 00000000..c717674f --- /dev/null +++ b/pages/spamfilter/mzta-spamfilter.html @@ -0,0 +1,29 @@ + + + + + ThunderAI - __MSG_SpamFilter_PageTitle__ + + + + +
+

__MSG_SpamFilter_PageTitle__

+

__MSG_SpamFilter_info_default__

+
+
+ __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/spamfilter/mzta-spamfilter.js b/pages/spamfilter/mzta-spamfilter.js new file mode 100644 index 00000000..54fca13f --- /dev/null +++ b/pages/spamfilter/mzta-spamfilter.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-spamfilter-page",true); + +document.addEventListener('DOMContentLoaded', async () => { + + i18n.updateDocument(); + await restoreOptions(); + + document.querySelectorAll(".option-input").forEach(element => { + element.addEventListener("change", saveOptions); + }); + + let spamfilter_textarea = document.getElementById('spamfilter_prompt_text'); + let spamfilter_save_btn = document.getElementById('btn_save_prompt'); + let spamfilter_reset_btn = document.getElementById('btn_reset_prompt'); + + let specialPrompts = await getSpecialPrompts(); + let spamfilter_prompt = specialPrompts.find(prompt => prompt.id === 'prompt_spamfilter'); + + spamfilter_textarea.addEventListener('input', (event) => { + spamfilter_reset_btn.disabled = (event.target.value === browser.i18n.getMessage('prompt_spamfilter_full_text')); + spamfilter_save_btn.disabled = (event.target.value === spamfilter_prompt.text); + if(spamfilter_save_btn.disabled){ + document.getElementById('spamfilter_prompt_unsaved').classList.add('hidden'); + } else { + document.getElementById('spamfilter_prompt_unsaved').classList.remove('hidden'); + } + }); + + spamfilter_reset_btn.addEventListener('click', () => { + spamfilter_textarea.value = browser.i18n.getMessage('prompt_spamfilter_full_text'); + spamfilter_reset_btn.disabled = true; + let event = new Event('input', { bubbles: true, cancelable: true }); + spamfilter_textarea.dispatchEvent(event); + }); + + spamfilter_save_btn.addEventListener('click', () => { + specialPrompts.find(prompt => prompt.id === 'prompt_spamfilter').text = spamfilter_textarea.value; + setSpecialPrompts(specialPrompts); + spamfilter_save_btn.disabled = true; + document.getElementById('spamfilter_prompt_unsaved').classList.add('hidden'); + browser.runtime.sendMessage({command: "reload_menus"}); + }); + + if(spamfilter_prompt.text === 'prompt_spamfilter_full_text'){ + spamfilter_prompt.text = browser.i18n.getMessage(spamfilter_prompt.text); + } + spamfilter_textarea.value = spamfilter_prompt.text; + spamfilter_reset_btn.disabled = (spamfilter_textarea.value === browser.i18n.getMessage('prompt_spamfilter_full_text')); + + autocompleteSuggestions = (await getPlaceholders(true)).filter(p => !(p.id === 'additional_text')).map(p => ({command: '{%'+p.id+'%}', type: p.type})); + textareaAutocomplete(spamfilter_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