Add custom logging framework and debug option

This commit is contained in:
Jordan Wages 2025-06-21 02:18:51 -05:00
commit 656d0322e6
8 changed files with 147 additions and 72 deletions

View file

@ -13,5 +13,6 @@
"template.qwen": { "message": "Qwen" }, "template.qwen": { "message": "Qwen" },
"template.mistral": { "message": "Mistral" }, "template.mistral": { "message": "Mistral" },
"template.custom": { "message": "Custom" }, "template.custom": { "message": "Custom" },
"options.save": { "message": "Save" } "options.save": { "message": "Save" },
"options.debugLogging": { "message": "Enable debug logging" }
} }

View file

@ -10,56 +10,59 @@
"use strict"; "use strict";
let logger;
// Startup // Startup
console.log("[ai-filter] background.js loaded ready to classify");
(async () => { (async () => {
logger = await import(browser.runtime.getURL("logger.js"));
logger.aiLog("background.js loaded ready to classify", {debug: true});
try { try {
const store = await browser.storage.local.get(["endpoint", "templateName", "customTemplate", "customSystemPrompt", "aiParams"]); const store = await browser.storage.local.get(["endpoint", "templateName", "customTemplate", "customSystemPrompt", "aiParams", "debugLogging"]);
logger.setDebug(store.debugLogging);
await browser.aiFilter.initConfig(store); await browser.aiFilter.initConfig(store);
console.log("[ai-filter] configuration loaded", store); logger.aiLog("configuration loaded", {debug: true}, store);
try { try {
await browser.DomContentScript.registerWindow( await browser.DomContentScript.registerWindow(
"chrome://messenger/content/FilterEditor.xhtml", "chrome://messenger/content/FilterEditor.xhtml",
"resource://aifilter/content/filterEditor.js" "resource://aifilter/content/filterEditor.js"
); );
console.log("[ai-filter] registered FilterEditor content script"); logger.aiLog("registered FilterEditor content script", {debug: true});
} catch (e) { } catch (e) {
console.error("[ai-filter] failed to register content script", e); logger.aiLog("failed to register content script", {level: 'error'}, e);
} }
} catch (err) { } catch (err) {
console.error("[ai-filter] failed to load config:", err); logger.aiLog("failed to load config", {level: 'error'}, err);
} }
})(); })();
// Listen for messages from UI/devtools // Listen for messages from UI/devtools
browser.runtime.onMessage.addListener((msg) => { browser.runtime.onMessage.addListener((msg) => {
console.log("[ai-filter] onMessage received:", msg); logger.aiLog("onMessage received", {debug: true}, msg);
if (msg?.type === "aiFilter:test") { if (msg?.type === "aiFilter:test") {
const { text = "", criterion = "" } = msg; const { text = "", criterion = "" } = msg;
console.log("[ai-filter] aiFilter:test text:", text); logger.aiLog("aiFilter:test text", {debug: true}, text);
console.log("[ai-filter] aiFilter:test criterion:", criterion); logger.aiLog("aiFilter:test criterion", {debug: true}, criterion);
try { try {
console.log("[ai-filter] Calling browser.aiFilter.classify()"); logger.aiLog("Calling browser.aiFilter.classify()", {debug: true});
const result = browser.aiFilter.classify(text, criterion); const result = browser.aiFilter.classify(text, criterion);
console.log("[ai-filter] classify() returned:", result); logger.aiLog("classify() returned", {debug: true}, result);
return { match: result }; return { match: result };
} }
catch (err) { catch (err) {
console.error("[ai-filter] Error in classify():", err); logger.aiLog("Error in classify()", {level: 'error'}, err);
// rethrow so the caller sees the failure // rethrow so the caller sees the failure
throw err; throw err;
} }
} }
else { else {
console.warn("[ai-filter] Unknown message type, ignoring:", msg?.type); logger.aiLog("Unknown message type, ignoring", {level: 'warn'}, msg?.type);
} }
}); });
// Catch any unhandled rejections // Catch any unhandled rejections
window.addEventListener("unhandledrejection", ev => { window.addEventListener("unhandledrejection", ev => {
console.error("[ai-filter] Unhandled promise rejection:", ev.reason); logger.aiLog("Unhandled promise rejection", {level: 'error'}, ev.reason);
}); });
browser.runtime.onInstalled.addListener(async ({ reason }) => { browser.runtime.onInstalled.addListener(async ({ reason }) => {

View file

@ -1,20 +1,21 @@
var { ExtensionCommon } = ChromeUtils.importESModule("resource://gre/modules/ExtensionCommon.sys.mjs"); var { ExtensionCommon } = ChromeUtils.importESModule("resource://gre/modules/ExtensionCommon.sys.mjs");
var { Services } = globalThis || ChromeUtils.importESModule("resource://gre/modules/Services.sys.mjs"); var { Services } = globalThis || ChromeUtils.importESModule("resource://gre/modules/Services.sys.mjs");
var { MailServices } = ChromeUtils.importESModule("resource:///modules/MailServices.sys.mjs"); var { MailServices } = ChromeUtils.importESModule("resource:///modules/MailServices.sys.mjs");
var { aiLog, setDebug } = ChromeUtils.import("resource://aifilter/modules/logger.jsm");
console.log("[ai-filter][api] Experiment API module loaded"); aiLog("[api] Experiment API module loaded", {debug: true});
var resProto = Cc["@mozilla.org/network/protocol;1?name=resource"] var resProto = Cc["@mozilla.org/network/protocol;1?name=resource"]
.getService(Ci.nsISubstitutingProtocolHandler); .getService(Ci.nsISubstitutingProtocolHandler);
function registerResourceUrl(extension, namespace) { function registerResourceUrl(extension, namespace) {
console.log(`[ai-filter][api] registerResourceUrl called for namespace="${namespace}"`); aiLog(`[api] registerResourceUrl called for namespace="${namespace}"`, {debug: true});
if (resProto.hasSubstitution(namespace)) { if (resProto.hasSubstitution(namespace)) {
console.log(`[ai-filter][api] namespace="${namespace}" already registered, skipping`); aiLog(`[api] namespace="${namespace}" already registered, skipping`, {debug: true});
return; return;
} }
let uri = Services.io.newURI(".", null, extension.rootURI); let uri = Services.io.newURI(".", null, extension.rootURI);
console.log(`[ai-filter][api] setting substitution for "${namespace}" → ${uri.spec}`); aiLog(`[api] setting substitution for "${namespace}" → ${uri.spec}`, {debug: true});
resProto.setSubstitutionWithFlags(namespace, uri, resProto.ALLOW_CONTENT_ACCESS); resProto.setSubstitutionWithFlags(namespace, uri, resProto.ALLOW_CONTENT_ACCESS);
} }
@ -23,63 +24,66 @@ var AIFilterMod;
var aiFilter = class extends ExtensionCommon.ExtensionAPI { var aiFilter = class extends ExtensionCommon.ExtensionAPI {
async onStartup() { async onStartup() {
console.log("[ai-filter][api] onStartup()"); aiLog("[api] onStartup()", {debug: true});
let { extension } = this; let { extension } = this;
registerResourceUrl(extension, "aifilter"); registerResourceUrl(extension, "aifilter");
try { try {
console.log("[ai-filter][api] importing ExpressionSearchFilter.jsm"); aiLog("[api] importing ExpressionSearchFilter.jsm", {debug: true});
AIFilterMod = ChromeUtils.import("resource://aifilter/modules/ExpressionSearchFilter.jsm"); AIFilterMod = ChromeUtils.import("resource://aifilter/modules/ExpressionSearchFilter.jsm");
console.log("[ai-filter][api] ExpressionSearchFilter.jsm import succeeded"); aiLog("[api] ExpressionSearchFilter.jsm import succeeded", {debug: true});
} }
catch (err) { catch (err) {
console.error("[ai-filter][api] failed to import ExpressionSearchFilter.jsm:", err); aiLog("[api] failed to import ExpressionSearchFilter.jsm", {level: 'error'}, err);
} }
} }
onShutdown(isAppShutdown) { onShutdown(isAppShutdown) {
console.log("[ai-filter][api] onShutdown(), isAppShutdown =", isAppShutdown); aiLog("[api] onShutdown()", {debug: true}, isAppShutdown);
if (!isAppShutdown && resProto.hasSubstitution("aifilter")) { if (!isAppShutdown && resProto.hasSubstitution("aifilter")) {
console.log("[ai-filter][api] removing substitution for namespace='aifilter'"); aiLog("[api] removing substitution for namespace='aifilter'", {debug: true});
resProto.setSubstitution("aifilter", null); resProto.setSubstitution("aifilter", null);
} }
} }
getAPI(context) { getAPI(context) {
console.log("[ai-filter][api] getAPI()"); aiLog("[api] getAPI()", {debug: true});
return { return {
aiFilter: { aiFilter: {
initConfig: async (config) => { initConfig: async (config) => {
try { try {
if (AIFilterMod?.AIFilter?.setConfig) { if (AIFilterMod?.AIFilter?.setConfig) {
AIFilterMod.AIFilter.setConfig(config); AIFilterMod.AIFilter.setConfig(config);
console.log("[ai-filter][api] configuration applied", config); if (typeof config.debugLogging === "boolean") {
setDebug(config.debugLogging);
}
aiLog("[api] configuration applied", {debug: true}, config);
} }
} catch (err) { } catch (err) {
console.error("[ai-filter][api] failed to apply config:", err); aiLog("[api] failed to apply config", {level: 'error'}, err);
} }
}, },
classify: (msg) => { classify: (msg) => {
console.log("[ai-filter][api] classify() called with msg:", msg); aiLog("[api] classify() called with msg", {debug: true}, msg);
try { try {
if (!gTerm) { if (!gTerm) {
console.log("[ai-filter][api] instantiating new ClassificationTerm"); aiLog("[api] instantiating new ClassificationTerm", {debug: true});
let mod = AIFilterMod || ChromeUtils.import("resource://aifilter/modules/ExpressionSearchFilter.jsm"); let mod = AIFilterMod || ChromeUtils.import("resource://aifilter/modules/ExpressionSearchFilter.jsm");
gTerm = new mod.ClassificationTerm(); gTerm = new mod.ClassificationTerm();
} }
console.log("[ai-filter][api] calling gTerm.match()"); aiLog("[api] calling gTerm.match()", {debug: true});
let matchResult = gTerm.match( let matchResult = gTerm.match(
msg.msgHdr, msg.msgHdr,
msg.value, msg.value,
Ci.nsMsgSearchOp.Contains Ci.nsMsgSearchOp.Contains
); );
console.log("[ai-filter][api] gTerm.match() returned:", matchResult); aiLog("[api] gTerm.match() returned", {debug: true}, matchResult);
return matchResult; return matchResult;
} }
catch (err) { catch (err) {
console.error("[ai-filter][api] error in classify():", err); aiLog("[api] error in classify()", {level: 'error'}, err);
throw err; throw err;
} }
} }

24
logger.js Normal file
View file

@ -0,0 +1,24 @@
let debugEnabled = false;
export function setDebug(value) {
debugEnabled = !!value;
}
function getCaller() {
try {
const stack = new Error().stack.split("\n");
if (stack.length >= 3) {
return stack[2].trim().replace(/^at\s+/, '');
}
} catch (e) {}
return '';
}
export function aiLog(message, opts = {}, ...args) {
const { level = 'log', debug = false } = opts;
if (debug && !debugEnabled) {
return;
}
const caller = getCaller();
const prefix = caller ? `[ai-filter][${caller}]` : '[ai-filter]';
console[level](`%c${prefix}`, 'color:#1c92d2;font-weight:bold', message, ...args);
}

View file

@ -5,6 +5,7 @@ var { Services } = globalThis || ChromeUtils.importESModule("resource://g
var { NetUtil } = ChromeUtils.importESModule("resource://gre/modules/NetUtil.sys.mjs"); var { NetUtil } = ChromeUtils.importESModule("resource://gre/modules/NetUtil.sys.mjs");
var { MimeParser } = ChromeUtils.importESModule("resource:///modules/mimeParser.sys.mjs"); var { MimeParser } = ChromeUtils.importESModule("resource:///modules/mimeParser.sys.mjs");
var { FileUtils } = ChromeUtils.importESModule("resource://gre/modules/FileUtils.sys.mjs"); var { FileUtils } = ChromeUtils.importESModule("resource://gre/modules/FileUtils.sys.mjs");
var { aiLog, setDebug } = ChromeUtils.import("resource://aifilter/modules/logger.jsm");
function sha256Hex(str) { function sha256Hex(str) {
const hasher = Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash); const hasher = Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
@ -41,36 +42,36 @@ class CustomerTermBase {
this._cacheFile.append("aifilter_cache.json"); this._cacheFile.append("aifilter_cache.json");
this._loadCache(); this._loadCache();
console.log(`[ai-filter][ExpressionSearchFilter] Initialized term base "${this.id}"`); aiLog(`[ExpressionSearchFilter] Initialized term base "${this.id}"`, {debug: true});
} }
_loadCache() { _loadCache() {
console.log(`[ai-filter][ExpressionSearchFilter] Loading cache from ${this._cacheFile.path}`); aiLog(`[ExpressionSearchFilter] Loading cache from ${this._cacheFile.path}` , {debug: true});
try { try {
if (this._cacheFile.exists()) { if (this._cacheFile.exists()) {
let stream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream); let stream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream);
stream.init(this._cacheFile, -1, 0, 0); stream.init(this._cacheFile, -1, 0, 0);
let data = NetUtil.readInputStreamToString(stream, stream.available()); let data = NetUtil.readInputStreamToString(stream, stream.available());
stream.close(); stream.close();
console.log(`[ai-filter][ExpressionSearchFilter] Cache file contents: ${data}`); aiLog(`[ExpressionSearchFilter] Cache file contents: ${data}`, {debug: true});
let obj = JSON.parse(data); let obj = JSON.parse(data);
for (let [k, v] of Object.entries(obj)) { for (let [k, v] of Object.entries(obj)) {
console.log(`[ai-filter][ExpressionSearchFilter] ⮡ Loaded entry '${k}' → ${v}`); aiLog(`[ExpressionSearchFilter] ⮡ Loaded entry '${k}' → ${v}`, {debug: true});
this.cache.set(k, v); this.cache.set(k, v);
} }
console.log(`[ai-filter][ExpressionSearchFilter] Loaded ${this.cache.size} cache entries`); aiLog(`[ExpressionSearchFilter] Loaded ${this.cache.size} cache entries`, {debug: true});
} else { } else {
console.log(`[ai-filter][ExpressionSearchFilter] Cache file does not exist`); aiLog(`[ExpressionSearchFilter] Cache file does not exist`, {debug: true});
} }
} catch (e) { } catch (e) {
console.error(`[ai-filter][ExpressionSearchFilter] Failed to load cache`, e); aiLog(`Failed to load cache`, {level: 'error'}, e);
} }
} }
_saveCache(updatedKey, updatedValue) { _saveCache(updatedKey, updatedValue) {
console.log(`[ai-filter][ExpressionSearchFilter] Saving cache to ${this._cacheFile.path}`); aiLog(`[ExpressionSearchFilter] Saving cache to ${this._cacheFile.path}`, {debug: true});
if (typeof updatedKey !== "undefined") { if (typeof updatedKey !== "undefined") {
console.log(`[ai-filter][ExpressionSearchFilter] ⮡ Persisting entry '${updatedKey}' → ${updatedValue}`); aiLog(`[ExpressionSearchFilter] ⮡ Persisting entry '${updatedKey}' → ${updatedValue}`, {debug: true});
} }
try { try {
let obj = Object.fromEntries(this.cache); let obj = Object.fromEntries(this.cache);
@ -83,39 +84,39 @@ class CustomerTermBase {
stream.write(data, data.length); stream.write(data, data.length);
stream.close(); stream.close();
} catch (e) { } catch (e) {
console.error(`[ai-filter][ExpressionSearchFilter] Failed to save cache`, e); aiLog(`Failed to save cache`, {level: 'error'}, e);
} }
} }
getEnabled() { getEnabled() {
console.log(`[ai-filter][ExpressionSearchFilter] getEnabled() called on "${this.id}"`); aiLog(`[ExpressionSearchFilter] getEnabled() called on "${this.id}"`, {debug: true});
return true; return true;
} }
getAvailable() { getAvailable() {
console.log(`[ai-filter][ExpressionSearchFilter] getAvailable() called on "${this.id}"`); aiLog(`[ExpressionSearchFilter] getAvailable() called on "${this.id}"`, {debug: true});
return true; return true;
} }
getAvailableOperators() { getAvailableOperators() {
console.log(`[ai-filter][ExpressionSearchFilter] getAvailableOperators() called on "${this.id}"`); aiLog(`[ExpressionSearchFilter] getAvailableOperators() called on "${this.id}"`, {debug: true});
return this.operators; return this.operators;
} }
getAvailableValues() { getAvailableValues() {
console.log(`[ai-filter][ExpressionSearchFilter] getAvailableValues() called on "${this.id}"`); aiLog(`[ExpressionSearchFilter] getAvailableValues() called on "${this.id}"`, {debug: true});
return null; return null;
} }
get attrib() { get attrib() {
console.log(`[ai-filter][ExpressionSearchFilter] attrib getter called for "${this.id}"`); aiLog(`[ExpressionSearchFilter] attrib getter called for "${this.id}"`, {debug: true});
//return Ci.nsMsgSearchAttrib.Custom; //return Ci.nsMsgSearchAttrib.Custom;
} }
} }
function getPlainText(msgHdr) { function getPlainText(msgHdr) {
console.log(`[ai-filter][ExpressionSearchFilter] Extracting plain text for message ID ${msgHdr.messageId}`); aiLog(`[ExpressionSearchFilter] Extracting plain text for message ID ${msgHdr.messageId}`, {debug: true});
let folder = msgHdr.folder; let folder = msgHdr.folder;
if (!folder.getMsgInputStream) return ""; if (!folder.getMsgInputStream) return "";
let reusable = {}; let reusable = {};
@ -161,7 +162,7 @@ function loadTemplate(name) {
return xhr.responseText; return xhr.responseText;
} }
} catch (e) { } catch (e) {
console.error(`[ai-filter][ExpressionSearchFilter] Failed to load template '${name}':`, e); aiLog(`Failed to load template '${name}':`, {level: 'error'}, e);
} }
return ""; return "";
} }
@ -186,9 +187,12 @@ function setConfig(config = {}) {
} }
} }
} }
if (typeof config.debugLogging === "boolean") {
setDebug(config.debugLogging);
}
gTemplateText = gTemplateName === "custom" ? gCustomTemplate : loadTemplate(gTemplateName); gTemplateText = gTemplateName === "custom" ? gCustomTemplate : loadTemplate(gTemplateName);
console.log(`[ai-filter][ExpressionSearchFilter] Endpoint set to ${gEndpoint}`); aiLog(`[ExpressionSearchFilter] Endpoint set to ${gEndpoint}`, {debug: true});
console.log(`[ai-filter][ExpressionSearchFilter] Template set to ${gTemplateName}`); aiLog(`[ExpressionSearchFilter] Template set to ${gTemplateName}`, {debug: true});
} }
function buildSystemPrompt() { function buildSystemPrompt() {
@ -196,7 +200,7 @@ function buildSystemPrompt() {
} }
function buildPrompt(body, criterion) { function buildPrompt(body, criterion) {
console.log(`[ai-filter][ExpressionSearchFilter] Building prompt with criterion: "${criterion}"`); aiLog(`[ExpressionSearchFilter] Building prompt with criterion: "${criterion}"`, {debug: true});
const data = { const data = {
system: buildSystemPrompt(), system: buildSystemPrompt(),
email: body, email: body,
@ -209,7 +213,7 @@ function buildPrompt(body, criterion) {
class ClassificationTerm extends CustomerTermBase { class ClassificationTerm extends CustomerTermBase {
constructor() { constructor() {
super("classification", [Ci.nsMsgSearchOp.Matches, Ci.nsMsgSearchOp.DoesntMatch]); super("classification", [Ci.nsMsgSearchOp.Matches, Ci.nsMsgSearchOp.DoesntMatch]);
console.log(`[ai-filter][ExpressionSearchFilter] ClassificationTerm constructed`); aiLog(`[ExpressionSearchFilter] ClassificationTerm constructed`, {debug: true});
} }
needsBody() { return true; } needsBody() { return true; }
@ -217,11 +221,11 @@ class ClassificationTerm extends CustomerTermBase {
match(msgHdr, value, op) { match(msgHdr, value, op) {
const opName = op === Ci.nsMsgSearchOp.Matches ? "matches" : const opName = op === Ci.nsMsgSearchOp.Matches ? "matches" :
op === Ci.nsMsgSearchOp.DoesntMatch ? "doesn't match" : `unknown (${op})`; op === Ci.nsMsgSearchOp.DoesntMatch ? "doesn't match" : `unknown (${op})`;
console.log(`[ai-filter][ExpressionSearchFilter] Matching message ${msgHdr.messageId} using op "${opName}" and value "${value}"`); aiLog(`[ExpressionSearchFilter] Matching message ${msgHdr.messageId} using op "${opName}" and value "${value}"`, {debug: true});
let key = [msgHdr.messageId, op, value].map(sha256Hex).join("|"); let key = [msgHdr.messageId, op, value].map(sha256Hex).join("|");
if (this.cache.has(key)) { if (this.cache.has(key)) {
console.log(`[ai-filter][ExpressionSearchFilter] Cache hit for key: ${key}`); aiLog(`[ExpressionSearchFilter] Cache hit for key: ${key}`, {debug: true});
return this.cache.get(key); return this.cache.get(key);
} }
@ -232,7 +236,7 @@ class ClassificationTerm extends CustomerTermBase {
let payload = JSON.stringify(payloadObj); let payload = JSON.stringify(payloadObj);
console.log(`[ai-filter][ExpressionSearchFilter] Sending classification request to ${gEndpoint}`); aiLog(`[ExpressionSearchFilter] Sending classification request to ${gEndpoint}`, {debug: true});
let matched = false; let matched = false;
try { try {
@ -242,44 +246,44 @@ class ClassificationTerm extends CustomerTermBase {
xhr.send(payload); xhr.send(payload);
if (xhr.status < 200 || xhr.status >= 300) { if (xhr.status < 200 || xhr.status >= 300) {
console.warn(`[ai-filter][ExpressionSearchFilter] HTTP status ${xhr.status}`); aiLog(`HTTP status ${xhr.status}`, {level: 'warn'});
} else { } else {
const result = JSON.parse(xhr.responseText); const result = JSON.parse(xhr.responseText);
console.log(`[ai-filter][ExpressionSearchFilter] Received response:`, result); aiLog(`[ExpressionSearchFilter] Received response:`, {debug: true}, result);
const rawText = result.choices?.[0]?.text || ""; const rawText = result.choices?.[0]?.text || "";
const thinkText = rawText.match(/<think>[\s\S]*?<\/think>/gi)?.join('') || ''; const thinkText = rawText.match(/<think>[\s\S]*?<\/think>/gi)?.join('') || '';
console.log('[ai-filter][ExpressionSearchFilter] ⮡ Reasoning: ', thinkText); aiLog('[ExpressionSearchFilter] ⮡ Reasoning:', {debug: true}, thinkText);
const cleanedText = rawText.replace(/<think>[\s\S]*?<\/think>/gi, "").trim(); const cleanedText = rawText.replace(/<think>[\s\S]*?<\/think>/gi, "").trim();
console.log('[ai-filter][ExpressionSearchFilter] ⮡ Cleaned Response Text: ', cleanedText); aiLog('[ExpressionSearchFilter] ⮡ Cleaned Response Text:', {debug: true}, cleanedText);
const obj = JSON.parse(cleanedText); const obj = JSON.parse(cleanedText);
matched = obj.matched === true || obj.match === true; matched = obj.matched === true || obj.match === true;
console.log(`[ai-filter][ExpressionSearchFilter] Caching entry '${key}' → ${matched}`); aiLog(`[ExpressionSearchFilter] Caching entry '${key}' → ${matched}`, {debug: true});
this.cache.set(key, matched); this.cache.set(key, matched);
this._saveCache(key, matched); this._saveCache(key, matched);
} }
} catch (e) { } catch (e) {
console.error(`[ai-filter][ExpressionSearchFilter] HTTP request failed:`, e); aiLog(`HTTP request failed`, {level: 'error'}, e);
} }
if (op === Ci.nsMsgSearchOp.DoesntMatch) { if (op === Ci.nsMsgSearchOp.DoesntMatch) {
matched = !matched; matched = !matched;
console.log(`[ai-filter][ExpressionSearchFilter] Operator is "doesn't match" → inverting to ${matched}`); aiLog(`[ExpressionSearchFilter] Operator is "doesn't match" → inverting to ${matched}`, {debug: true});
} }
console.log(`[ai-filter][ExpressionSearchFilter] Final match result: ${matched}`); aiLog(`[ExpressionSearchFilter] Final match result: ${matched}`, {debug: true});
return matched; return matched;
} }
} }
(function register() { (function register() {
console.log(`[ai-filter][ExpressionSearchFilter] Registering custom filter term...`); aiLog(`[ExpressionSearchFilter] Registering custom filter term...`, {debug: true});
let term = new ClassificationTerm(); let term = new ClassificationTerm();
if (!MailServices.filters.getCustomTerm(term.id)) { if (!MailServices.filters.getCustomTerm(term.id)) {
MailServices.filters.addCustomTerm(term); MailServices.filters.addCustomTerm(term);
console.log(`[ai-filter][ExpressionSearchFilter] Registered term: ${term.id}`); aiLog(`[ExpressionSearchFilter] Registered term: ${term.id}`, {debug: true});
} else { } else {
console.log(`[ai-filter][ExpressionSearchFilter] Term already registered: ${term.id}`); aiLog(`[ExpressionSearchFilter] Term already registered: ${term.id}`, {debug: true});
} }
})(); })();

26
modules/logger.jsm Normal file
View file

@ -0,0 +1,26 @@
var EXPORTED_SYMBOLS = ['aiLog', 'setDebug'];
let debugEnabled = false;
function setDebug(value) {
debugEnabled = !!value;
}
function getCaller() {
try {
let stack = new Error().stack.split('\n');
if (stack.length >= 3) {
return stack[2].trim().replace(/^@?\s*\(?/,'').replace(/^at\s+/, '');
}
} catch (e) {}
return '';
}
function aiLog(message, opts = {}, ...args) {
const { level = 'log', debug = false } = opts;
if (debug && !debugEnabled) {
return;
}
const caller = getCaller();
const prefix = caller ? `[ai-filter][${caller}]` : '[ai-filter]';
console[level](`%c${prefix}`, 'color:#1c92d2;font-weight:bold', message, ...args);
}

View file

@ -147,6 +147,11 @@
</div> </div>
<div id="advanced-options" style="display:none"> <div id="advanced-options" style="display:none">
<div class="form-group">
<label>
<input type="checkbox" id="debug-logging"> Enable debug logging
</label>
</div>
<div class="form-group"> <div class="form-group">
<label for="max_tokens">Max tokens:</label> <label for="max_tokens">Max tokens:</label>
<input type="number" id="max_tokens"> <input type="number" id="max_tokens">

View file

@ -1,11 +1,14 @@
document.addEventListener('DOMContentLoaded', async () => { document.addEventListener('DOMContentLoaded', async () => {
const logger = await import(browser.runtime.getURL('logger.js'));
const defaults = await browser.storage.local.get([ const defaults = await browser.storage.local.get([
'endpoint', 'endpoint',
'templateName', 'templateName',
'customTemplate', 'customTemplate',
'customSystemPrompt', 'customSystemPrompt',
'aiParams' 'aiParams',
'debugLogging'
]); ]);
logger.setDebug(defaults.debugLogging === true);
const DEFAULT_AI_PARAMS = { const DEFAULT_AI_PARAMS = {
max_tokens: 4096, max_tokens: 4096,
temperature: 0.6, temperature: 0.6,
@ -52,6 +55,9 @@ document.addEventListener('DOMContentLoaded', async () => {
advancedBox.style.display = advancedBox.style.display === 'none' ? 'block' : 'none'; advancedBox.style.display = advancedBox.style.display === 'none' ? 'block' : 'none';
}); });
const debugToggle = document.getElementById('debug-logging');
debugToggle.checked = defaults.debugLogging === true;
const aiParams = Object.assign({}, DEFAULT_AI_PARAMS, defaults.aiParams || {}); const aiParams = Object.assign({}, DEFAULT_AI_PARAMS, defaults.aiParams || {});
for (const [key, val] of Object.entries(aiParams)) { for (const [key, val] of Object.entries(aiParams)) {
const el = document.getElementById(key); const el = document.getElementById(key);
@ -78,11 +84,13 @@ document.addEventListener('DOMContentLoaded', async () => {
aiParamsSave[key] = isNaN(num) ? DEFAULT_AI_PARAMS[key] : num; aiParamsSave[key] = isNaN(num) ? DEFAULT_AI_PARAMS[key] : num;
} }
} }
await browser.storage.local.set({ endpoint, templateName, customTemplate: customTemplateText, customSystemPrompt, aiParams: aiParamsSave }); const debugLogging = debugToggle.checked;
await browser.storage.local.set({ endpoint, templateName, customTemplate: customTemplateText, customSystemPrompt, aiParams: aiParamsSave, debugLogging });
try { try {
await browser.aiFilter.initConfig({ endpoint, templateName, customTemplate: customTemplateText, customSystemPrompt, aiParams: aiParamsSave }); await browser.aiFilter.initConfig({ endpoint, templateName, customTemplate: customTemplateText, customSystemPrompt, aiParams: aiParamsSave, debugLogging });
logger.setDebug(debugLogging);
} catch (e) { } catch (e) {
console.error('[ai-filter][options] failed to apply config', e); logger.aiLog('[options] failed to apply config', {level: 'error'}, e);
} }
}); });
}); });