feat: account management overhaul + 7 issue resolutions (#147)
- Add smart JSON export/import with schema validation and deduplication - Add per-account proxy configurations (Issue #79) - Add advanced account sorting engine (Issues #126 & #99) - Add compact horizontal layout, AI credits, inline model visibility (Issues #145 & #134) - Add desktop push notifications for low quota (Issue #53) - Fix Arch Linux AUR process detection bug (Issue #117) - Default theme set to Dark Mode (it's better) - UI Polishing: Improved sort dropdown and symmetrical dialog layouts - Dev Mode: Resolved port conflicts and single-instance locks - Updated README and README.zh-CN with new features
This commit is contained in:
37 files changed
+1989
-455
No files matched your search
@@ -1,3 +1,4 @@
|
||||
import { Notification } from 'electron';
|
||||
import { CloudAccountRepo } from '../ipc/database/cloudHandler';
|
||||
import { GoogleAPIService } from './GoogleAPIService';
|
||||
import { AutoSwitchService } from './AutoSwitchService';
|
||||
@@ -101,18 +102,33 @@ export class CloudMonitorService {
|
||||
let accessToken = account.token.access_token;
|
||||
if (account.token.expiry_timestamp < now + 600) {
|
||||
logger.info(`Monitor: Refreshing token for ${account.email}`);
|
||||
const newToken = await GoogleAPIService.refreshAccessToken(account.token.refresh_token);
|
||||
account.token.access_token = newToken.access_token;
|
||||
account.token.expires_in = newToken.expires_in;
|
||||
account.token.expiry_timestamp = now + newToken.expires_in;
|
||||
await CloudAccountRepo.updateToken(account.id, account.token);
|
||||
accessToken = newToken.access_token;
|
||||
try {
|
||||
const newToken = await GoogleAPIService.refreshAccessToken(
|
||||
account.token.refresh_token,
|
||||
account.proxy_url,
|
||||
);
|
||||
account.token.access_token = newToken.access_token;
|
||||
account.token.expires_in = newToken.expires_in;
|
||||
account.token.expiry_timestamp = now + newToken.expires_in;
|
||||
await CloudAccountRepo.updateToken(account.id, account.token);
|
||||
accessToken = newToken.access_token;
|
||||
} catch (refreshError) {
|
||||
logger.error(`Monitor: Token refresh failed for ${account.email}`, refreshError);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Fetch Quota
|
||||
// We delay slightly between requests to act human/avoid spike
|
||||
await new Promise((r) => setTimeout(r, 1000));
|
||||
const quota = await GoogleAPIService.fetchQuota(accessToken);
|
||||
const quota = await GoogleAPIService.fetchQuota(accessToken, account.proxy_url);
|
||||
|
||||
try {
|
||||
const aiCredits = await GoogleAPIService.fetchAICredits(accessToken, account.proxy_url);
|
||||
if (aiCredits) {
|
||||
quota.ai_credits = aiCredits;
|
||||
}
|
||||
} catch (creditError) {
|
||||
logger.warn(`Monitor: Failed to fetch credits for ${account.email}`, creditError);
|
||||
}
|
||||
|
||||
// 3. Update DB
|
||||
await CloudAccountRepo.updateQuota(account.id, quota);
|
||||
@@ -123,7 +139,30 @@ export class CloudMonitorService {
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Check for Auto-Switch
|
||||
// 4. Check for Quota Alerts
|
||||
const alertEnabled = CloudAccountRepo.getSetting<boolean>('quota_alert_enabled', false);
|
||||
const alertThreshold = CloudAccountRepo.getSetting<number>('quota_alert_threshold', 20);
|
||||
|
||||
if (alertEnabled) {
|
||||
for (const account of accounts) {
|
||||
if (!account.quota?.models) continue;
|
||||
const lowQuotaModels = Object.entries(account.quota.models)
|
||||
.filter(([_, info]) => info.percentage <= alertThreshold && info.percentage > 0)
|
||||
.map(([name, info]) => {
|
||||
return info.display_name || name.replace('models/', '').replace(/-/g, ' ');
|
||||
});
|
||||
|
||||
if (lowQuotaModels.length > 0) {
|
||||
new Notification({
|
||||
title: 'Low Quota Alert',
|
||||
body: `${account.email}: ${lowQuotaModels.join(', ')} are low on quota`,
|
||||
silent: false,
|
||||
}).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Check for Auto-Switch
|
||||
await AutoSwitchService.checkAndSwitchIfNeeded();
|
||||
} finally {
|
||||
this.isPolling = false;
|
||||
|
||||
Reference in new issue
Block a user