Add message details popup

This commit is contained in:
Jordan Wages 2025-06-27 04:08:04 -05:00
commit 1d7f0d5344
7 changed files with 66 additions and 116 deletions

View file

@ -13,8 +13,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
logger.js = logger.js
manifest.json = manifest.json
README.md = README.md
reasoning.html = reasoning.html
reasoning.js = reasoning.js
EndProjectSection
ProjectSection(FolderGlobals) = preProject
Q_5_4Users_4Jordan_4Documents_4Gitea_4thunderbird-ai-filter_4src_4manifest_1json__JsonSchema =
@ -54,8 +52,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "prompt_templates", "prompt_
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "resources", "resources", "{68A87938-5C2B-49F5-8AAA-8A34FBBFD854}"
ProjectSection(SolutionItems) = preProject
resources\clearCacheButton.js = resources\clearCacheButton.js
resources\reasonButton.js = resources\reasonButton.js
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "img", "img", "{F266602F-1755-4A95-A11B-6C90C701C5BF}"

View file

@ -30,7 +30,7 @@ function setIcon(path) {
}
function updateActionIcon() {
let path = "resources/img/logo32.png";
let path = "resources/img/brain.png";
if (processing || queuedCount > 0) {
path = "resources/img/busy.png";
}
@ -223,40 +223,9 @@ async function clearCacheForMessages(idsInput) {
logger.aiLog("background.js loaded ready to classify", {debug: true});
if (browser.messageDisplayAction) {
browser.messageDisplayAction.setTitle({ title: "Classify" });
browser.messageDisplayAction.setTitle({ title: "Details" });
if (browser.messageDisplayAction.setLabel) {
browser.messageDisplayAction.setLabel({ label: "Classify" });
}
}
if (browser.scripting && browser.scripting.messageDisplay) {
try {
const scripts = [
{
id: "clear-cache-button",
js: ["resources/clearCacheButton.js"],
},
{
id: "reason-button",
js: ["resources/reasonButton.js"],
},
];
await browser.scripting.messageDisplay.registerScripts(scripts);
} catch (e) {
logger.aiLog("failed to register message display script", { level: 'warn' }, e);
}
} else if (browser.messageDisplayScripts) {
try {
const scripts = [
{ js: ["resources/clearCacheButton.js"] },
{ js: ["resources/reasonButton.js"] },
];
if (browser.messageDisplayScripts.registerScripts) {
await browser.messageDisplayScripts.registerScripts(scripts);
} else if (browser.messageDisplayScripts.register) {
await browser.messageDisplayScripts.register(scripts);
}
} catch (e) {
logger.aiLog("failed to register message display script", { level: 'warn' }, e);
browser.messageDisplayAction.setLabel({ label: "Details" });
}
}
@ -293,17 +262,7 @@ async function clearCacheForMessages(idsInput) {
icons: { "16": "resources/img/brain.png" }
});
if (browser.messageDisplayAction) {
browser.messageDisplayAction.onClicked.addListener(async (tab) => {
try {
const msgs = await browser.messageDisplay.getDisplayedMessages(tab.id);
const ids = msgs.map(m => m.id);
await applyAiRules(ids);
} catch (e) {
logger.aiLog("failed to apply AI rules from action", { level: 'error' }, e);
}
});
}
browser.menus.onClicked.addListener(async info => {
if (info.menuItemId === "apply-ai-rules-list" || info.menuItemId === "apply-ai-rules-display") {
@ -317,7 +276,7 @@ async function clearCacheForMessages(idsInput) {
} else if (info.menuItemId === "view-ai-reason-list" || info.menuItemId === "view-ai-reason-display") {
const id = info.messageId || info.selectedMessages?.messages?.[0]?.id;
if (id) {
const url = browser.runtime.getURL(`reasoning.html?mid=${id}`);
const url = browser.runtime.getURL(`details.html?mid=${id}`);
browser.tabs.create({ url });
}
}
@ -383,6 +342,45 @@ async function clearCacheForMessages(idsInput) {
logger.aiLog("failed to collect reasons", { level: 'error' }, e);
return { subject: '', reasons: [] };
}
} else if (msg?.type === "sortana:getDetails") {
try {
const id = msg.id;
const hdr = await messenger.messages.get(id);
const subject = hdr?.subject || "";
if (!aiRules.length) {
const { aiRules: stored } = await storage.local.get("aiRules");
aiRules = Array.isArray(stored) ? stored.map(r => {
if (r.actions) return r;
const actions = [];
if (r.tag) actions.push({ type: 'tag', tagKey: r.tag });
if (r.moveTo) actions.push({ type: 'move', folder: r.moveTo });
const rule = { criterion: r.criterion, actions };
if (r.stopProcessing) rule.stopProcessing = true;
return rule;
}) : [];
}
const results = [];
for (const rule of aiRules) {
const key = await sha256Hex(`${id}|${rule.criterion}`);
const matched = AiClassifier.getCachedResult(key);
const reason = AiClassifier.getReason(key);
if (matched !== null || reason) {
results.push({ criterion: rule.criterion, matched, reason });
}
}
return { subject, results };
} catch (e) {
logger.aiLog("failed to collect details", { level: 'error' }, e);
return { subject: '', results: [] };
}
} else if (msg?.type === "sortana:clearCacheForMessage") {
try {
await clearCacheForMessages([msg.id]);
return { ok: true };
} catch (e) {
logger.aiLog("failed to clear cache for message", { level: 'error' }, e);
return { ok: false };
}
} else {
logger.aiLog("Unknown message type, ignoring", {level: 'warn'}, msg?.type);
}

View file

@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<title>AI Reasoning</title>
<title>AI Details</title>
<link rel="stylesheet" href="options/bulma.css">
</head>
<body>
@ -10,8 +10,11 @@
<div class="container">
<h1 class="title" id="subject"></h1>
<div id="rules"></div>
<div class="buttons mt-4">
<button class="button is-danger" id="clear">Clear Cache</button>
</div>
</div>
</section>
<script src="reasoning.js"></script>
<script src="details.js"></script>
</body>
</html>

View file

@ -3,25 +3,33 @@ document.addEventListener('DOMContentLoaded', async () => {
const id = parseInt(params.get('mid'), 10);
if (!id) return;
try {
const { subject, reasons } = await browser.runtime.sendMessage({ type: 'sortana:getReasons', id });
const { subject, results } = await browser.runtime.sendMessage({ type: 'sortana:getDetails', id });
document.getElementById('subject').textContent = subject;
const container = document.getElementById('rules');
for (const r of reasons) {
for (const r of results) {
const article = document.createElement('article');
article.className = 'message mb-4';
const color = r.matched === true ? 'is-success' : 'is-danger';
article.className = `message ${color} mb-4`;
const header = document.createElement('div');
header.className = 'message-header';
header.innerHTML = `<p>${r.criterion}</p>`;
const body = document.createElement('div');
body.className = 'message-body';
const status = document.createElement('p');
status.textContent = r.matched ? 'Matched' : 'Did not match';
const pre = document.createElement('pre');
pre.textContent = r.reason;
pre.textContent = r.reason || '';
body.appendChild(status);
body.appendChild(pre);
article.appendChild(header);
article.appendChild(body);
container.appendChild(article);
}
document.getElementById('clear').addEventListener('click', async () => {
await browser.runtime.sendMessage({ type: 'sortana:clearCacheForMessage', id });
window.close();
});
} catch (e) {
console.error('failed to load reasons', e);
console.error('failed to load details', e);
}
});

View file

@ -22,9 +22,10 @@
"default_icon": "resources/img/logo32.png"
},
"message_display_action": {
"default_icon": "resources/img/logo32.png",
"default_title": "Classify",
"default_label": "Classify"
"default_icon": "resources/img/brain.png",
"default_title": "Details",
"default_label": "Details",
"default_popup": "details.html"
},
"background": { "scripts": [ "background.js" ] },
"options_ui": {

View file

@ -1,22 +0,0 @@
(function() {
function addButton() {
const toolbar = document.querySelector("#header-view-toolbar") ||
document.querySelector("#mail-toolbox toolbar");
if (!toolbar || document.getElementById('sortana-clear-cache-button')) return;
const button = document.createXULElement ?
document.createXULElement('toolbarbutton') :
document.createElement('button');
button.id = 'sortana-clear-cache-button';
button.setAttribute('label', 'Clear Cache');
button.className = 'toolbarbutton-1';
button.addEventListener('command', () => {
browser.runtime.sendMessage({ type: 'sortana:clearCacheForDisplayed' });
});
toolbar.appendChild(button);
}
if (document.readyState === 'complete' || document.readyState === 'interactive') {
addButton();
} else {
document.addEventListener('DOMContentLoaded', addButton, { once: true });
}
})();

View file

@ -1,34 +0,0 @@
(function() {
function addButton() {
const toolbar = document.querySelector("#header-view-toolbar") ||
document.querySelector("#mail-toolbox toolbar");
if (!toolbar || document.getElementById('sortana-reason-button')) return;
const button = document.createXULElement ?
document.createXULElement('toolbarbutton') :
document.createElement('button');
button.id = 'sortana-reason-button';
button.setAttribute('label', 'View Reasoning');
button.className = 'toolbarbutton-1';
const icon = browser.runtime.getURL('resources/img/brain.png');
if (button.setAttribute) {
button.setAttribute('image', icon);
} else {
button.style.backgroundImage = `url(${icon})`;
button.style.backgroundSize = 'contain';
}
button.addEventListener('command', async () => {
const tabs = await browser.tabs.query({ active: true, currentWindow: true });
const tabId = tabs[0]?.id;
const msgs = tabId ? await browser.messageDisplay.getDisplayedMessages(tabId) : [];
if (!msgs.length) return;
const url = browser.runtime.getURL(`reasoning.html?mid=${msgs[0].id}`);
browser.tabs.create({ url });
});
toolbar.appendChild(button);
}
if (document.readyState === 'complete' || document.readyState === 'interactive') {
addButton();
} else {
document.addEventListener('DOMContentLoaded', addButton, { once: true });
}
})();