@@ -10,24 +10,108 @@ browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
1010 }
1111} ) ;
1212
13- // Gemini rate limiting functions (free tier: 5/min, 20/day)
13+ // Gemini rate limiting functions (free tier: 5/min, 20/day per key )
1414async function checkGeminiRateLimit ( ) {
1515 const now = Date . now ( ) ;
16- const data = await browser . storage . local . get ( [ 'geminiRateLimit' ] ) ;
16+ const data = await browser . storage . local . get ( [
17+ 'geminiApiKeys' ,
18+ 'geminiRateLimits' ,
19+ 'currentGeminiKeyIndex' ,
20+ 'geminiPaidPlan' ,
21+ 'geminiRateLimit' // Legacy single-key support
22+ ] ) ;
23+
24+ // Handle paid plan - no limits
25+ if ( data . geminiPaidPlan ) {
26+ return { allowed : true , waitTime : 0 } ;
27+ }
28+
29+ // Multi-key mode
30+ if ( data . geminiApiKeys && data . geminiApiKeys . length > 0 ) {
31+ const keys = data . geminiApiKeys ;
32+ const rateLimits = data . geminiRateLimits || keys . map ( ( ) => ( {
33+ requests : [ ] ,
34+ dailyCount : 0 ,
35+ dailyResetTime : now + ( 24 * 60 * 60 * 1000 )
36+ } ) ) ;
37+ let currentIndex = data . currentGeminiKeyIndex || 0 ;
38+
39+ // Try to find an available key
40+ const startIndex = currentIndex ;
41+ let attempts = 0 ;
42+
43+ while ( attempts < keys . length ) {
44+ const rateLimit = rateLimits [ currentIndex ] ;
45+
46+ // Reset daily count if it's a new day
47+ if ( now > rateLimit . dailyResetTime ) {
48+ rateLimit . dailyCount = 0 ;
49+ rateLimit . dailyResetTime = now + ( 24 * 60 * 60 * 1000 ) ;
50+ rateLimit . requests = [ ] ;
51+ }
52+
53+ // Remove requests older than 1 minute
54+ const oneMinuteAgo = now - 60000 ;
55+ rateLimit . requests = rateLimit . requests . filter ( time => time > oneMinuteAgo ) ;
56+
57+ // Check if this key is available
58+ if ( rateLimit . dailyCount < 20 ) {
59+ // Check if we need to wait
60+ if ( rateLimit . requests . length > 0 ) {
61+ const lastRequest = Math . max ( ...rateLimit . requests ) ;
62+ const timeSinceLastRequest = now - lastRequest ;
63+ const minInterval = 12000 ; // 12 seconds
64+
65+ if ( timeSinceLastRequest < minInterval ) {
66+ const waitTime = Math . ceil ( ( minInterval - timeSinceLastRequest ) / 1000 ) ;
67+ return {
68+ allowed : true ,
69+ waitTime : waitTime ,
70+ keyIndex : currentIndex
71+ } ;
72+ }
73+ }
74+
75+ // This key is ready to use
76+ await browser . storage . local . set ( {
77+ currentGeminiKeyIndex : currentIndex ,
78+ geminiRateLimits : rateLimits
79+ } ) ;
80+
81+ return {
82+ allowed : true ,
83+ waitTime : 0 ,
84+ keyIndex : currentIndex
85+ } ;
86+ }
87+
88+ // This key has reached its limit, try next one
89+ currentIndex = ( currentIndex + 1 ) % keys . length ;
90+ attempts ++ ;
91+ }
92+
93+ // All keys have reached their limits
94+ return {
95+ allowed : false ,
96+ message : `All ${ keys . length } Gemini API keys have reached their daily limit (20/day each). Please wait for reset or add more API keys in settings.`
97+ } ;
98+ }
99+
100+ // Legacy single-key mode (backward compatibility)
17101 const rateLimit = data . geminiRateLimit || { requests : [ ] , dailyCount : 0 , dailyResetTime : now } ;
18102
19103 // Reset daily count if it's a new day
20104 if ( now > rateLimit . dailyResetTime ) {
21105 rateLimit . dailyCount = 0 ;
22- rateLimit . dailyResetTime = now + ( 24 * 60 * 60 * 1000 ) ; // 24 hours from now
106+ rateLimit . dailyResetTime = now + ( 24 * 60 * 60 * 1000 ) ;
23107 }
24108
25109 // Check daily limit (20 per day)
26110 if ( rateLimit . dailyCount >= 20 ) {
27111 const hoursUntilReset = Math . ceil ( ( rateLimit . dailyResetTime - now ) / ( 1000 * 60 * 60 ) ) ;
28112 return {
29113 allowed : false ,
30- message : `Gemini free tier daily limit reached (20/day). Resets in ${ hoursUntilReset } hours. Upgrade to paid plan in settings to remove limits.`
114+ message : `Gemini free tier daily limit reached (20/day). Resets in ${ hoursUntilReset } hours. Upgrade to paid plan or add multiple API keys in settings to remove limits.`
31115 } ;
32116 }
33117
@@ -56,22 +140,52 @@ async function checkGeminiRateLimit() {
56140 } ;
57141}
58142
59- async function trackGeminiRequest ( ) {
143+ async function trackGeminiRequest ( keyIndex = null ) {
60144 const now = Date . now ( ) ;
61- const data = await browser . storage . local . get ( [ 'geminiRateLimit' ] ) ;
62- const rateLimit = data . geminiRateLimit || { requests : [ ] , dailyCount : 0 , dailyResetTime : now + ( 24 * 60 * 60 * 1000 ) } ;
145+ const data = await browser . storage . local . get ( [
146+ 'geminiApiKeys' ,
147+ 'geminiRateLimits' ,
148+ 'currentGeminiKeyIndex' ,
149+ 'geminiRateLimit' // Legacy
150+ ] ) ;
63151
64- // Add current request
65- rateLimit . requests . push ( now ) ;
66- rateLimit . dailyCount += 1 ;
67-
68- // Clean old requests
69- const oneMinuteAgo = now - 60000 ;
70- rateLimit . requests = rateLimit . requests . filter ( time => time > oneMinuteAgo ) ;
71-
72- await browser . storage . local . set ( { geminiRateLimit : rateLimit } ) ;
73-
74- console . log ( `Gemini requests: ${ rateLimit . dailyCount } /20 today, ${ rateLimit . requests . length } in last minute` ) ;
152+ // Multi-key mode
153+ if ( data . geminiApiKeys && data . geminiApiKeys . length > 0 && keyIndex !== null ) {
154+ const rateLimits = data . geminiRateLimits || data . geminiApiKeys . map ( ( ) => ( {
155+ requests : [ ] ,
156+ dailyCount : 0 ,
157+ dailyResetTime : now + ( 24 * 60 * 60 * 1000 )
158+ } ) ) ;
159+
160+ const rateLimit = rateLimits [ keyIndex ] ;
161+
162+ // Add current request
163+ rateLimit . requests . push ( now ) ;
164+ rateLimit . dailyCount += 1 ;
165+
166+ // Clean old requests
167+ const oneMinuteAgo = now - 60000 ;
168+ rateLimit . requests = rateLimit . requests . filter ( time => time > oneMinuteAgo ) ;
169+
170+ await browser . storage . local . set ( { geminiRateLimits : rateLimits } ) ;
171+
172+ console . log ( `Gemini Key #${ keyIndex + 1 } : ${ rateLimit . dailyCount } /20 today, ${ rateLimit . requests . length } in last minute` ) ;
173+ } else {
174+ // Legacy single-key mode
175+ const rateLimit = data . geminiRateLimit || { requests : [ ] , dailyCount : 0 , dailyResetTime : now + ( 24 * 60 * 60 * 1000 ) } ;
176+
177+ // Add current request
178+ rateLimit . requests . push ( now ) ;
179+ rateLimit . dailyCount += 1 ;
180+
181+ // Clean old requests
182+ const oneMinuteAgo = now - 60000 ;
183+ rateLimit . requests = rateLimit . requests . filter ( time => time > oneMinuteAgo ) ;
184+
185+ await browser . storage . local . set ( { geminiRateLimit : rateLimit } ) ;
186+
187+ console . log ( `Gemini requests: ${ rateLimit . dailyCount } /20 today, ${ rateLimit . requests . length } in last minute` ) ;
188+ }
75189}
76190
77191// Function to show notification
@@ -124,10 +238,21 @@ async function analyzeEmailContent(emailContent) {
124238 "Starting email analysis..."
125239 ) ;
126240
127- const settings = await browser . storage . local . get ( [ 'apiKey' , 'aiProvider' , 'labels' , 'enableAi' , 'geminiPaidPlan' , 'geminiRateLimit' ] ) ;
241+ const settings = await browser . storage . local . get ( [
242+ 'apiKey' ,
243+ 'geminiApiKeys' ,
244+ 'currentGeminiKeyIndex' ,
245+ 'aiProvider' ,
246+ 'labels' ,
247+ 'enableAi' ,
248+ 'geminiPaidPlan' ,
249+ 'geminiRateLimit' ,
250+ 'geminiRateLimits'
251+ ] ) ;
128252 const provider = settings . aiProvider || 'gemini' ;
129253
130254 // Check Gemini rate limits (free tier only)
255+ let keyIndexToUse = null ;
131256 if ( provider === 'gemini' && ! settings . geminiPaidPlan ) {
132257 const rateLimitCheck = await checkGeminiRateLimit ( ) ;
133258 if ( ! rateLimitCheck . allowed ) {
@@ -136,22 +261,23 @@ async function analyzeEmailContent(emailContent) {
136261 "AutoSort+ Rate Limit" ,
137262 rateLimitCheck . message
138263 ) ;
139- return null ;
264+ throw new Error ( rateLimitCheck . message ) ;
140265 }
141266
142- // Wait if needed for rate limiting (12 seconds between requests)
143267 if ( rateLimitCheck . waitTime > 0 ) {
144268 await updateNotification (
145269 notificationId ,
146- "AutoSort+ Rate Limiting " ,
147- `Waiting ${ rateLimitCheck . waitTime } seconds for rate limit ...`
270+ "AutoSort+ Rate Limit " ,
271+ `Rate limit reached. Waiting ${ rateLimitCheck . waitTime } seconds...`
148272 ) ;
149273 await new Promise ( resolve => setTimeout ( resolve , rateLimitCheck . waitTime * 1000 ) ) ;
150274 }
275+
276+ keyIndexToUse = rateLimitCheck . keyIndex ;
151277 }
152278
153279 console . log ( "Settings retrieved:" , {
154- hasApiKey : ! ! settings . apiKey ,
280+ hasApiKey : ! ! ( settings . apiKey || ( settings . geminiApiKeys && settings . geminiApiKeys . length > 0 ) ) ,
155281 provider : provider ,
156282 labels : settings . labels ,
157283 enableAi : settings . enableAi !== false
@@ -167,7 +293,22 @@ async function analyzeEmailContent(emailContent) {
167293 return null ;
168294 }
169295
170- if ( ! settings . apiKey ) {
296+ // Check API key availability based on provider
297+ let apiKeyToUse = null ;
298+ if ( provider === 'gemini' ) {
299+ if ( settings . geminiApiKeys && settings . geminiApiKeys . length > 0 ) {
300+ const keyIndex = keyIndexToUse !== null ? keyIndexToUse : ( settings . currentGeminiKeyIndex || 0 ) ;
301+ apiKeyToUse = settings . geminiApiKeys [ keyIndex ] ;
302+ console . log ( `Using Gemini API Key #${ keyIndex + 1 } of ${ settings . geminiApiKeys . length } ` ) ;
303+ } else if ( settings . apiKey ) {
304+ // Legacy single key
305+ apiKeyToUse = settings . apiKey ;
306+ }
307+ } else {
308+ apiKeyToUse = settings . apiKey ;
309+ }
310+
311+ if ( ! apiKeyToUse ) {
171312 console . error ( "Missing API key" ) ;
172313 await updateNotification (
173314 notificationId ,
@@ -209,12 +350,12 @@ async function analyzeEmailContent(emailContent) {
209350 let data ;
210351
211352 if ( provider === 'gemini' ) {
212- const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=${ settings . apiKey } ` ;
353+ const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=${ apiKeyToUse } ` ;
213354 console . log ( "Making API request to Gemini..." ) ;
214355
215356 // Track request for rate limiting (free tier only)
216357 if ( ! settings . geminiPaidPlan ) {
217- await trackGeminiRequest ( ) ;
358+ await trackGeminiRequest ( keyIndexToUse ) ;
218359 }
219360
220361 await updateNotification (
@@ -281,7 +422,7 @@ async function analyzeEmailContent(emailContent) {
281422 method : 'POST' ,
282423 headers : {
283424 'Content-Type' : 'application/json' ,
284- 'Authorization' : `Bearer ${ settings . apiKey } `
425+ 'Authorization' : `Bearer ${ apiKeyToUse } `
285426 } ,
286427 body : JSON . stringify ( {
287428 model : 'gpt-4o-mini' ,
@@ -304,7 +445,7 @@ async function analyzeEmailContent(emailContent) {
304445 method : 'POST' ,
305446 headers : {
306447 'Content-Type' : 'application/json' ,
307- 'x-api-key' : settings . apiKey ,
448+ 'x-api-key' : apiKeyToUse ,
308449 'anthropic-version' : '2023-06-01'
309450 } ,
310451 body : JSON . stringify ( {
@@ -327,7 +468,7 @@ async function analyzeEmailContent(emailContent) {
327468 method : 'POST' ,
328469 headers : {
329470 'Content-Type' : 'application/json' ,
330- 'Authorization' : `Bearer ${ settings . apiKey } `
471+ 'Authorization' : `Bearer ${ apiKeyToUse } `
331472 } ,
332473 body : JSON . stringify ( {
333474 model : 'llama-3.3-70b-versatile' ,
@@ -350,7 +491,7 @@ async function analyzeEmailContent(emailContent) {
350491 method : 'POST' ,
351492 headers : {
352493 'Content-Type' : 'application/json' ,
353- 'Authorization' : `Bearer ${ settings . apiKey } `
494+ 'Authorization' : `Bearer ${ apiKeyToUse } `
354495 } ,
355496 body : JSON . stringify ( {
356497 model : 'mistral-small-latest' ,
0 commit comments