commit
a9acf76333
16 changed files with 802 additions and 35 deletions
|
|
@ -8,6 +8,7 @@
|
|||
<h2>Version 3.2.0 - ??/??/2025</h2>
|
||||
<ul>
|
||||
<li><i>[ChatGPT API][Ollama API][OpenAI Comp API][Gemini API]</i> Added an option to automatically tag incoming emails [<a href="https://github.com/micz/ThunderAI/issues/237">#237</a>].</li>
|
||||
<li><i>[ChatGPT API][Ollama API][OpenAI Comp API][Gemini API]</i> Added an configurable antispam filter for incoming emails [<a href="https://github.com/micz/ThunderAI/issues/231">#231</a>].</li>
|
||||
<li>Added the <i>{%thunderai_def_sign%}</i> placeholder to get the default signature as defined in the options [<a href="https://github.com/micz/ThunderAI/issues/248">#248</a>].</li>
|
||||
<li>Added the <i>{%thunderai_def_lang%}</i> placeholder to get the default language as defined in the options [<a href="https://github.com/micz/ThunderAI/issues/248">#248</a>].</li>
|
||||
<li>Various minor improvements.</li>
|
||||
|
|
|
|||
|
|
@ -1050,5 +1050,105 @@
|
|||
"thunderai_def_lang": {
|
||||
"message": "Default language as defined in ThunderAI options.",
|
||||
"description": ""
|
||||
},
|
||||
"prefs_OptionText_spamfilter": {
|
||||
"message": "Automatic Spam Filter",
|
||||
"description": ""
|
||||
},
|
||||
"prefs_OptionText_spamfilter_Info": {
|
||||
"message": "If checked, ThunderAI will automatically move spam emails to the spam folder.",
|
||||
"description": ""
|
||||
},
|
||||
"prefs_OptionText_btnManageSpamFilterInfo": {
|
||||
"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\": <integer from 0 to 100>,\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" : ""
|
||||
},
|
||||
"SpamFilter_prompt_prefs_title": {
|
||||
"message": "Spam Filter Options",
|
||||
"description": ""
|
||||
},
|
||||
"prefs_OptionText_spamfilter_threshold": {
|
||||
"message": "Spam threshold",
|
||||
"description": ""
|
||||
},
|
||||
"prefs_OptionText_spamfilter_threshold_Info": {
|
||||
"message": "If the value returned by the AI is above this threshold, the email will be moved to the spam folder.",
|
||||
"description": ""
|
||||
},
|
||||
"spamfilter_threshold_too_low": {
|
||||
"message": "The spam threshold is too low! You will likely mark too much mails as spam!",
|
||||
"description": ""
|
||||
},
|
||||
"spamfilter_threshold_zero": {
|
||||
"message": "The spam threshold is zero! You will mark all mails as spam!",
|
||||
"description": ""
|
||||
},
|
||||
"spamfilter_no_reports": {
|
||||
"message": "No messages have screened for spam yet. Here you'll find a list of the last 100 spam reports for the current session only.",
|
||||
"description": ""
|
||||
},
|
||||
"SpamReport_Title": {
|
||||
"message": "Spam Filter Reports",
|
||||
"description": ""
|
||||
},
|
||||
"Date": {
|
||||
"message": "Date",
|
||||
"description": ""
|
||||
},
|
||||
"From": {
|
||||
"message": "From",
|
||||
"description": ""
|
||||
},
|
||||
"Subject": {
|
||||
"message": "Subject",
|
||||
"description": ""
|
||||
},
|
||||
"Spam_Value": {
|
||||
"message": "Spam Value",
|
||||
"description": ""
|
||||
},
|
||||
"Moved_to_Spam": {
|
||||
"message": "Moved to Spam",
|
||||
"description": ""
|
||||
},
|
||||
"Explanation": {
|
||||
"message": "Explanation",
|
||||
"description": ""
|
||||
},
|
||||
"Report_Date": {
|
||||
"message": "Report Date",
|
||||
"description": ""
|
||||
},
|
||||
"spamfilter_moved": {
|
||||
"message": "Yes",
|
||||
"description": ""
|
||||
},
|
||||
"spamfilter_not_moved": {
|
||||
"message": "No",
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
|
|
@ -318,6 +318,8 @@ export const placeholdersUtils = {
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return finalSubs;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -195,6 +195,19 @@ const specialPrompts = [
|
|||
is_default: "1",
|
||||
is_special: "1",
|
||||
},
|
||||
{
|
||||
id: 'prompt_spamfilter',
|
||||
name: "__MSG_prompt_spamfilter__",
|
||||
text: "prompt_spamfilter_full_text",
|
||||
type: "1",
|
||||
action: "0",
|
||||
need_selected: "0",
|
||||
need_signature: "0",
|
||||
need_custom_text: "0",
|
||||
define_response_lang: "0",
|
||||
is_default: "1",
|
||||
is_special: "1",
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
|
|
@ -359,4 +372,8 @@ export async function getSpecialPrompts(){
|
|||
export async function setSpecialPrompts(prompts) {
|
||||
// console.log(">>>>>>>>>>>> setSpecialPrompts prompts: " + JSON.stringify(prompts));
|
||||
await browser.storage.local.set({_special_prompts: prompts});
|
||||
}
|
||||
|
||||
export async function getSpamFilterPrompt(){
|
||||
return (await getSpecialPrompts()).find(prompt => prompt.id == 'prompt_spamfilter');
|
||||
}
|
||||
91
js/mzta-spamreport.js
Normal file
91
js/mzta-spamreport.js
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
export const taSpamReport = {
|
||||
|
||||
logger: console,
|
||||
_internal_data_id: 'mzta-spam-report-data',
|
||||
_max_reports: 100,
|
||||
|
||||
async saveReportData(data, data_id){
|
||||
let obj = await browser.storage.session.get(taSpamReport._internal_data_id);
|
||||
//console.log(">>>>>>>> saveReportData obj 1: " + JSON.stringify(obj));
|
||||
if(obj == undefined){
|
||||
obj = {};
|
||||
obj[taSpamReport._internal_data_id] = {};
|
||||
//console.log(">>>>>>>> saveReportData obj 2a: " + JSON.stringify(obj));
|
||||
}
|
||||
if(Object.keys(obj).length === 0){
|
||||
obj[taSpamReport._internal_data_id] = {};
|
||||
//console.log(">>>>>>>> saveReportData obj 2b: " + JSON.stringify(obj));
|
||||
}
|
||||
|
||||
obj[taSpamReport._internal_data_id][data_id] = data;
|
||||
//console.log(">>>>>>>> saveReportData obj 3: " + JSON.stringify(obj));
|
||||
await browser.storage.session.set(obj);
|
||||
},
|
||||
|
||||
async loadReportData(data_id){
|
||||
let output = await browser.storage.session.get(taSpamReport._internal_data_id);
|
||||
return output[taSpamReport._internal_data_id][data_id];
|
||||
},
|
||||
|
||||
async getAllReportData(){
|
||||
let data = await browser.storage.session.get(taSpamReport._internal_data_id);
|
||||
//console.log(">>>>>>>>>>> getAllReportData: " + JSON.stringify(data));
|
||||
return data[taSpamReport._internal_data_id];
|
||||
},
|
||||
|
||||
saveAllData(data){
|
||||
browser.storage.session.set({[taSpamReport._internal_data_id]: data});
|
||||
},
|
||||
|
||||
clearReportData(){
|
||||
browser.storage.session.clear();
|
||||
},
|
||||
|
||||
async truncReportData(){
|
||||
let data = taSpamReport.sortReportsByDate(await taSpamReport.getAllReportData());
|
||||
if(data == undefined) return;
|
||||
//console.log(">>>>>>>>>> truncReportData: " + JSON.stringify(data));
|
||||
if(Object.keys(data).length > taSpamReport._max_reports){
|
||||
let keys = Object.keys(data);
|
||||
for(let i = taSpamReport._max_reports; i < keys.length; i++){
|
||||
// console.log(">>>>>>>>>> truncReportData: " + keys[i]);
|
||||
// console.log(">>>>>>>>>> truncReportData i: " + i);
|
||||
delete data[keys[i]];
|
||||
}
|
||||
}
|
||||
taSpamReport.saveAllData(data);
|
||||
},
|
||||
|
||||
sortReportsByDate(data) {
|
||||
if(data == undefined) return undefined;
|
||||
const reportKeys = Object.keys(data);
|
||||
reportKeys.sort((a, b) => {
|
||||
const dateA = new Date(data[a].report_date);
|
||||
const dateB = new Date(data[b].report_date);
|
||||
return dateB - dateA;
|
||||
});
|
||||
const sortedReports = {};
|
||||
reportKeys.forEach((key) => {
|
||||
sortedReports[key] = data[key];
|
||||
});
|
||||
return sortedReports;
|
||||
}
|
||||
}
|
||||
|
|
@ -37,7 +37,7 @@ export const taPromptUtils = {
|
|||
fullPrompt = curr_prompt.text + (String(curr_prompt.need_signature) == "1" ? " " + await taPromptUtils.getDefaultSignature():"") + " " + chatgpt_lang + " \"" + (selection_text=='' ? body_text : selection_text) + "\" ";
|
||||
}else{
|
||||
// we have at least a placeholder, do the magic!
|
||||
let finalSubs = placeholdersUtils.getPlaceholdersValues(curr_prompt.text, curr_message, subject_text, body_text, msg_text, only_typed_text, selection_text, tags_full_list);
|
||||
let finalSubs = await placeholdersUtils.getPlaceholdersValues(curr_prompt.text, curr_message, subject_text, body_text, msg_text, only_typed_text, selection_text, tags_full_list);
|
||||
// console.log(">>>>>>>>>> finalSubs: " + JSON.stringify(finalSubs));
|
||||
let prefs_ph = await browser.storage.sync.get({placeholders_use_default_value: false});
|
||||
fullPrompt = (placeholdersUtils.replacePlaceholders(curr_prompt.text, finalSubs, prefs_ph.placeholders_use_default_value, true) + (String(curr_prompt.need_signature) == "1" ? " " + await taPromptUtils.getDefaultSignature():"") + " " + chatgpt_lang).trim();
|
||||
|
|
|
|||
|
|
@ -302,6 +302,23 @@ export function getActiveSpecialPromptsIDs(addtags = false, get_calendar_event =
|
|||
return output;
|
||||
}
|
||||
|
||||
export function extractJsonObject(inputString) {
|
||||
try {
|
||||
const jsonMatch = inputString.match(/\{[\s\S]*\}/);
|
||||
if (jsonMatch) {
|
||||
const jsonObject = JSON.parse(jsonMatch[0]);
|
||||
// console.log(">>>>>>>>>> Extracted JSON object:", jsonObject);
|
||||
return jsonObject;
|
||||
} else {
|
||||
console.error("[ThunderAI] No JSON object found in the input string.");
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("[ThunderAI] Error extracting JSON object:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function checkSparksPresence() {
|
||||
try {
|
||||
return (await browser.runtime.sendMessage('thunderai-sparks@micz.it',{action: "checkPresence"}) === 'ok');
|
||||
|
|
|
|||
|
|
@ -36,6 +36,9 @@
|
|||
"messagesUpdate",
|
||||
"https://*.chatgpt.com/*"
|
||||
],
|
||||
"optional_permissions": [
|
||||
"messagesMove"
|
||||
],
|
||||
"background": {
|
||||
"page": "mzta-background.html"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -20,9 +20,13 @@ import { mzta_script } from './js/mzta-chatgpt.js';
|
|||
import { prefs_default } from './options/mzta-options-default.js';
|
||||
import { mzta_Menus } from './js/mzta-menus.js';
|
||||
import { taLogger } from './js/mzta-logger.js';
|
||||
import { getCurrentIdentity, getOriginalBody, replaceBody, setBody, i18nConditionalGet, generateCallID, migrateCustomPromptsStorage, migrateDefaultPromptsPropStorage, getGPTWebModelString, getTagsList, createTag, assignTagsToMessage, checkIfTagExists, getActiveSpecialPromptsIDs, checkSparksPresence, getMessages, getMailBody } from './js/mzta-utils.js';
|
||||
import { getCurrentIdentity, getOriginalBody, replaceBody, setBody, i18nConditionalGet, generateCallID, migrateCustomPromptsStorage, migrateDefaultPromptsPropStorage, getGPTWebModelString, getTagsList, createTag, assignTagsToMessage, checkIfTagExists, getActiveSpecialPromptsIDs, checkSparksPresence, getMessages, getMailBody, extractJsonObject } from './js/mzta-utils.js';
|
||||
import { taPromptUtils } from './js/mzta-utils-prompt.js';
|
||||
import { mzta_specialCommand } from './js/mzta-special-commands.js';
|
||||
import { getSpamFilterPrompt } from './js/mzta-prompts.js';
|
||||
import { taSpamReport } from './js/mzta-spamreport.js';
|
||||
|
||||
//browser.permissions.remove({ permissions: ["messagesMove"] }).then(console.log);
|
||||
|
||||
await migrateCustomPromptsStorage();
|
||||
await migrateDefaultPromptsPropStorage();
|
||||
|
|
@ -601,7 +605,7 @@ async function doGetCalendarEvent(get_calendar_event) {
|
|||
}
|
||||
|
||||
async function reload_pref_init(){
|
||||
prefs_init = await browser.storage.sync.get({do_debug: false, add_tags: true, get_calendar_event: true, connection_type: 'chatgpt_web', add_tags_auto: false, add_tags_auto_force_existing: false, add_tags_auto_only_inbox: true});
|
||||
prefs_init = await browser.storage.sync.get({do_debug: false, add_tags: true, 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});
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -657,52 +661,54 @@ const newEmailListener = (folder, messagesList) => {
|
|||
async function _newEmailListener(){
|
||||
let messages = getMessages(messagesList);
|
||||
|
||||
taSpamReport.logger = taLog;
|
||||
|
||||
for await (let message of messages) {
|
||||
let fullMessage = await browser.messages.getFull(message.id);
|
||||
// let fullMessage = await browser.messages.getFull(message.id);
|
||||
// taLog.log(`From: ${fullMessage.headers.from}`);
|
||||
// taLog.log(`Subject: ${fullMessage.headers.subject}`);
|
||||
// taLog.log(`Name: ${fullMessage.name}`);
|
||||
// taLog.log(`Body: ${fullMessage.body}`);
|
||||
// taLog.log(`contentType: [${fullMessage.contentType}]`);
|
||||
// taLog.log(`fullMessage.parts.length: ${fullMessage.parts.length}`);
|
||||
|
||||
taLog.log(`From: ${fullMessage.headers.from}`);
|
||||
taLog.log(`Subject: ${fullMessage.headers.subject}`);
|
||||
taLog.log(`Name: ${fullMessage.name}`);
|
||||
taLog.log(`Body: ${fullMessage.body}`);
|
||||
taLog.log(`contentType: [${fullMessage.contentType}]`);
|
||||
taLog.log(`fullMessage.parts.length: ${fullMessage.parts.length}`);
|
||||
|
||||
if (fullMessage.parts.length > 0) {
|
||||
for (let part of fullMessage.parts) {
|
||||
taLog.log(`From: ${part.headers.from}`);
|
||||
taLog.log(`Subject: ${part.headers.subject}`);
|
||||
taLog.log(`Name: ${part.name}`);
|
||||
taLog.log(`Body: ${part.body}`);
|
||||
taLog.log(`contentType: [${part.contentType}]`);
|
||||
taLog.log('contentType JSON: '+JSON.stringify(part.contentType));
|
||||
if (part.contentType.trim().toLowerCase() === "text/plain") {
|
||||
taLog.log("Body (text/plain):" + part.body);
|
||||
// return part.body;
|
||||
}
|
||||
if (part.contentType.trim().toLowerCase() === "text/html") {
|
||||
taLog.log("Body (text/html):" + part.body);
|
||||
// return part.body;
|
||||
}
|
||||
}
|
||||
}
|
||||
// if (fullMessage.parts.length > 0) {
|
||||
// for (let part of fullMessage.parts) {
|
||||
// taLog.log(`From: ${part.headers.from}`);
|
||||
// taLog.log(`Subject: ${part.headers.subject}`);
|
||||
// taLog.log(`Name: ${part.name}`);
|
||||
// taLog.log(`Body: ${part.body}`);
|
||||
// taLog.log(`contentType: [${part.contentType}]`);
|
||||
// taLog.log('contentType JSON: '+JSON.stringify(part.contentType));
|
||||
// if (part.contentType.trim().toLowerCase() === "text/plain") {
|
||||
// taLog.log("Body (text/plain):" + part.body);
|
||||
// // return part.body;
|
||||
// }
|
||||
// if (part.contentType.trim().toLowerCase() === "text/html") {
|
||||
// taLog.log("Body (text/html):" + part.body);
|
||||
// // return part.body;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
let curr_fullMessage = null;
|
||||
let msg_text = null;
|
||||
let body_text = '';
|
||||
|
||||
// if some auto feature is active prepare some data
|
||||
// this will be used when will be implemented the spam filter (https://github.com/micz/ThunderAI/issues/231)
|
||||
// or the auto translator https://github.com/micz/ThunderAI/issues/247
|
||||
if(prefs_init.add_tags_auto){
|
||||
// this will be used when will be implemented also the auto translator https://github.com/micz/ThunderAI/issues/247
|
||||
if(prefs_init.add_tags_auto || prefs_init.spamfilter){
|
||||
curr_fullMessage = await browser.messages.getFull(message.id);
|
||||
msg_text = await getMailBody(curr_fullMessage);
|
||||
body_text = msg_text.text.replace(/\s+/g, ' ').trim();
|
||||
}
|
||||
|
||||
// Auto add_tags
|
||||
if(prefs_init.add_tags_auto){
|
||||
let specialFullPrompt_add_tags = '';
|
||||
let curr_prompt_add_tags = menus.allPrompts.find(p => p.id === 'prompt_add_tags');
|
||||
let chatgpt_lang = taPromptUtils.getDefaultLang(curr_prompt_add_tags);
|
||||
let msg_text = await getMailBody(curr_fullMessage);
|
||||
let body_text = msg_text.text.replace(/\s+/g, ' ').trim();
|
||||
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, /*selection_text*/ '', body_text, curr_fullMessage.headers.subject, msg_text, /*only_typed_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});
|
||||
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);
|
||||
|
|
@ -719,6 +725,73 @@ const newEmailListener = (folder, messagesList) => {
|
|||
let _data = {messageId: message.id, tags: tags_current_email.split(/,\s*/)};
|
||||
_assign_tags(_data, !prefs_aat.add_tags_auto_force_existing);
|
||||
}
|
||||
|
||||
|
||||
// Spam filter
|
||||
if(prefs_init.spamfilter){
|
||||
let curr_prompt_spamfilter = await getSpamFilterPrompt();
|
||||
//console.log(">>>>>>>>>>>> curr_prompt_spamfilter: " + JSON.stringify(curr_prompt_spamfilter));
|
||||
let chatgpt_lang = await taPromptUtils.getDefaultLang(curr_prompt_spamfilter);
|
||||
let specialFullPrompt_spamfilter = await taPromptUtils.preparePrompt(curr_prompt_spamfilter, message, chatgpt_lang, /*selection_text*/ '', body_text, curr_fullMessage.headers.subject, msg_text, /*only_typed_text*/ '', '');
|
||||
taLog.log("Special prompt: " + specialFullPrompt_spamfilter);
|
||||
let cmd_spamfilter = new mzta_specialCommand(specialFullPrompt_spamfilter,prefs_init.connection_type,prefs_init.do_debug);
|
||||
await cmd_spamfilter.initWorker();
|
||||
/* We expect to receive from the AI a JSON object like this:
|
||||
* {
|
||||
* "spamValue": <integer from 0 to 100>,
|
||||
* "explanation": "Brief explanation of your reasoning",
|
||||
* }
|
||||
*/
|
||||
let spamfilter_result = '';
|
||||
taLog.log("Sending the prompt...");
|
||||
try{
|
||||
spamfilter_result = (await cmd_spamfilter.sendPrompt()).trim();
|
||||
}catch(err){
|
||||
console.error("[ThunderAI | SpamFilter] Error getting spamfilter: ", err);
|
||||
}
|
||||
taLog.log("spamfilter_result: " + spamfilter_result);
|
||||
let jsonObj = {};
|
||||
taLog.log("Decoding the AI response...");
|
||||
try{
|
||||
jsonObj = extractJsonObject(spamfilter_result);
|
||||
}catch(e){
|
||||
console.error("[ThunderAI | SpamFilter] Error extracting JSON from AI response: ", e);
|
||||
}
|
||||
taLog.log("SpamFilter jsonObj: " + JSON.stringify(jsonObj));
|
||||
|
||||
let report_data = {};
|
||||
report_data.report_date = new Date();
|
||||
report_data.headerMessageId = message.headerMessageId;
|
||||
report_data.spamValue = jsonObj.spamValue;
|
||||
report_data.explanation = jsonObj.explanation;
|
||||
report_data.subject = curr_fullMessage.headers.subject;
|
||||
report_data.from = curr_fullMessage.headers.from;
|
||||
report_data.message_date = new Date(message.date);
|
||||
report_data.moved = false;
|
||||
report_data.SpamThreshold = prefs_init.spamfilter_threshold;
|
||||
//console.log(">>>>>>>>>>>> report_data.SpamThreshold: " + JSON.stringify(report_data.SpamThreshold));
|
||||
|
||||
//TODO move email if it's spam
|
||||
if(jsonObj.spamValue >= prefs_init.spamfilter_threshold){
|
||||
taLog.log("Marking as spam ["+message.headerMessageId+"]");
|
||||
// mark as spam
|
||||
messenger.messages.update(message.id, { junk: true });
|
||||
//get the spam folder
|
||||
let spamFolder = await messenger.folders.query({accountId: message.folder.accountId, specialUse: ['junk']})
|
||||
//console.log(">>>>>>>>>>>> spamFolder: " + JSON.stringify(spamFolder));
|
||||
//move the message
|
||||
messenger.messages.move([message.id], spamFolder[0].id);
|
||||
report_data.moved = true;
|
||||
taLog.log("Marked as spam ["+message.headerMessageId+"]");
|
||||
}
|
||||
|
||||
// Save the operation log
|
||||
taSpamReport.saveReportData(report_data,message.headerMessageId);
|
||||
}
|
||||
}
|
||||
|
||||
if(prefs_init.spamfilter){
|
||||
taSpamReport.truncReportData();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -51,4 +51,6 @@ export const prefs_default = {
|
|||
add_tags_auto_force_existing: false,
|
||||
add_tags_auto_only_inbox: true,
|
||||
get_calendar_event: true,
|
||||
spamfilter: false,
|
||||
spamfilter_threshold: 70,
|
||||
}
|
||||
|
|
@ -334,6 +334,16 @@
|
|||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="spamfilter_tr">
|
||||
<td><span>__MSG_prefs_OptionText_spamfilter__</span>
|
||||
<br><button id="btnManageSpamFilterInfo" class="btn_small">__MSG_prefs_OptionText_btnManageSpamFilterInfo__</button></td>
|
||||
<td>
|
||||
<label>
|
||||
<input type="checkbox" id="spamfilter" name="spamfilter" class="option-input" />
|
||||
__MSG_prefs_OptionText_spamfilter_Info__
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="get_calendar_event_tr">
|
||||
<td><span>__MSG_prefs_OptionText_get_calendar_event__</span>
|
||||
<br><button id="btnManageCalendarEventInfo" class="btn_small">__MSG_prefs_OptionText_btnManageCalendarEventInfo__</button></td>
|
||||
|
|
|
|||
|
|
@ -304,6 +304,23 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||
});
|
||||
addtags_info_btn.disabled = addtags_el.checked ? '' : 'disabled';
|
||||
|
||||
let spamfilter_el = document.getElementById('spamfilter');
|
||||
let spamfilter_info_btn = document.getElementById('btnManageSpamFilterInfo');
|
||||
spamfilter_el.addEventListener('click', (event) => {
|
||||
async function _spamfilter_el_change() {
|
||||
if (event.target.checked) {
|
||||
const granted = await messenger.permissions.request({ permissions: ["messagesMove"] });
|
||||
if (!granted) {
|
||||
event.target.checked = false;
|
||||
spamfilter_info_btn.disabled = 'disabled';
|
||||
}
|
||||
}
|
||||
}
|
||||
_spamfilter_el_change();
|
||||
spamfilter_info_btn.disabled = event.target.checked ? '' : 'disabled';
|
||||
});
|
||||
spamfilter_info_btn.disabled = spamfilter_el.checked ? '' : 'disabled';
|
||||
|
||||
let get_calendar_event_el = document.getElementById('get_calendar_event');
|
||||
let get_calendar_event_info_btn = document.getElementById('btnManageCalendarEventInfo');
|
||||
get_calendar_event_el.addEventListener('click', (event) => {
|
||||
|
|
@ -337,6 +354,19 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||
})
|
||||
});
|
||||
|
||||
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')});
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
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) => {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
<h2>Version 3.2.0 - ??/??/2025</h2>
|
||||
<ul>
|
||||
<li><i>[ChatGPT API][Ollama API][OpenAI Comp API][Gemini API]</i> Added an option to automatically tag incoming emails [<a href="https://github.com/micz/ThunderAI/issues/237">#237</a>].</li>
|
||||
<li><i>[ChatGPT API][Ollama API][OpenAI Comp API][Gemini API]</i> Added an configurable antispam filter for incoming emails [<a href="https://github.com/micz/ThunderAI/issues/231">#231</a>].</li>
|
||||
<li>Added the <i>{%thunderai_def_sign%}</i> placeholder to get the default signature as defined in the options [<a href="https://github.com/micz/ThunderAI/issues/248">#248</a>].</li>
|
||||
<li>Added the <i>{%thunderai_def_lang%}</i> placeholder to get the default language as defined in the options [<a href="https://github.com/micz/ThunderAI/issues/248">#248</a>].</li>
|
||||
<li>Various minor improvements.</li>
|
||||
|
|
|
|||
154
pages/spamfilter/mzta-spamfilter.css
Normal file
154
pages/spamfilter/mzta-spamfilter.css
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
#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;
|
||||
}
|
||||
|
||||
table#miczPrefs {
|
||||
padding: 10px;
|
||||
border-spacing: 0px;
|
||||
width: 90%;
|
||||
margin: 0px auto 40px auto;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.spamfilter_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;
|
||||
}
|
||||
|
||||
#spamfilter_threshold_too_low{
|
||||
display: none;
|
||||
color:red;
|
||||
font-weight: bold;
|
||||
font-size: 1.1em;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
table#report_data {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 20px;
|
||||
}
|
||||
table#report_data th, table#report_data td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
table#report_data th {
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
table#report_data th,
|
||||
table#report_data td {
|
||||
border-color: #444;
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
table#report_data th {
|
||||
background-color: #333;
|
||||
}
|
||||
|
||||
table#report_data td {
|
||||
background-color: #222;
|
||||
}
|
||||
}
|
||||
59
pages/spamfilter/mzta-spamfilter.html
Normal file
59
pages/spamfilter/mzta-spamfilter.html
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>ThunderAI - __MSG_SpamFilter_PageTitle__</title>
|
||||
<link rel="stylesheet" href="mzta-spamfilter.css">
|
||||
<link rel="icon" href="../../images/icon-16px.png">
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<h1 class="page_title">__MSG_SpamFilter_PageTitle__</h1>
|
||||
<p>__MSG_SpamFilter_info_default__</p>
|
||||
</div>
|
||||
<div class="spamfilter_table_title section_title">__MSG_SpamFilter_prompt_prefs_title__</div>
|
||||
<table id="miczPrefs">
|
||||
<tr class="spamfilter_tr">
|
||||
<td><span>__MSG_prefs_OptionText_spamfilter_threshold__</span></td>
|
||||
<td>
|
||||
<label>
|
||||
<input type="number" id="spamfilter_threshold" name="spamfilter_threshold" min="0" max="100" class="option-input" /><span id="spamfilter_threshold_too_low">__MSG_spamfilter_threshold_too_low__</span>
|
||||
<br>__MSG_prefs_OptionText_spamfilter_threshold_Info__
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div id="spamfilter_container">
|
||||
<span class="section_title">__MSG_GetCalendarEvent_prompt_text_title__</span><span id="spamfilter_event_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="spamfilter_prompt_text" rows="15"></textarea>
|
||||
<ul class="autocomplete-list hidden"></ul>
|
||||
</div>
|
||||
<div id="spamfilter_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>
|
||||
<h2>__MSG_SpamReport_Title__</h2>
|
||||
<table id="report_data">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Message_ID</th>
|
||||
<th>__MSG_Date__</th>
|
||||
<th>__MSG_From__</th>
|
||||
<th>__MSG_Subject__</th>
|
||||
<th>__MSG_Spam_Value__</th>
|
||||
<th>__MSG_Moved_to_Spam__</th>
|
||||
<th>__MSG_Explanation__</th>
|
||||
<th>__MSG_Report_Date__</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="report_data_body">
|
||||
<!-- Rows will be inserted dynamically -->
|
||||
</tbody>
|
||||
</table>
|
||||
<script src="mzta-spamfilter.js" type="module"></script>
|
||||
<script src="../../js/mzta-i18n.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
207
pages/spamfilter/mzta-spamfilter.js
Normal file
207
pages/spamfilter/mzta-spamfilter.js
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
/*
|
||||
* 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";
|
||||
import { taSpamReport } from '../../js/mzta-spamreport.js';
|
||||
|
||||
let autocompleteSuggestions = [];
|
||||
let taLog = new taLogger("mzta-spamfilter-page",true);
|
||||
taSpamReport.logger = taLog;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
|
||||
i18n.updateDocument();
|
||||
await restoreOptions();
|
||||
|
||||
document.querySelectorAll(".option-input").forEach(element => {
|
||||
element.addEventListener("change", saveOptions);
|
||||
});
|
||||
|
||||
document.getElementById("spamfilter_threshold").addEventListener("input", check_spamfilter_threshold);
|
||||
check_spamfilter_threshold({target: document.getElementById("spamfilter_threshold")});
|
||||
|
||||
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
|
||||
|
||||
loadSpamReport()
|
||||
});
|
||||
|
||||
function check_spamfilter_threshold(event) {
|
||||
let spamfilter_threshold_too_low = document.getElementById("spamfilter_threshold_too_low");
|
||||
if(event.target.value < 50){
|
||||
spamfilter_threshold_too_low.style.display = "inline";
|
||||
if(event.target.value == 0){
|
||||
spamfilter_threshold_too_low.textContent = browser.i18n.getMessage('spamfilter_threshold_zero');
|
||||
spamfilter_threshold_too_low.style.fontSize = "1.5em";
|
||||
}else{
|
||||
spamfilter_threshold_too_low.textContent = browser.i18n.getMessage('spamfilter_threshold_too_low');
|
||||
spamfilter_threshold_too_low.style.fontSize = "1.1em";
|
||||
}
|
||||
}else{
|
||||
spamfilter_threshold_too_low.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
async function loadSpamReport(){
|
||||
let report_data = await taSpamReport.getAllReportData();
|
||||
console.log(">>>>>>>>>>>> loadSpamReport: " + JSON.stringify(report_data));
|
||||
//document.getElementById("report_data").textContent = JSON.stringify(report_data, null, 2);
|
||||
if(report_data == undefined){
|
||||
document.getElementById("report_data").innerText = browser.i18n.getMessage("spamfilter_no_reports");
|
||||
}else{
|
||||
populateTable(report_data);
|
||||
}
|
||||
}
|
||||
|
||||
// Function to populate the table
|
||||
function populateTable(data) {
|
||||
const tableBody = document.getElementById("report_data_body");
|
||||
tableBody.innerHTML = ""; // Clear table before inserting new data
|
||||
|
||||
Object.keys(data).forEach(email => {
|
||||
const report = data[email];
|
||||
|
||||
// Create a new row
|
||||
const row = document.createElement("tr");
|
||||
|
||||
row.innerHTML = `
|
||||
<td>${report.headerMessageId}</td>
|
||||
<td>${new Date(report.message_date).toLocaleString()}</td>
|
||||
<td>${report.from.join(", ")}</td>
|
||||
<td>${report.subject.join(", ")}</td>
|
||||
<td>${report.spamValue}</td>
|
||||
<td>${report.moved?browser.i18n.getMessage("spamfilter_moved"):browser.i18n.getMessage("spamfilter_not_moved")} (${report.SpamThreshold})</td>
|
||||
<td>${report.explanation}</td>
|
||||
<td>${new Date(report.report_date).toLocaleString()}</td>
|
||||
`;
|
||||
|
||||
// Append the row to the table
|
||||
tableBody.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 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);
|
||||
}
|
||||
Loading…
Reference in a new issue