Merge pull request #33 from wagesj45/codex/suggest-ai-classification-status-spots

Add classification status indicators
This commit is contained in:
Jordan Wages 2025-06-25 14:54:18 -05:00 committed by GitHub
commit 2353f6db4d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 55 additions and 18 deletions

View file

@ -21,6 +21,7 @@ message meets a specified criterion.
- **Debug logging** optional colorized logs help troubleshoot interactions with the AI service.
- **Automatic rules** create rules that tag or move new messages based on AI classification.
- **Context menu** apply AI rules to selected messages from the message list or display.
- **Status icons** toolbar icons indicate when messages are queued or being classified.
- **Packaging script** `build-xpi.ps1` builds an XPI ready for installation.
## Architecture Overview

View file

@ -13,6 +13,24 @@
let logger;
let AiClassifier;
let aiRules = [];
let queue = Promise.resolve();
let queuedCount = 0;
let processing = false;
function updateActionIcon() {
let path = "resources/img/logo32.png";
if (processing) {
path = "resources/img/status-classifying.png";
} else if (queuedCount > 0) {
path = "resources/img/status-queued.png";
}
if (browser.browserAction) {
browser.browserAction.setIcon({ path });
}
if (browser.messageDisplayAction) {
browser.messageDisplayAction.setIcon({ path });
}
}
async function sha256Hex(str) {
const buf = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(str));
@ -21,7 +39,7 @@ async function sha256Hex(str) {
async function applyAiRules(idsInput) {
const ids = Array.isArray(idsInput) ? idsInput : [idsInput];
if (!ids.length) return;
if (!ids.length) return queue;
if (!aiRules.length) {
const { aiRules: stored } = await browser.storage.local.get("aiRules");
@ -36,28 +54,40 @@ async function applyAiRules(idsInput) {
for (const msg of ids) {
const id = msg?.id ?? msg;
try {
const full = await messenger.messages.getFull(id);
const text = full?.parts?.[0]?.body || "";
for (const rule of aiRules) {
const cacheKey = await sha256Hex(`${id}|${rule.criterion}`);
const matched = await AiClassifier.classifyText(text, rule.criterion, cacheKey);
if (matched) {
for (const act of (rule.actions || [])) {
if (act.type === 'tag' && act.tagKey) {
await messenger.messages.update(id, { tags: [act.tagKey] });
} else if (act.type === 'move' && act.folder) {
await messenger.messages.move([id], act.folder);
} else if (act.type === 'junk') {
await messenger.messages.update(id, { junk: !!act.junk });
queuedCount++;
updateActionIcon();
queue = queue.then(async () => {
processing = true;
queuedCount--;
updateActionIcon();
try {
const full = await messenger.messages.getFull(id);
const text = full?.parts?.[0]?.body || "";
for (const rule of aiRules) {
const cacheKey = await sha256Hex(`${id}|${rule.criterion}`);
const matched = await AiClassifier.classifyText(text, rule.criterion, cacheKey);
if (matched) {
for (const act of (rule.actions || [])) {
if (act.type === 'tag' && act.tagKey) {
await messenger.messages.update(id, { tags: [act.tagKey] });
} else if (act.type === 'move' && act.folder) {
await messenger.messages.move([id], act.folder);
} else if (act.type === 'junk') {
await messenger.messages.update(id, { junk: !!act.junk });
}
}
}
}
} catch (e) {
logger.aiLog("failed to apply AI rules", { level: 'error' }, e);
} finally {
processing = false;
updateActionIcon();
}
} catch (e) {
logger.aiLog("failed to apply AI rules", { level: 'error' }, e);
}
});
}
return queue;
}
(async () => {

View file

@ -18,6 +18,12 @@
"96": "resources/img/logo96.png",
"128": "resources/img/logo128.png"
},
"browser_action": {
"default_icon": "resources/img/logo32.png"
},
"message_display_action": {
"default_icon": "resources/img/logo32.png"
},
"background": { "scripts": [ "background.js" ] },
"options_ui": {
"page": "options/options.html",