Merge pull request #97 from wagesj45/codex/update-error-handling-in-background.js
Add persistent error indicator
This commit is contained in:
commit
83b890a419
2 changed files with 49 additions and 7 deletions
|
@ -22,7 +22,8 @@ message meets a specified criterion.
|
|||
- **Rule enable/disable** – temporarily turn a rule off without removing it.
|
||||
- **Account & folder filters** – limit rules to specific accounts or folders.
|
||||
- **Context menu** – apply AI rules from the message list or the message display action button.
|
||||
- **Status icons** – toolbar icons show when classification is in progress and briefly display success or error states.
|
||||
- **Status icons** – toolbar icons show when classification is in progress and briefly display success states. If a failure occurs the icon turns red until you dismiss the notification.
|
||||
- **Error notification** – failed classification displays a notification with a button to clear the error and reset the icon.
|
||||
- **View reasoning** – inspect why rules matched via the Details popup.
|
||||
- **Cache management** – clear cached results from the context menu or options page.
|
||||
- **Queue & timing stats** – monitor processing time on the Maintenance tab.
|
||||
|
@ -76,11 +77,12 @@ Sortana is implemented entirely with standard WebExtension scripts—no custom e
|
|||
deleting or archiving a message when it matches. Drag rules to
|
||||
reorder them, check *Only apply to unread messages* to skip read mail,
|
||||
set optional minimum or maximum message age limits, select the accounts or
|
||||
folders a rule should apply to, uncheck *Enabled* to temporarily disable a rule, and
|
||||
folders a rule should apply to, uncheck *Enabled* to temporarily disable a rule, and
|
||||
check *Stop after match* to halt further processing. Forward and reply actions
|
||||
open a compose window using the account that received the message.
|
||||
3. Save your settings. New mail will be evaluated automatically using the
|
||||
configured rules.
|
||||
4. If the toolbar icon shows a red X, click the notification's **Dismiss** button to clear the error.
|
||||
|
||||
### Example Filters
|
||||
|
||||
|
|
|
@ -30,6 +30,8 @@ let TurndownService = null;
|
|||
let userTheme = 'auto';
|
||||
let currentTheme = 'light';
|
||||
let detectSystemTheme;
|
||||
let errorPending = false;
|
||||
const ERROR_NOTIFICATION_ID = 'sortana-error';
|
||||
|
||||
function normalizeRules(rules) {
|
||||
return Array.isArray(rules) ? rules.map(r => {
|
||||
|
@ -68,7 +70,8 @@ const ICONS = {
|
|||
logo: () => 'resources/img/logo.png',
|
||||
circledots: () => iconPaths('circledots'),
|
||||
circle: () => iconPaths('circle'),
|
||||
average: () => iconPaths('average')
|
||||
average: () => iconPaths('average'),
|
||||
error: () => iconPaths('x')
|
||||
};
|
||||
|
||||
function setIcon(path) {
|
||||
|
@ -82,19 +85,31 @@ function setIcon(path) {
|
|||
|
||||
function updateActionIcon() {
|
||||
let path = ICONS.logo();
|
||||
if (processing || queuedCount > 0) {
|
||||
if (errorPending) {
|
||||
path = ICONS.error();
|
||||
} else if (processing || queuedCount > 0) {
|
||||
path = ICONS.circledots();
|
||||
}
|
||||
setIcon(path);
|
||||
}
|
||||
|
||||
function showTransientIcon(factory, delay = 1500) {
|
||||
if (errorPending) {
|
||||
return;
|
||||
}
|
||||
clearTimeout(iconTimer);
|
||||
const path = typeof factory === 'function' ? factory() : factory;
|
||||
setIcon(path);
|
||||
iconTimer = setTimeout(updateActionIcon, delay);
|
||||
}
|
||||
|
||||
async function clearError() {
|
||||
errorPending = false;
|
||||
await storage.local.set({ errorPending: false });
|
||||
await browser.notifications.clear(ERROR_NOTIFICATION_ID);
|
||||
updateActionIcon();
|
||||
}
|
||||
|
||||
function refreshMenuIcons() {
|
||||
browser.menus.update('apply-ai-rules-list', { icons: iconPaths('eye') });
|
||||
browser.menus.update('apply-ai-rules-display', { icons: iconPaths('eye') });
|
||||
|
@ -307,9 +322,17 @@ async function processMessage(id) {
|
|||
const elapsed = Date.now() - currentStart;
|
||||
currentStart = 0;
|
||||
updateTimingStats(elapsed);
|
||||
await storage.local.set({ classifyStats: timingStats });
|
||||
await storage.local.set({ classifyStats: timingStats, errorPending: true });
|
||||
errorPending = true;
|
||||
logger.aiLog("failed to apply AI rules", { level: 'error' }, e);
|
||||
showTransientIcon(ICONS.average);
|
||||
setIcon(ICONS.error());
|
||||
browser.notifications.create(ERROR_NOTIFICATION_ID, {
|
||||
type: 'basic',
|
||||
iconUrl: browser.runtime.getURL('resources/img/logo.png'),
|
||||
title: 'Sortana Error',
|
||||
message: 'Failed to apply AI rules',
|
||||
buttons: [{ title: 'Dismiss' }]
|
||||
});
|
||||
}
|
||||
}
|
||||
async function applyAiRules(idsInput) {
|
||||
|
@ -368,7 +391,7 @@ async function clearCacheForMessages(idsInput) {
|
|||
}
|
||||
|
||||
try {
|
||||
const store = await storage.local.get(["endpoint", "templateName", "customTemplate", "customSystemPrompt", "aiParams", "debugLogging", "htmlToMarkdown", "stripUrlParams", "altTextImages", "collapseWhitespace", "aiRules", "theme"]);
|
||||
const store = await storage.local.get(["endpoint", "templateName", "customTemplate", "customSystemPrompt", "aiParams", "debugLogging", "htmlToMarkdown", "stripUrlParams", "altTextImages", "collapseWhitespace", "aiRules", "theme", "errorPending"]);
|
||||
logger.setDebug(store.debugLogging);
|
||||
await AiClassifier.setConfig(store);
|
||||
userTheme = store.theme || 'auto';
|
||||
|
@ -378,6 +401,7 @@ async function clearCacheForMessages(idsInput) {
|
|||
stripUrlParams = store.stripUrlParams === true;
|
||||
altTextImages = store.altTextImages === true;
|
||||
collapseWhitespace = store.collapseWhitespace === true;
|
||||
errorPending = store.errorPending === true;
|
||||
const savedStats = await storage.local.get('classifyStats');
|
||||
if (savedStats.classifyStats && typeof savedStats.classifyStats === 'object') {
|
||||
Object.assign(timingStats, savedStats.classifyStats);
|
||||
|
@ -409,6 +433,10 @@ async function clearCacheForMessages(idsInput) {
|
|||
collapseWhitespace = changes.collapseWhitespace.newValue === true;
|
||||
logger.aiLog("collapseWhitespace updated from storage change", { debug: true }, collapseWhitespace);
|
||||
}
|
||||
if (changes.errorPending) {
|
||||
errorPending = changes.errorPending.newValue === true;
|
||||
updateActionIcon();
|
||||
}
|
||||
if (changes.theme) {
|
||||
userTheme = changes.theme.newValue || 'auto';
|
||||
currentTheme = userTheme === 'auto' ? await detectSystemTheme() : userTheme;
|
||||
|
@ -634,6 +662,18 @@ async function clearCacheForMessages(idsInput) {
|
|||
logger.aiLog("Unhandled promise rejection", { level: 'error' }, ev.reason);
|
||||
});
|
||||
|
||||
browser.notifications.onClicked.addListener(id => {
|
||||
if (id === ERROR_NOTIFICATION_ID) {
|
||||
clearError();
|
||||
}
|
||||
});
|
||||
|
||||
browser.notifications.onButtonClicked.addListener((id) => {
|
||||
if (id === ERROR_NOTIFICATION_ID) {
|
||||
clearError();
|
||||
}
|
||||
});
|
||||
|
||||
browser.runtime.onInstalled.addListener(async ({ reason }) => {
|
||||
if (reason === "install") {
|
||||
await browser.runtime.openOptionsPage();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue