diff --git a/_locales/en/messages.json b/_locales/en/messages.json index d4080c9c..3515a3b5 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1151,11 +1151,11 @@ "message": "If checked, the AI will only add tags to emails received in the inbox folder.", "description": "" }, - "prefs_OptionText_add_tags_use_specific_integration": { + "prefs_OptionText_use_specific_integration": { "message": "Use specific Model and API", "description": "" }, - "prefs_OptionText_add_tags_use_specific_integration_Info": { + "prefs_OptionText_use_specific_integration_Info": { "message": "If checked, the Model and API specified below will be used for adding tags to emails, regardless the one choosen in the ThunderAI options page.", "description": "" }, @@ -1211,6 +1211,14 @@ "message": "Spam Filter Options", "description": "" }, + "prefs_OptionText_spamfilter_use_specific_integration": { + "message": "Use specific Model and API", + "description": "" + }, + "prefs_OptionText_spamfilter_use_specific_integration_Info": { + "message": "If checked, the Model and API specified below will be used for the spam filter, regardless the one chosen in the ThunderAI options page.", + "description": "" + }, "prefs_OptionText_spamfilter_threshold": { "message": "Spam threshold", "description": "" @@ -1665,4 +1673,4 @@ "message": "To use this feature, you need an API integration rather than the ChatGPT Web Integration. You can define a specific API in the feature settings page by first checking the checkbox above and then clicking the button on the left.", "description": "" } -} \ No newline at end of file +} diff --git a/options/mzta-options-default.js b/options/mzta-options-default.js index cc28ecd6..de164daa 100644 --- a/options/mzta-options-default.js +++ b/options/mzta-options-default.js @@ -82,10 +82,10 @@ export const prefs_default = { spamfilter_context_menu: true, spamfilter_enabled_accounts: [], spamfilter_use_specific_integration: false, - spamfilter_connection_type: '', + spamfilter_connection_type: 'chatgpt_api', spamfilter_chatgpt_model: '', spamfilter_ollama_model: '', spamfilter_openai_comp_model: '', spamfilter_google_gemini_model: '', spamfilter_anthropic_model: '', -} \ No newline at end of file +} diff --git a/pages/spamfilter/mzta-spamfilter.html b/pages/spamfilter/mzta-spamfilter.html index 9c7b4fd3..0a3d8a21 100644 --- a/pages/spamfilter/mzta-spamfilter.html +++ b/pages/spamfilter/mzta-spamfilter.html @@ -4,6 +4,7 @@ ThunderAI - __MSG_SpamFilter_PageTitle__ + @@ -13,6 +14,18 @@
__MSG_SpamFilter_prompt_prefs_title__
+ + + + + +
__MSG_prefs_OptionText_use_specific_integration__ + + +
__MSG_prefs_OptionText_spamfilter_threshold__ @@ -74,4 +87,4 @@ - \ No newline at end of file + diff --git a/pages/spamfilter/mzta-spamfilter.js b/pages/spamfilter/mzta-spamfilter.js index 164465ab..a783d3ca 100644 --- a/pages/spamfilter/mzta-spamfilter.js +++ b/pages/spamfilter/mzta-spamfilter.js @@ -18,18 +18,39 @@ 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 { getSpecialPrompts, setSpecialPrompts, loadPrompt, savePrompt, clearPromptAPI } 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'; import { getAccountsList, isAPIKeyValue } from "../../js/mzta-utils.js"; +import { + injectConnectionUI, + updateWarnings, + changeConnTypeRowColor +} from "../_lib/connection-ui.js"; let autocompleteSuggestions = []; let taLog = new taLogger("mzta-spamfilter-page",true); taSpamReport.logger = taLog; +let conntype_select_id = 'spamfilter_connection_type'; +let model_prefix = 'spamfilter_'; + document.addEventListener('DOMContentLoaded', async () => { + try { + await injectConnectionUI({ + afterTrId: 'connection_ui_anchor', + tr_class: 'specific_integration_sub', + selectId: conntype_select_id, + modelId_prefix: model_prefix, + no_chatgpt_web: true, + taLog: taLog + }); + } catch (e) { + console.error('Failed to inject connection UI (spamfilter)', e); + } + i18n.updateDocument(); await restoreOptions(); @@ -40,6 +61,48 @@ document.addEventListener('DOMContentLoaded', async () => { document.getElementById("spamfilter_threshold").addEventListener("input", check_spamfilter_threshold); check_spamfilter_threshold({target: document.getElementById("spamfilter_threshold")}); + // Bind prompt API updates to connection type and model selects + document.querySelectorAll(".option-input-model").forEach(element => { + element.addEventListener("change", updatePromptAPIInfo); + }); + const conntype_el = document.getElementById(conntype_select_id); + if (conntype_el) { + conntype_el.addEventListener('change', updatePromptAPIInfo); + const conntype_row = document.getElementById(conntype_select_id + '_tr'); + if (conntype_row) changeConnTypeRowColor(conntype_row, conntype_el); + } + + // Specific integration toggle behavior + const spamfilter_use_specific_integration_el = document.getElementById('spamfilter_use_specific_integration'); + let prefs_spamfilter_init = await browser.storage.sync.get({ spamfilter_enabled_accounts: [], connection_type: 'chatgpt_web' }); + if(prefs_spamfilter_init.connection_type == 'chatgpt_web'){ + spamfilter_use_specific_integration_el.checked = true; + spamfilter_use_specific_integration_el.dispatchEvent(new Event('change')); + spamfilter_use_specific_integration_el.disabled = true; + } + const conntype_end_el = document.getElementById('connection_ui_end'); + const conntype_row = document.getElementById(conntype_select_id + '_tr'); + spamfilter_use_specific_integration_el.addEventListener('change', async (event) => { + document.querySelectorAll('.specific_integration_sub').forEach(tr => { + tr.style.display = event.target.checked && tr.classList.contains('conntype_' + conntype_el.value) ? 'table-row' : 'none'; + }); + if (conntype_row) conntype_row.style.display = event.target.checked ? 'table-row' : 'none'; + if (conntype_end_el) conntype_end_el.style.display = event.target.checked ? 'table-row' : 'none'; + if(!event.target.checked){ + await clearPromptAPI('prompt_spamfilter'); + }else{ + updatePromptAPIInfo(); + } + if (conntype_row) changeConnTypeRowColor(conntype_row, conntype_el); + }); + + // Initialize visibility per current toggle value + document.querySelectorAll('.specific_integration_sub').forEach(tr => { + tr.style.display = spamfilter_use_specific_integration_el.checked && tr.classList.contains('conntype_' + conntype_el.value) ? 'table-row' : 'none'; + }); + if (conntype_row) conntype_row.style.display = spamfilter_use_specific_integration_el.checked ? 'table-row' : 'none'; + if (conntype_end_el) conntype_end_el.style.display = spamfilter_use_specific_integration_el.checked ? 'table-row' : 'none'; + 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'); @@ -136,6 +199,9 @@ document.addEventListener('DOMContentLoaded', async () => { }); loadSpamReport(); + updateWarnings(model_prefix); + // Sync prompt API/model once on load + updatePromptAPIInfo(); }); function check_spamfilter_threshold(event) { @@ -165,6 +231,21 @@ async function loadSpamReport(){ } } +async function updatePromptAPIInfo(){ + const conntypeEl = document.getElementById(conntype_select_id); + if (!conntypeEl || !conntypeEl.value) return; + const conntype = conntypeEl.value; + const model_value = conntype.substring(0, conntype.length - 4) + '_model'; + const modelEl = document.getElementById(model_prefix + model_value); + if (!modelEl) return; + const model = modelEl.value; + let spamfilter_prompt = await loadPrompt('prompt_spamfilter'); + if (!spamfilter_prompt) return; + spamfilter_prompt.api = conntype; + spamfilter_prompt.model = model; + await savePrompt(spamfilter_prompt); +} + // Function to populate the table function populateTable(data) { const tableBody = document.getElementById("report_data_body"); @@ -228,13 +309,16 @@ function saveOptions(e) { case 'number': options[element.id] = element.valueAsNumber; break; - case 'text': - case 'password': + case 'text': + case 'password': options[element.id] = element.value.trim(); break; case 'select-one': options[element.id] = element.value; break; + case 'textarea': + options[element.id] = element.value; + break; default: console.error("[ThunderAI] Unhandled input type:", element.type); } @@ -257,6 +341,7 @@ async function restoreOptions() { 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; @@ -267,7 +352,15 @@ async function restoreOptions() { 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.id == 'spamfilter_connection_type') default_select_value = 'chatgpt_api'; + const restoreValue = result[element.id] || default_select_value; + // Ensure option exists before restoring + let optionExists = Array.from(element.options).some(opt => opt.value === restoreValue); + if (!optionExists && restoreValue !== '') { + let newOption = new Option(restoreValue, restoreValue); + element.add(newOption); + } + element.value = restoreValue; if (element.value === '') { element.selectedIndex = -1; } @@ -280,4 +373,4 @@ async function restoreOptions() { let getting = await browser.storage.sync.get(prefs_default); setCurrentChoice(getting); -} \ No newline at end of file +}