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.
|
- **Rule enable/disable** – temporarily turn a rule off without removing it.
|
||||||
- **Account & folder filters** – limit rules to specific accounts or folders.
|
- **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.
|
- **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.
|
- **View reasoning** – inspect why rules matched via the Details popup.
|
||||||
- **Cache management** – clear cached results from the context menu or options page.
|
- **Cache management** – clear cached results from the context menu or options page.
|
||||||
- **Queue & timing stats** – monitor processing time on the Maintenance tab.
|
- **Queue & timing stats** – monitor processing time on the Maintenance tab.
|
||||||
|
@ -81,6 +82,7 @@ Sortana is implemented entirely with standard WebExtension scripts—no custom e
|
||||||
open a compose window using the account that received the message.
|
open a compose window using the account that received the message.
|
||||||
3. Save your settings. New mail will be evaluated automatically using the
|
3. Save your settings. New mail will be evaluated automatically using the
|
||||||
configured rules.
|
configured rules.
|
||||||
|
4. If the toolbar icon shows a red X, click the notification's **Dismiss** button to clear the error.
|
||||||
|
|
||||||
### Example Filters
|
### Example Filters
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,8 @@ let TurndownService = null;
|
||||||
let userTheme = 'auto';
|
let userTheme = 'auto';
|
||||||
let currentTheme = 'light';
|
let currentTheme = 'light';
|
||||||
let detectSystemTheme;
|
let detectSystemTheme;
|
||||||
|
let errorPending = false;
|
||||||
|
const ERROR_NOTIFICATION_ID = 'sortana-error';
|
||||||
|
|
||||||
function normalizeRules(rules) {
|
function normalizeRules(rules) {
|
||||||
return Array.isArray(rules) ? rules.map(r => {
|
return Array.isArray(rules) ? rules.map(r => {
|
||||||
|
@ -68,7 +70,8 @@ const ICONS = {
|
||||||
logo: () => 'resources/img/logo.png',
|
logo: () => 'resources/img/logo.png',
|
||||||
circledots: () => iconPaths('circledots'),
|
circledots: () => iconPaths('circledots'),
|
||||||
circle: () => iconPaths('circle'),
|
circle: () => iconPaths('circle'),
|
||||||
average: () => iconPaths('average')
|
average: () => iconPaths('average'),
|
||||||
|
error: () => iconPaths('x')
|
||||||
};
|
};
|
||||||
|
|
||||||
function setIcon(path) {
|
function setIcon(path) {
|
||||||
|
@ -82,19 +85,31 @@ function setIcon(path) {
|
||||||
|
|
||||||
function updateActionIcon() {
|
function updateActionIcon() {
|
||||||
let path = ICONS.logo();
|
let path = ICONS.logo();
|
||||||
if (processing || queuedCount > 0) {
|
if (errorPending) {
|
||||||
|
path = ICONS.error();
|
||||||
|
} else if (processing || queuedCount > 0) {
|
||||||
path = ICONS.circledots();
|
path = ICONS.circledots();
|
||||||
}
|
}
|
||||||
setIcon(path);
|
setIcon(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
function showTransientIcon(factory, delay = 1500) {
|
function showTransientIcon(factory, delay = 1500) {
|
||||||
|
if (errorPending) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
clearTimeout(iconTimer);
|
clearTimeout(iconTimer);
|
||||||
const path = typeof factory === 'function' ? factory() : factory;
|
const path = typeof factory === 'function' ? factory() : factory;
|
||||||
setIcon(path);
|
setIcon(path);
|
||||||
iconTimer = setTimeout(updateActionIcon, delay);
|
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() {
|
function refreshMenuIcons() {
|
||||||
browser.menus.update('apply-ai-rules-list', { icons: iconPaths('eye') });
|
browser.menus.update('apply-ai-rules-list', { icons: iconPaths('eye') });
|
||||||
browser.menus.update('apply-ai-rules-display', { 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;
|
const elapsed = Date.now() - currentStart;
|
||||||
currentStart = 0;
|
currentStart = 0;
|
||||||
updateTimingStats(elapsed);
|
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);
|
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) {
|
async function applyAiRules(idsInput) {
|
||||||
|
@ -368,7 +391,7 @@ async function clearCacheForMessages(idsInput) {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
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);
|
logger.setDebug(store.debugLogging);
|
||||||
await AiClassifier.setConfig(store);
|
await AiClassifier.setConfig(store);
|
||||||
userTheme = store.theme || 'auto';
|
userTheme = store.theme || 'auto';
|
||||||
|
@ -378,6 +401,7 @@ async function clearCacheForMessages(idsInput) {
|
||||||
stripUrlParams = store.stripUrlParams === true;
|
stripUrlParams = store.stripUrlParams === true;
|
||||||
altTextImages = store.altTextImages === true;
|
altTextImages = store.altTextImages === true;
|
||||||
collapseWhitespace = store.collapseWhitespace === true;
|
collapseWhitespace = store.collapseWhitespace === true;
|
||||||
|
errorPending = store.errorPending === true;
|
||||||
const savedStats = await storage.local.get('classifyStats');
|
const savedStats = await storage.local.get('classifyStats');
|
||||||
if (savedStats.classifyStats && typeof savedStats.classifyStats === 'object') {
|
if (savedStats.classifyStats && typeof savedStats.classifyStats === 'object') {
|
||||||
Object.assign(timingStats, savedStats.classifyStats);
|
Object.assign(timingStats, savedStats.classifyStats);
|
||||||
|
@ -409,6 +433,10 @@ async function clearCacheForMessages(idsInput) {
|
||||||
collapseWhitespace = changes.collapseWhitespace.newValue === true;
|
collapseWhitespace = changes.collapseWhitespace.newValue === true;
|
||||||
logger.aiLog("collapseWhitespace updated from storage change", { debug: true }, collapseWhitespace);
|
logger.aiLog("collapseWhitespace updated from storage change", { debug: true }, collapseWhitespace);
|
||||||
}
|
}
|
||||||
|
if (changes.errorPending) {
|
||||||
|
errorPending = changes.errorPending.newValue === true;
|
||||||
|
updateActionIcon();
|
||||||
|
}
|
||||||
if (changes.theme) {
|
if (changes.theme) {
|
||||||
userTheme = changes.theme.newValue || 'auto';
|
userTheme = changes.theme.newValue || 'auto';
|
||||||
currentTheme = userTheme === 'auto' ? await detectSystemTheme() : userTheme;
|
currentTheme = userTheme === 'auto' ? await detectSystemTheme() : userTheme;
|
||||||
|
@ -634,6 +662,18 @@ async function clearCacheForMessages(idsInput) {
|
||||||
logger.aiLog("Unhandled promise rejection", { level: 'error' }, ev.reason);
|
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 }) => {
|
browser.runtime.onInstalled.addListener(async ({ reason }) => {
|
||||||
if (reason === "install") {
|
if (reason === "install") {
|
||||||
await browser.runtime.openOptionsPage();
|
await browser.runtime.openOptionsPage();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue