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:
vd authored and GitHub committed 2026-04-06 20:32:42 +08:00
1 parent 50a325be11
commit ed0dd811a8
37 files changed
+1989 -455

No files matched your search

+49 -10
View File
@@ -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;