fix(cloud): fetch AI credits on startup with endpoint fallback and preserve cached credits
This commit is contained in:
1 parent
ed0dd811a8
commit
61716bb512
5 files changed
+65
-12
No files matched your search
@@ -324,6 +324,7 @@ export function CloudAccountCard({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const aiCredits = account.quota?.ai_credits;
|
const aiCredits = account.quota?.ai_credits;
|
||||||
|
const shouldShowAiCredits = !!aiCredits && Number.isFinite(aiCredits.credits) && aiCredits.credits >= 0;
|
||||||
|
|
||||||
const formatCreditsExpiry = (expiryDate: string) => {
|
const formatCreditsExpiry = (expiryDate: string) => {
|
||||||
if (!expiryDate) return '';
|
if (!expiryDate) return '';
|
||||||
@@ -369,7 +370,7 @@ export function CloudAccountCard({
|
|||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription className="truncate text-xs">{account.email}</CardDescription>
|
<CardDescription className="truncate text-xs">{account.email}</CardDescription>
|
||||||
|
|
||||||
{aiCredits && aiCredits.credits > 0 && (
|
{shouldShowAiCredits && aiCredits && (
|
||||||
<div className="mt-1 flex items-center gap-1 text-[10px] font-medium text-amber-500">
|
<div className="mt-1 flex items-center gap-1 text-[10px] font-medium text-amber-500">
|
||||||
<span>${aiCredits.credits.toFixed(2)}</span>
|
<span>${aiCredits.credits.toFixed(2)}</span>
|
||||||
{aiCredits.expiryDate && (
|
{aiCredits.expiryDate && (
|
||||||
@@ -623,6 +624,7 @@ export function CompactCloudAccountCard({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const aiCredits = account.quota?.ai_credits;
|
const aiCredits = account.quota?.ai_credits;
|
||||||
|
const shouldShowAiCredits = !!aiCredits && Number.isFinite(aiCredits.credits) && aiCredits.credits >= 0;
|
||||||
|
|
||||||
const formatCreditsExpiry = (expiryDate: string) => {
|
const formatCreditsExpiry = (expiryDate: string) => {
|
||||||
if (!expiryDate) return '';
|
if (!expiryDate) return '';
|
||||||
@@ -672,7 +674,7 @@ export function CompactCloudAccountCard({
|
|||||||
<div className="text-muted-foreground flex items-center gap-3 text-xs">
|
<div className="text-muted-foreground flex items-center gap-3 text-xs">
|
||||||
<span className="truncate">{account.email}</span>
|
<span className="truncate">{account.email}</span>
|
||||||
|
|
||||||
{aiCredits && aiCredits.credits > 0 && (
|
{shouldShowAiCredits && aiCredits && (
|
||||||
<span className="shrink-0 text-amber-500">
|
<span className="shrink-0 text-amber-500">
|
||||||
${aiCredits.credits.toFixed(2)}
|
${aiCredits.credits.toFixed(2)}
|
||||||
{aiCredits.expiryDate && (
|
{aiCredits.expiryDate && (
|
||||||
|
|||||||
@@ -280,7 +280,21 @@ export function CloudAccountList() {
|
|||||||
refreshMutation.mutate(
|
refreshMutation.mutate(
|
||||||
{ accountId: id },
|
{ accountId: id },
|
||||||
{
|
{
|
||||||
onSuccess: () => toast({ title: t('cloud.toast.quotaRefreshed') }),
|
onSuccess: (updatedAccount) => {
|
||||||
|
const credits = updatedAccount.quota?.ai_credits?.credits;
|
||||||
|
if (typeof credits === 'number') {
|
||||||
|
toast({
|
||||||
|
title: t('cloud.toast.quotaRefreshed'),
|
||||||
|
description: `AI credits: $${credits.toFixed(2)}`,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
toast({
|
||||||
|
title: t('cloud.toast.quotaRefreshed'),
|
||||||
|
description: 'AI credits not available for this refresh.',
|
||||||
|
});
|
||||||
|
},
|
||||||
onError: () => toast({ title: t('cloud.toast.refreshFailed'), variant: 'destructive' }),
|
onError: () => toast({ title: t('cloud.toast.refreshFailed'), variant: 'destructive' }),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -86,8 +86,18 @@ export async function addGoogleAccount(authCode: string): Promise<CloudAccount>
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const quota = await GoogleAPIService.fetchQuota(account.token.access_token);
|
const quota = await GoogleAPIService.fetchQuota(account.token.access_token);
|
||||||
|
try {
|
||||||
|
const aiCredits = await GoogleAPIService.fetchAICredits(account.token.access_token);
|
||||||
|
if (aiCredits) {
|
||||||
|
quota.ai_credits = aiCredits;
|
||||||
|
} else {
|
||||||
|
logger.info(`No AI credits returned for ${account.email} during initial sync`);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn('Failed to fetch initial AI credits', e);
|
||||||
|
}
|
||||||
account.quota = quota;
|
account.quota = quota;
|
||||||
await CloudAccountRepo.updateQuota(account.id, quota);
|
await CloudAccountRepo.updateQuota(account.id, account.quota);
|
||||||
notifyTrayUpdate(account);
|
notifyTrayUpdate(account);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.warn('Failed to fetch initial quota', e);
|
logger.warn('Failed to fetch initial quota', e);
|
||||||
@@ -135,9 +145,8 @@ export async function refreshAccountQuota(accountId: string): Promise<CloudAccou
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const previousAICredits = account.quota?.ai_credits;
|
||||||
const quota = await GoogleAPIService.fetchQuota(account.token.access_token, account.proxy_url);
|
const quota = await GoogleAPIService.fetchQuota(account.token.access_token, account.proxy_url);
|
||||||
account.quota = quota;
|
|
||||||
await CloudAccountRepo.updateQuota(account.id, quota);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const aiCredits = await GoogleAPIService.fetchAICredits(
|
const aiCredits = await GoogleAPIService.fetchAICredits(
|
||||||
@@ -145,13 +154,22 @@ export async function refreshAccountQuota(accountId: string): Promise<CloudAccou
|
|||||||
account.proxy_url,
|
account.proxy_url,
|
||||||
);
|
);
|
||||||
if (aiCredits) {
|
if (aiCredits) {
|
||||||
account.quota = { ...quota, ai_credits: aiCredits };
|
quota.ai_credits = aiCredits;
|
||||||
await CloudAccountRepo.updateQuota(account.id, account.quota);
|
} else {
|
||||||
|
logger.info(`No AI credits returned for ${account.email} during quota refresh`);
|
||||||
|
if (previousAICredits) {
|
||||||
|
quota.ai_credits = previousAICredits;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.warn('Failed to fetch AI credits', e);
|
logger.warn('Failed to fetch AI credits', e);
|
||||||
|
if (previousAICredits) {
|
||||||
|
quota.ai_credits = previousAICredits;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
account.quota = quota;
|
||||||
|
await CloudAccountRepo.updateQuota(account.id, account.quota);
|
||||||
await CloudAccountRepo.updateLastUsed(account.id);
|
await CloudAccountRepo.updateLastUsed(account.id);
|
||||||
account.last_used = Math.floor(Date.now() / 1000);
|
account.last_used = Math.floor(Date.now() / 1000);
|
||||||
notifyTrayUpdate(account);
|
notifyTrayUpdate(account);
|
||||||
@@ -172,12 +190,11 @@ export async function refreshAccountQuota(accountId: string): Promise<CloudAccou
|
|||||||
|
|
||||||
await CloudAccountRepo.updateToken(account.id, account.token);
|
await CloudAccountRepo.updateToken(account.id, account.token);
|
||||||
|
|
||||||
|
const previousAICredits = account.quota?.ai_credits;
|
||||||
const quota = await GoogleAPIService.fetchQuota(
|
const quota = await GoogleAPIService.fetchQuota(
|
||||||
account.token.access_token,
|
account.token.access_token,
|
||||||
account.proxy_url,
|
account.proxy_url,
|
||||||
);
|
);
|
||||||
account.quota = quota;
|
|
||||||
await CloudAccountRepo.updateQuota(account.id, quota);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const aiCredits = await GoogleAPIService.fetchAICredits(
|
const aiCredits = await GoogleAPIService.fetchAICredits(
|
||||||
@@ -185,13 +202,22 @@ export async function refreshAccountQuota(accountId: string): Promise<CloudAccou
|
|||||||
account.proxy_url,
|
account.proxy_url,
|
||||||
);
|
);
|
||||||
if (aiCredits) {
|
if (aiCredits) {
|
||||||
account.quota = { ...quota, ai_credits: aiCredits };
|
quota.ai_credits = aiCredits;
|
||||||
await CloudAccountRepo.updateQuota(account.id, account.quota);
|
} else {
|
||||||
|
logger.info(`No AI credits returned for ${account.email} after token refresh`);
|
||||||
|
if (previousAICredits) {
|
||||||
|
quota.ai_credits = previousAICredits;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.warn('Failed to fetch AI credits after token refresh', e);
|
logger.warn('Failed to fetch AI credits after token refresh', e);
|
||||||
|
if (previousAICredits) {
|
||||||
|
quota.ai_credits = previousAICredits;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
account.quota = quota;
|
||||||
|
await CloudAccountRepo.updateQuota(account.id, account.quota);
|
||||||
await CloudAccountRepo.updateLastUsed(account.id);
|
await CloudAccountRepo.updateLastUsed(account.id);
|
||||||
account.last_used = Math.floor(Date.now() / 1000);
|
account.last_used = Math.floor(Date.now() / 1000);
|
||||||
return account;
|
return account;
|
||||||
|
|||||||
@@ -425,6 +425,11 @@ app
|
|||||||
if (enabled) {
|
if (enabled) {
|
||||||
logger.info('Startup: Auto-Switch enabled, starting monitor...');
|
logger.info('Startup: Auto-Switch enabled, starting monitor...');
|
||||||
CloudMonitorService.start();
|
CloudMonitorService.start();
|
||||||
|
} else {
|
||||||
|
logger.info(
|
||||||
|
'Startup: Auto-Switch disabled, running one-time quota and AI credits sync...',
|
||||||
|
);
|
||||||
|
await CloudMonitorService.poll();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('Startup: Failed to initialize services', e);
|
logger.error('Startup: Failed to initialize services', e);
|
||||||
|
|||||||
@@ -120,14 +120,20 @@ export class CloudMonitorService {
|
|||||||
|
|
||||||
await new Promise((r) => setTimeout(r, 1000));
|
await new Promise((r) => setTimeout(r, 1000));
|
||||||
const quota = await GoogleAPIService.fetchQuota(accessToken, account.proxy_url);
|
const quota = await GoogleAPIService.fetchQuota(accessToken, account.proxy_url);
|
||||||
|
const previousAICredits = account.quota?.ai_credits;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const aiCredits = await GoogleAPIService.fetchAICredits(accessToken, account.proxy_url);
|
const aiCredits = await GoogleAPIService.fetchAICredits(accessToken, account.proxy_url);
|
||||||
if (aiCredits) {
|
if (aiCredits) {
|
||||||
quota.ai_credits = aiCredits;
|
quota.ai_credits = aiCredits;
|
||||||
|
} else if (previousAICredits) {
|
||||||
|
quota.ai_credits = previousAICredits;
|
||||||
}
|
}
|
||||||
} catch (creditError) {
|
} catch (creditError) {
|
||||||
logger.warn(`Monitor: Failed to fetch credits for ${account.email}`, creditError);
|
logger.warn(`Monitor: Failed to fetch credits for ${account.email}`, creditError);
|
||||||
|
if (previousAICredits) {
|
||||||
|
quota.ai_credits = previousAICredits;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Update DB
|
// 3. Update DB
|
||||||
|
|||||||
Reference in new issue
Block a user