From 49641f90a7034c024011a35472412bc7dd127e8c Mon Sep 17 00:00:00 2001 From: Nigel Hagen Date: Tue, 13 Jan 2026 03:07:18 +0100 Subject: [PATCH 01/82] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index b60dfc3..74428c2 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # AutoSort+ - AI-Powered Email Organization for Thunderbird +icon-96 + + + [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Development Status](https://img.shields.io/badge/status-active-green)](https://github.com/nigelhagen/AutoSort-Plus) From c71d036ab1d3251a4a0b00fee2fd69a890b2f367 Mon Sep 17 00:00:00 2001 From: Nigel Hagen Date: Tue, 13 Jan 2026 03:09:14 +0100 Subject: [PATCH 02/82] fix: Update GitHub links to use Nigel1992 instead of nigelhagen --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 74428c2..30f1628 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -[![Development Status](https://img.shields.io/badge/status-active-green)](https://github.com/nigelhagen/AutoSort-Plus) +[![Development Status](https://img.shields.io/badge/status-active-green)](https://github.com/Nigel1992/AutoSort-Plus) **Automatically sort and label your emails with AI intelligence** @@ -47,14 +47,14 @@ AutoSort+ is a powerful Thunderbird addon that uses artificial intelligence to a ## 📦 Installation ### From Release File -1. Download `autosortplus.xpi` from [Latest Release](https://github.com/nigelhagen/AutoSort-Plus/releases) +1. Download `autosortplus.xpi` from [Latest Release](https://github.com/Nigel1992/AutoSort-Plus/releases) 2. In Thunderbird: **Tools → Add-ons and Extensions** 3. Click gear icon (⚙️) → **Install Add-on From File** 4. Select `autosortplus.xpi` ### Build from Source ```bash -git clone https://github.com/nigelhagen/AutoSort-Plus.git +git clone https://github.com/Nigel1992/AutoSort-Plus.git cd AutoSort-Plus zip -r autosortplus.xpi manifest.json background.js options.js options.html styles.css content.js icons/ ``` @@ -217,7 +217,7 @@ None currently known. Please report any issues on GitHub. ## 💬 Support - **Questions?** Check [Troubleshooting](#troubleshooting) above -- **Found a bug?** Open an issue on [GitHub](https://github.com/nigelhagen/AutoSort-Plus/issues) +- **Found a bug?** Open an issue on [GitHub](https://github.com/Nigel1992/AutoSort-Plus/issues) - **Feature request?** Create a discussion or issue ## 📄 License @@ -232,4 +232,4 @@ Pull requests welcome! For major changes, please open an issue first to discuss. **Made with ❤️ to help you organize email faster** -[GitHub](https://github.com/nigelhagen/AutoSort-Plus) • [Issues](https://github.com/nigelhagen/AutoSort-Plus/issues) • [Latest Release](https://github.com/nigelhagen/AutoSort-Plus/releases) +[GitHub](https://github.com/Nigel1992/AutoSort-Plus) • [Issues](https://github.com/Nigel1992/AutoSort-Plus/issues) • [Latest Release](https://github.com/Nigel1992/AutoSort-Plus/releases) From 2150e21a99f1fd0a9001e3ab197e477425b10b1c Mon Sep 17 00:00:00 2001 From: Nigel Hagen Date: Tue, 13 Jan 2026 03:10:24 +0100 Subject: [PATCH 03/82] Update LICENSE --- LICENSE | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index 84c8e56..ed6b201 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Nigel Hagen +Copyright (c) 2026 Nigel Hagen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. From bfcade655ca5b1745de6f8c34aef58421f17f370 Mon Sep 17 00:00:00 2001 From: Nigel Hagen Date: Tue, 13 Jan 2026 03:11:24 +0100 Subject: [PATCH 04/82] docs: Add icon attribution for Flaticon --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 30f1628..1926093 100644 --- a/README.md +++ b/README.md @@ -224,6 +224,10 @@ None currently known. Please report any issues on GitHub. MIT License - See [LICENSE](LICENSE) file for details +## 🎨 Credits + +Icon Attribution: Email filtering icons created by Fantasyou - Flaticon + ## 🙏 Contributing Pull requests welcome! For major changes, please open an issue first to discuss. From 72d7eb90957582e080e97c5d6db06ad9b9978e69 Mon Sep 17 00:00:00 2001 From: Nigel Hagen Date: Tue, 13 Jan 2026 03:13:30 +0100 Subject: [PATCH 05/82] docs: Add email folder category examples with spoiler --- README.md | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/README.md b/README.md index 1926093..ae494f0 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,47 @@ Click **"Get API Key"** in AutoSort+ settings to open signup page instantly. 2. Enter label names (one per field) 3. These become your email categories +
+📚 Example Folder Categories + +**Work & Professional:** +- Meetings +- Project Updates +- Invoices +- HR & Benefits + +**Financial:** +- Bills & Payments +- Bank Statements +- Receipts +- Tax Documents + +**Personal:** +- Family +- Friends +- Health +- Travel + +**Online Services:** +- Shopping Confirmations +- Social Media Notifications +- Subscriptions +- Password Resets + +**Promotions:** +- Newsletters +- Sales & Discounts +- Offers +- Marketing + +**Support:** +- Tickets & Help +- Documentation +- Updates +- Complaints + +
+ ### Step 5: Save Settings 1. Review your configuration 2. Click **"Save Settings"** From 59e901a81b9a746446e5db96cbb8c1ba34d00da9 Mon Sep 17 00:00:00 2001 From: Nigel Hagen Date: Tue, 13 Jan 2026 03:16:35 +0100 Subject: [PATCH 06/82] fix: Handle all selected emails for manual label application - Fix manual label menu to properly handle selected messages - Use await with browser.tabs.sendMessage to get response - Get full message objects from mailTabs.getSelectedMessages - Process all selected messages instead of just getting response --- background.js | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/background.js b/background.js index 0ae37db..e0cd449 100644 --- a/background.js +++ b/background.js @@ -620,10 +620,29 @@ browser.menus.onClicked.addListener(async (info, tab) => { const label = info.menuItemId.replace("label-", ""); console.log(`Manual label selected: ${label}`); await showNotification("AutoSort+", `Applying label: ${label}`); - browser.tabs.sendMessage(tab.id, { - action: "getSelectedMessages", - label: label - }); + try { + // Get selected messages from content script + const response = await browser.tabs.sendMessage(tab.id, { + action: "getSelectedMessages", + label: label + }); + console.log("Got selected messages from content script:", response); + + if (response && response.length > 0) { + // Get the current mail tab for processing + const mailTabs = await browser.mailTabs.query({ active: true, currentWindow: true }); + if (mailTabs && mailTabs.length > 0) { + // Get full message objects + const messages = await browser.mailTabs.getSelectedMessages(mailTabs[0].id); + if (messages && messages.messages && messages.messages.length > 0) { + await applyLabelsToMessages(messages.messages, label); + } + } + } + } catch (error) { + console.error("Error applying manual label:", error); + await showNotification("AutoSort+ Error", `Error applying label: ${error.message}`); + } } else if (info.menuItemId === "autosort-analyze") { console.log("AI analysis selected - starting process"); await showNotification("AutoSort+", "Starting AI analysis of selected messages..."); From 64066ab8ee16e7b8a6cfd5fe3dad146efc047f5a Mon Sep 17 00:00:00 2001 From: Nigel Hagen Date: Tue, 13 Jan 2026 03:17:44 +0100 Subject: [PATCH 07/82] v1.2.1: Bug fixes and documentation improvements - Fixed manual label menu to process all selected emails (not just 2) - Added example email folder categories with collapsible spoiler - Improved error handling for batch operations - Better message routing for manual label application --- README.md | 8 +++++++- manifest.json | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ae494f0..f6aaf5f 100644 --- a/README.md +++ b/README.md @@ -236,7 +236,13 @@ Click **"Get API Key"** in AutoSort+ settings to open signup page instantly. ## 📝 Version History -### v1.2.0 (2026-01-13) - Multi-Provider Release ⭐ +### v1.2.1 (2026-01-13) - Bug Fixes & Documentation ⭐ +- ✅ Fixed manual label menu processing more than 2 emails +- ✅ Added example email folder categories with spoiler +- ✅ Improved message handling for batch operations +- ✅ Better error handling in manual label application + +### v1.2.0 (2026-01-13) - Multi-Provider Release - ✅ Multi-provider AI support (Gemini, OpenAI, Anthropic, Groq, Mistral) - ✅ Groq API updated to llama-3.3-70b (Mixtral deprecated) - ✅ IMAP folder discovery with recursive traversal diff --git a/manifest.json b/manifest.json index 4316ff1..8468e1b 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 2, "name": "AutoSort+", - "version": "1.2.0", + "version": "1.2.1", "description": "Automatically sort and label your emails with custom rules using AI", "author": "Nigel Hagen", "applications": { From a9c81065782c99e55772fb022f4f09dd5f0a7222 Mon Sep 17 00:00:00 2001 From: Nigel Hagen Date: Tue, 13 Jan 2026 03:22:00 +0100 Subject: [PATCH 08/82] fix: Skip null categories and try next one when applying labels - When a category folder is not found (null), skip to the next category - Prevents early exit if a matching category prefix exists but folder not found - Better fallback handling for direct folder match --- background.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/background.js b/background.js index e0cd449..18ed93b 100644 --- a/background.js +++ b/background.js @@ -451,6 +451,9 @@ async function applyLabelsToMessages(messages, label) { console.log("Looking for subfolder:", subfolderName); targetFolder = findFolder(categoryFolder.subFolders || [], subfolderName); break; + } else { + console.log("Category folder not found:", category, "- skipping to next category"); + continue; } } } From d0525c3e01b91e2a112052e86473df8891bfbad6 Mon Sep 17 00:00:00 2001 From: Nigel Hagen Date: Tue, 13 Jan 2026 03:22:26 +0100 Subject: [PATCH 09/82] v1.2.0: Complete multi-provider release with all fixes - Multi-provider AI support (Gemini, OpenAI, Anthropic, Groq, Mistral) - Fixed batch email processing (all selected emails now processed) - Added example email folder categories with spoiler - Skip null categories and try next one - IMAP folder discovery with recursive traversal - Professional UI with provider info cards - Move history tracking (last 100 entries) - All syntax errors fixed --- README.md | 11 ++++------- manifest.json | 2 +- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index f6aaf5f..51ba26d 100644 --- a/README.md +++ b/README.md @@ -236,13 +236,7 @@ Click **"Get API Key"** in AutoSort+ settings to open signup page instantly. ## 📝 Version History -### v1.2.1 (2026-01-13) - Bug Fixes & Documentation ⭐ -- ✅ Fixed manual label menu processing more than 2 emails -- ✅ Added example email folder categories with spoiler -- ✅ Improved message handling for batch operations -- ✅ Better error handling in manual label application - -### v1.2.0 (2026-01-13) - Multi-Provider Release +### v1.2.0 (2026-01-13) - Multi-Provider Release ⭐ - ✅ Multi-provider AI support (Gemini, OpenAI, Anthropic, Groq, Mistral) - ✅ Groq API updated to llama-3.3-70b (Mixtral deprecated) - ✅ IMAP folder discovery with recursive traversal @@ -251,6 +245,9 @@ Click **"Get API Key"** in AutoSort+ settings to open signup page instantly. - ✅ Move history tracking (last 100 entries) - ✅ Professional funnel/envelope icons - ✅ Bulk label import +- ✅ Fixed batch email processing (all selected emails) +- ✅ Added example email folder categories +- ✅ Skip null categories and try next one - ✅ Fixed syntax errors in options.js - ✅ Unified API key storage diff --git a/manifest.json b/manifest.json index 8468e1b..4316ff1 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 2, "name": "AutoSort+", - "version": "1.2.1", + "version": "1.2.0", "description": "Automatically sort and label your emails with custom rules using AI", "author": "Nigel Hagen", "applications": { From 7a648351a1a7a306008022d1ccf69cfe08c0cddb Mon Sep 17 00:00:00 2001 From: Nigel Hagen Date: Tue, 13 Jan 2026 03:28:40 +0100 Subject: [PATCH 10/82] feat: auto-create missing custom folders when applying labels - Create folder on the fly if a configured custom label folder is missing - Skip auto-creation for imported/structured labels (contain / or \) - Uses first account folder as parent; logs failures but continues --- background.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/background.js b/background.js index 18ed93b..620d0de 100644 --- a/background.js +++ b/background.js @@ -464,6 +464,28 @@ async function applyLabelsToMessages(messages, label) { targetFolder = findFolder(account.folders, label); } + // Auto-create missing folder when it's a custom label (skip imported/structured labels) + if (!targetFolder) { + const looksImported = label.includes('/') || label.includes('\\'); + if (looksImported) { + console.warn(`Folder "${label}" looks imported/structured; skipping auto-create.`); + } else { + try { + const parentFolder = account.folders && account.folders.length > 0 ? account.folders[0] : null; + if (parentFolder && browser.folders && browser.folders.create) { + console.log(`Creating missing folder "${label}" under ${parentFolder.name || 'root'}`); + const created = await browser.folders.create(parentFolder, label); + if (created) { + targetFolder = created; + console.log("Created folder:", created); + } + } + } catch (createError) { + console.error(`Failed to create folder "${label}":`, createError); + } + } + } + console.log("Moving message to folder:", targetFolder ? targetFolder.name : "not found"); try { From f2edd4a27a8f060a66e8373152956255f1d31f97 Mon Sep 17 00:00:00 2001 From: Nigel Hagen Date: Tue, 13 Jan 2026 03:29:32 +0100 Subject: [PATCH 11/82] chore: update v1.2.0 notes for auto-create folders --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 51ba26d..c3dcd20 100644 --- a/README.md +++ b/README.md @@ -248,6 +248,7 @@ Click **"Get API Key"** in AutoSort+ settings to open signup page instantly. - ✅ Fixed batch email processing (all selected emails) - ✅ Added example email folder categories - ✅ Skip null categories and try next one +- ✅ Auto-create missing custom folders (skips imported/structured) - ✅ Fixed syntax errors in options.js - ✅ Unified API key storage From 75a73b53e6123f57d3b7c4f9d2b58e66756a171e Mon Sep 17 00:00:00 2001 From: Nigel Hagen Date: Tue, 13 Jan 2026 03:31:49 +0100 Subject: [PATCH 12/82] fix: skip emails when AI returns null label --- background.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/background.js b/background.js index 620d0de..fd6a896 100644 --- a/background.js +++ b/background.js @@ -753,15 +753,16 @@ browser.menus.onClicked.addListener(async (info, tab) => { console.log("Analyzing message content"); const label = await analyzeEmailContent(emailContent); - - if (label) { - console.log("Applying label:", label); - await applyLabelsToMessages([message], label); - await showNotification("AutoSort+", `Successfully applied label: ${label}`); - } else { - console.log("No label generated from analysis"); - await showNotification("AutoSort+ Error", "Could not generate label from analysis"); + + // Skip if AI returned null/no label + if (!label || String(label).trim().toLowerCase() === "null") { + console.log("Skipping message because generated label was null/empty"); + continue; } + + console.log("Applying label:", label); + await applyLabelsToMessages([message], label); + await showNotification("AutoSort+", `Successfully applied label: ${label}`); } } catch (error) { console.error("Error during AI analysis:", error); From b089a8d40168e37cd4ca555d88e3a64810a7b312 Mon Sep 17 00:00:00 2001 From: Nigel Hagen Date: Tue, 13 Jan 2026 05:14:42 +0100 Subject: [PATCH 13/82] v1.2.0: Add rate limit warnings for free API tiers - Added prominent warning box in settings UI - Updated README with detailed rate limit estimates per provider - Updated release notes with API quota guidance - Free tiers: 5-20 emails before hitting limits - Groq and Gemini recommended for best free tier limits - Paid plans recommended for daily email processing --- README.md | 22 +++++++++++++++++++++ options.html | 4 ++++ release_notes.md | 50 ++++++++++++++++++++++++++++++++++++++++++++++++ styles.css | 13 +++++++++++++ 4 files changed, 89 insertions(+) diff --git a/README.md b/README.md index c3dcd20..b0266fb 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,28 @@ zip -r autosortplus.xpi manifest.json background.js options.js options.html styl Click **"Get API Key"** in AutoSort+ settings to open signup page instantly. +**⚠️ Important Rate Limit Warning:** +Free API tiers are **severely limited** when processing emails due to large text content. You may only be able to analyze **5-20 emails** before hitting rate limits. + +**Email processing estimates on free tiers:** +- **Gemini**: 15-20 emails (best free option for email) +- **Groq**: 20-30 emails (fastest, highest free limits) +- **Anthropic**: 10-15 emails +- **OpenAI**: 5-10 emails (very restrictive on free tier) +- **Mistral**: 10-15 emails + +**For daily email processing, paid API plans are strongly recommended** ($5-20/month). + +Free tiers are suitable for: +- Occasional use (a few emails per day) +- Testing and evaluation +- Light personal inbox management + +For regular use, consider: +- **Paid API tiers**: Unlimited or high-volume processing +- **Batch processing**: Process emails in small groups with delays between batches +- **Best free option**: Start with Groq or Gemini for highest limits + ### Step 3: Add Your API Key 1. Paste API key into the **"API Key"** field 2. Click **"Test API Connection"** to verify diff --git a/options.html b/options.html index 0d96648..86c9e91 100644 --- a/options.html +++ b/options.html @@ -25,6 +25,10 @@

AI Settings

+
+ ⚠️ Rate Limit Warning: Free API tiers are severely limited when processing emails. You may only process 5-20 emails before hitting rate limits. Paid plans ($5-20/month) are recommended for daily email processing. +
+
diff --git a/release_notes.md b/release_notes.md index 3a678ce..baf2a0f 100644 --- a/release_notes.md +++ b/release_notes.md @@ -1,3 +1,53 @@ +## AutoSort+ v1.2.0 + +### ⚠️ Important Rate Limit Warning +**Free API tiers are severely limited when processing emails!** Email content is large text, which counts heavily against rate limits: + +- **Gemini**: ~15-20 emails before hitting limits +- **OpenAI**: ~5-10 emails (very strict on free tier) +- **Anthropic**: ~10-15 emails +- **Groq**: ~20-30 emails (best free option) +- **Mistral**: ~10-15 emails + +**For daily email processing, paid API plans are strongly recommended.** + +Free tiers are suitable for: +- Occasional use (a few emails per day) +- Testing the addon +- Light personal email management + +For regular use, consider: +- Upgrading to paid API tiers ($5-20/month) +- Processing emails in small batches with delays +- Using Groq for the highest free tier limits + +### Features +- **Multi-provider AI support** (Gemini, OpenAI, Anthropic, Groq, Mistral) +- **IMAP folder discovery** - Automatically load folders from mail accounts +- **Batch email processing** - Select and sort multiple emails at once +- **Move history tracking** - Last 100 email moves recorded +- **Smart label matching** - Skips null categories, auto-creates custom folders +- **Professional UI** - Provider info cards, real-time validation + +### Changes +- Added support for 5 AI providers with easy switching +- Updated Groq to llama-3.3-70b model +- Improved error handling and validation +- Fixed batch email processing bugs +- Enhanced folder management +- Added rate limit guidance + +### Installation in Thunderbird +1. Download the autosortplus.xpi file +2. Open Thunderbird +3. Click the Menu button (☰) and select "Add-ons and Themes" +4. Click the gear icon and select "Install Add-on From File..." +5. Select the downloaded autosortplus.xpi file +6. Click "Add" when prompted to install the add-on +7. Restart Thunderbird when prompted + +--- + ## AutoSort+ v1.0.0 ### Features diff --git a/styles.css b/styles.css index fcdc255..7de42eb 100644 --- a/styles.css +++ b/styles.css @@ -111,6 +111,19 @@ h2 { color: white; } +.warning-box { + background-color: #fff3cd; + border: 1px solid #ffc107; + border-radius: 4px; + padding: 12px 16px; + margin: 15px 0; + color: #856404; +} + +.warning-box strong { + color: #856404; +} + .button { background-color: var(--primary-color); color: white; From 169ce75e2e05727528dfeb81ba75519437073752 Mon Sep 17 00:00:00 2001 From: Nigel Hagen Date: Tue, 13 Jan 2026 05:15:38 +0100 Subject: [PATCH 14/82] Add v1.2.0 release XPI file --- autosortplus.xpi | Bin 0 -> 18986 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 autosortplus.xpi diff --git a/autosortplus.xpi b/autosortplus.xpi new file mode 100644 index 0000000000000000000000000000000000000000..b4336438027f13082994eeed13029252e4e3b91f GIT binary patch literal 18986 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 literal 0 HcmV?d00001 From 75a6bb25a558250ef443bcf90c63deb6118c151b Mon Sep 17 00:00:00 2001 From: Nigel Hagen Date: Tue, 13 Jan 2026 05:30:26 +0100 Subject: [PATCH 15/82] Implement Gemini free tier rate limiting (5/min, 20/day) - Free tier: 5 requests per minute (12 second delay between requests) - Free tier: 20 requests per day limit - Added 'Gemini paid plan' checkbox in settings to bypass limits - Shows countdown timer during rate limiting - Displays hours until daily reset when limit reached - Tracks requests in browser storage - Only applies to Gemini provider - Paid plan users can disable rate limiting --- autosortplus.xpi | Bin 18986 -> 19989 bytes background.js | 94 ++++++++++++++++++++++++++++++++++++++++++++++- options.html | 5 +++ options.js | 17 ++++++++- 4 files changed, 113 insertions(+), 3 deletions(-) diff --git a/autosortplus.xpi b/autosortplus.xpi index b4336438027f13082994eeed13029252e4e3b91f..788d73b9d9453a4b98b22bcfd7218dcda445b392 100644 GIT binary patch delta 14273 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 aR3X8l$w@LAkYZBFr!su7W3vBiPyZJSW>S6t delta 13238 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 diff --git a/background.js b/background.js index fd6a896..81917c6 100644 --- a/background.js +++ b/background.js @@ -10,6 +10,70 @@ browser.runtime.onMessage.addListener((message, sender, sendResponse) => { } }); +// Gemini rate limiting functions (free tier: 5/min, 20/day) +async function checkGeminiRateLimit() { + const now = Date.now(); + const data = await browser.storage.local.get(['geminiRateLimit']); + const rateLimit = data.geminiRateLimit || { requests: [], dailyCount: 0, dailyResetTime: now }; + + // Reset daily count if it's a new day + if (now > rateLimit.dailyResetTime) { + rateLimit.dailyCount = 0; + rateLimit.dailyResetTime = now + (24 * 60 * 60 * 1000); // 24 hours from now + } + + // Check daily limit (20 per day) + if (rateLimit.dailyCount >= 20) { + const hoursUntilReset = Math.ceil((rateLimit.dailyResetTime - now) / (1000 * 60 * 60)); + return { + allowed: false, + message: `Gemini free tier daily limit reached (20/day). Resets in ${hoursUntilReset} hours. Upgrade to paid plan in settings to remove limits.` + }; + } + + // Remove requests older than 1 minute + const oneMinuteAgo = now - 60000; + rateLimit.requests = rateLimit.requests.filter(time => time > oneMinuteAgo); + + // Check if we need to wait (12 seconds between requests = 5 per minute) + if (rateLimit.requests.length > 0) { + const lastRequest = Math.max(...rateLimit.requests); + const timeSinceLastRequest = now - lastRequest; + const minInterval = 12000; // 12 seconds + + if (timeSinceLastRequest < minInterval) { + const waitTime = Math.ceil((minInterval - timeSinceLastRequest) / 1000); + return { + allowed: true, + waitTime: waitTime + }; + } + } + + return { + allowed: true, + waitTime: 0 + }; +} + +async function trackGeminiRequest() { + const now = Date.now(); + const data = await browser.storage.local.get(['geminiRateLimit']); + const rateLimit = data.geminiRateLimit || { requests: [], dailyCount: 0, dailyResetTime: now + (24 * 60 * 60 * 1000) }; + + // Add current request + rateLimit.requests.push(now); + rateLimit.dailyCount += 1; + + // Clean old requests + const oneMinuteAgo = now - 60000; + rateLimit.requests = rateLimit.requests.filter(time => time > oneMinuteAgo); + + await browser.storage.local.set({ geminiRateLimit: rateLimit }); + + console.log(`Gemini requests: ${rateLimit.dailyCount}/20 today, ${rateLimit.requests.length} in last minute`); +} + // Function to show notification async function showNotification(title, message, type = "basic") { // Log to console (Thunderbird doesn't support browser.notifications) @@ -60,9 +124,32 @@ async function analyzeEmailContent(emailContent) { "Starting email analysis..." ); - const settings = await browser.storage.local.get(['apiKey', 'aiProvider', 'labels', 'enableAi']); + const settings = await browser.storage.local.get(['apiKey', 'aiProvider', 'labels', 'enableAi', 'geminiPaidPlan', 'geminiRateLimit']); const provider = settings.aiProvider || 'gemini'; + // Check Gemini rate limits (free tier only) + if (provider === 'gemini' && !settings.geminiPaidPlan) { + const rateLimitCheck = await checkGeminiRateLimit(); + if (!rateLimitCheck.allowed) { + await updateNotification( + notificationId, + "AutoSort+ Rate Limit", + rateLimitCheck.message + ); + return null; + } + + // Wait if needed for rate limiting (12 seconds between requests) + if (rateLimitCheck.waitTime > 0) { + await updateNotification( + notificationId, + "AutoSort+ Rate Limiting", + `Waiting ${rateLimitCheck.waitTime} seconds for rate limit...` + ); + await new Promise(resolve => setTimeout(resolve, rateLimitCheck.waitTime * 1000)); + } + } + console.log("Settings retrieved:", { hasApiKey: !!settings.apiKey, provider: provider, @@ -125,6 +212,11 @@ async function analyzeEmailContent(emailContent) { const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=${settings.apiKey}`; console.log("Making API request to Gemini..."); + // Track request for rate limiting (free tier only) + if (!settings.geminiPaidPlan) { + await trackGeminiRequest(); + } + await updateNotification( notificationId, "AutoSort+ AI Analysis", diff --git a/options.html b/options.html index 86c9e91..07b65e7 100644 --- a/options.html +++ b/options.html @@ -44,6 +44,11 @@

AI Settings

+ +

AutoSort+ uses AI to analyze your emails and automatically sort them into categories/folders based on their content. The AI will: