fix: resets time update

This commit is contained in:
YouXam
2026-01-07 14:26:34 +08:00
parent 733b7ccea6
commit a7dd47ab8f
2 changed files with 117 additions and 62 deletions

View File

@@ -1,4 +1,4 @@
import { useState, useEffect } from 'react'; import { useState, useEffect, useRef } from 'react';
import { UsageProgressBar } from './UsageProgressBar'; import { UsageProgressBar } from './UsageProgressBar';
interface ClaudeAccount { interface ClaudeAccount {
@@ -73,17 +73,20 @@ export function AIAccounts({ apiKey }: AIAccountsProps) {
const [openaiAccounts, setOpenaiAccounts] = useState<OpenAIAccount[]>([]); const [openaiAccounts, setOpenaiAccounts] = useState<OpenAIAccount[]>([]);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [isRefreshing, setIsRefreshing] = useState(false); const [isRefreshing, setIsRefreshing] = useState(false);
const [error, setError] = useState<string>(''); const [refreshError, setRefreshError] = useState<string>('');
const [initialError, setInitialError] = useState<string>('');
const hasLoadedDataRef = useRef(false);
useEffect(() => { useEffect(() => {
fetchAccounts(); fetchAccounts('initial');
const interval = setInterval(fetchAccounts, 30000); const interval = setInterval(() => fetchAccounts('auto'), 30000);
return () => clearInterval(interval); return () => clearInterval(interval);
}, [apiKey]); }, [apiKey]);
const fetchAccounts = async (isRefresh = false) => { const fetchAccounts = async (type: 'initial' | 'manual' | 'auto' = 'auto') => {
if (isRefresh) { if (type === 'manual') {
setIsRefreshing(true); setIsRefreshing(true);
setRefreshError('');
} }
try { try {
@@ -100,18 +103,34 @@ export function AIAccounts({ apiKey }: AIAccountsProps) {
const data = await response.json(); const data = await response.json();
setClaudeAccounts(data.claude || []); setClaudeAccounts(data.claude || []);
setOpenaiAccounts(data.openai || []); setOpenaiAccounts(data.openai || []);
setError(''); setRefreshError('');
setInitialError('');
hasLoadedDataRef.current = true;
} catch (err) { } catch (err) {
console.error('Error fetching AI accounts:', err); console.error('Error fetching AI accounts:', err);
setError(err instanceof Error ? err.message : 'Failed to load AI accounts'); const errorMessage = err instanceof Error ? err.message : 'Failed to load AI accounts';
if (type === 'initial') {
setInitialError(errorMessage);
} else if (hasLoadedDataRef.current) {
// Only set refresh error if we have previously loaded data successfully
setRefreshError(errorMessage);
}
} finally { } finally {
if (type === 'initial') {
setIsLoading(false); setIsLoading(false);
if (isRefresh) { }
if (type === 'manual') {
setIsRefreshing(false); setIsRefreshing(false);
} }
} }
}; };
const handleRetry = () => {
setRefreshError('');
fetchAccounts('manual');
};
const formatLastUsed = (lastUsedAt: string | null) => { const formatLastUsed = (lastUsedAt: string | null) => {
if (!lastUsedAt) return 'Never'; if (!lastUsedAt) return 'Never';
const date = new Date(lastUsedAt); const date = new Date(lastUsedAt);
@@ -127,20 +146,6 @@ export function AIAccounts({ apiKey }: AIAccountsProps) {
return `${diffDays}d ago`; return `${diffDays}d ago`;
}; };
const formatTime = (seconds: number) => {
if (seconds <= 0) return '0m';
const days = Math.floor(seconds / 86400);
const hours = Math.floor((seconds % 86400) / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
if (days > 0) {
if (hours > 0) return `${days}d ${hours}h`;
return `${days}d`;
}
if (hours > 0) return `${hours}h ${minutes}m`;
return `${minutes}m`;
};
const getStatusColor = (status: string) => { const getStatusColor = (status: string) => {
switch (status) { switch (status) {
case 'active': case 'active':
@@ -186,16 +191,16 @@ export function AIAccounts({ apiKey }: AIAccountsProps) {
); );
} }
if (error) { if (initialError) {
return ( return (
<div className="bg-card rounded-lg shadow-sm border border-border overflow-hidden mb-6"> <div className="bg-card rounded-lg shadow-sm border border-border overflow-hidden mb-6">
<div className="px-6 py-4 border-b border-border"> <div className="px-6 py-4 border-b border-border">
<h3 className="text-lg font-medium text-card-foreground">AI Accounts Status</h3> <h3 className="text-lg font-medium text-card-foreground">AI Accounts Status</h3>
</div> </div>
<div className="text-center py-12"> <div className="text-center py-12">
<p className="text-destructive">{error}</p> <p className="text-destructive">{initialError}</p>
<button <button
onClick={() => fetchAccounts(true)} onClick={() => fetchAccounts('initial')}
className="mt-3 text-sm text-primary hover:underline" className="mt-3 text-sm text-primary hover:underline"
> >
Retry Retry
@@ -210,8 +215,20 @@ export function AIAccounts({ apiKey }: AIAccountsProps) {
<div className="px-6 py-4 border-b border-border"> <div className="px-6 py-4 border-b border-border">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h3 className="text-lg font-medium text-card-foreground">AI Accounts Status</h3> <h3 className="text-lg font-medium text-card-foreground">AI Accounts Status</h3>
<div className="flex items-center gap-3">
{refreshError && (
<div className="flex items-center gap-2">
<span className="text-sm text-destructive">{refreshError}</span>
<button <button
onClick={() => fetchAccounts(true)} onClick={handleRetry}
className="text-sm text-primary hover:underline"
>
Retry
</button>
</div>
)}
<button
onClick={() => fetchAccounts('manual')}
disabled={isRefreshing} disabled={isRefreshing}
className="flex items-center gap-2 px-3 py-1.5 text-sm bg-secondary text-secondary-foreground rounded-md hover:bg-secondary/80 transition-colors disabled:opacity-50 disabled:cursor-not-allowed" className="flex items-center gap-2 px-3 py-1.5 text-sm bg-secondary text-secondary-foreground rounded-md hover:bg-secondary/80 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
> >
@@ -227,6 +244,7 @@ export function AIAccounts({ apiKey }: AIAccountsProps) {
</button> </button>
</div> </div>
</div> </div>
</div>
{allAccounts.length > 0 ? ( {allAccounts.length > 0 ? (
<div className="overflow-x-auto"> <div className="overflow-x-auto">
@@ -275,27 +293,21 @@ export function AIAccounts({ apiKey }: AIAccountsProps) {
{claudeAcc.claudeUsage.fiveHour && ( {claudeAcc.claudeUsage.fiveHour && (
<UsageProgressBar <UsageProgressBar
label="5h Window" label="5h Window"
resetTime={formatTime(claudeAcc.claudeUsage.fiveHour.remainingSeconds)}
percentage={claudeAcc.claudeUsage.fiveHour.utilization} percentage={claudeAcc.claudeUsage.fiveHour.utilization}
resetAfterSeconds={claudeAcc.claudeUsage.fiveHour.remainingSeconds}
resetAt={claudeAcc.claudeUsage.fiveHour.resetsAt} resetAt={claudeAcc.claudeUsage.fiveHour.resetsAt}
/> />
)} )}
{claudeAcc.claudeUsage.sevenDay && ( {claudeAcc.claudeUsage.sevenDay && (
<UsageProgressBar <UsageProgressBar
label="7d Window" label="7d Window"
resetTime={formatTime(claudeAcc.claudeUsage.sevenDay.remainingSeconds)}
percentage={claudeAcc.claudeUsage.sevenDay.utilization} percentage={claudeAcc.claudeUsage.sevenDay.utilization}
resetAfterSeconds={claudeAcc.claudeUsage.sevenDay.remainingSeconds}
resetAt={claudeAcc.claudeUsage.sevenDay.resetsAt} resetAt={claudeAcc.claudeUsage.sevenDay.resetsAt}
/> />
)} )}
{claudeAcc.claudeUsage.sevenDayOpus && ( {claudeAcc.claudeUsage.sevenDayOpus && (
<UsageProgressBar <UsageProgressBar
label="Opus Window" label="Opus Window"
resetTime={formatTime(claudeAcc.claudeUsage.sevenDayOpus.remainingSeconds)}
percentage={claudeAcc.claudeUsage.sevenDayOpus.utilization} percentage={claudeAcc.claudeUsage.sevenDayOpus.utilization}
resetAfterSeconds={claudeAcc.claudeUsage.sevenDayOpus.remainingSeconds}
resetAt={claudeAcc.claudeUsage.sevenDayOpus.resetsAt} resetAt={claudeAcc.claudeUsage.sevenDayOpus.resetsAt}
/> />
)} )}
@@ -305,18 +317,14 @@ export function AIAccounts({ apiKey }: AIAccountsProps) {
{openaiAcc.codexUsage.primary && ( {openaiAcc.codexUsage.primary && (
<UsageProgressBar <UsageProgressBar
label="5h Window" label="5h Window"
resetTime={formatTime(openaiAcc.codexUsage.primary.resetAfterSeconds)}
percentage={openaiAcc.codexUsage.primary.usedPercent} percentage={openaiAcc.codexUsage.primary.usedPercent}
resetAfterSeconds={openaiAcc.codexUsage.primary.resetAfterSeconds}
resetAt={openaiAcc.codexUsage.primary.resetAt} resetAt={openaiAcc.codexUsage.primary.resetAt}
/> />
)} )}
{openaiAcc.codexUsage.secondary && ( {openaiAcc.codexUsage.secondary && (
<UsageProgressBar <UsageProgressBar
label="7d Window" label="7d Window"
resetTime={formatTime(openaiAcc.codexUsage.secondary.resetAfterSeconds)}
percentage={openaiAcc.codexUsage.secondary.usedPercent} percentage={openaiAcc.codexUsage.secondary.usedPercent}
resetAfterSeconds={openaiAcc.codexUsage.secondary.resetAfterSeconds}
resetAt={openaiAcc.codexUsage.secondary.resetAt} resetAt={openaiAcc.codexUsage.secondary.resetAt}
/> />
)} )}

View File

@@ -1,18 +1,65 @@
import { useState, useEffect } from 'react';
interface UsageProgressBarProps { interface UsageProgressBarProps {
label: string; label: string;
resetTime: string;
percentage: number; percentage: number;
resetAfterSeconds?: number; resetAt: string;
resetAt?: string;
} }
export function UsageProgressBar({ label, resetTime, percentage, resetAfterSeconds, resetAt }: UsageProgressBarProps) { export function UsageProgressBar({ label, percentage, resetAt }: UsageProgressBarProps) {
// Check if reset time has passed const [remainingTime, setRemainingTime] = useState('');
const resetElapsed = const [resetElapsed, setResetElapsed] = useState(false);
resetAfterSeconds !== undefined && (
resetAfterSeconds <= 0 || useEffect(() => {
(resetAt && !isNaN(Date.parse(resetAt)) && Date.now() >= Date.parse(resetAt)) const updateRemainingTime = () => {
); const resetTimestamp = Date.parse(resetAt);
if (isNaN(resetTimestamp)) {
setRemainingTime('--');
setResetElapsed(false);
return;
}
const now = Date.now();
const diffMs = resetTimestamp - now;
if (diffMs <= 0) {
setResetElapsed(true);
setRemainingTime('');
return;
}
setResetElapsed(false);
const totalSeconds = Math.floor(diffMs / 1000);
const days = Math.floor(totalSeconds / 86400);
const hours = Math.floor((totalSeconds % 86400) / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = totalSeconds % 60;
if (days > 0) {
if (hours > 0) {
setRemainingTime(`${days}d ${hours}h`);
} else {
setRemainingTime(`${days}d`);
}
} else if (hours > 0) {
setRemainingTime(`${hours}h ${minutes}m`);
} else if (minutes > 0) {
setRemainingTime(`${minutes}m ${seconds}s`);
} else {
setRemainingTime(`${seconds}s`);
}
};
// Initial update
updateRemainingTime();
// Update every second
const interval = setInterval(updateRemainingTime, 1000);
return () => clearInterval(interval);
}, [resetAt]);
// If reset time has passed, show 0% // If reset time has passed, show 0%
const displayPercentage = resetElapsed ? 0 : percentage; const displayPercentage = resetElapsed ? 0 : percentage;
@@ -20,7 +67,7 @@ export function UsageProgressBar({ label, resetTime, percentage, resetAfterSecon
return ( return (
<div> <div>
<div className="text-xs text-muted-foreground mb-1"> <div className="text-xs text-muted-foreground mb-1">
{label}{!resetElapsed && ` · Resets in ${resetTime}`} {label}{!resetElapsed && remainingTime && ` · Resets in ${remainingTime}`}
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="flex-1 h-2 bg-chart-3 rounded-full overflow-hidden"> <div className="flex-1 h-2 bg-chart-3 rounded-full overflow-hidden">