import { useEffect, useRef, useState } from 'react'; import { ChevronDown, Loader2 } from 'lucide-react'; import { apiFetch } from '../api.js'; import { formatQuotaPercent, quotaRemainingPercent, quotaToneClass } from '../app-quota-utils.js'; function requireArray(value, fieldName) { if (!Array.isArray(value)) { throw new Error(`invalid_quota_response:${fieldName}`); } return value; } function normalizeQuotaAccounts(result) { return requireArray(result?.accounts, 'accounts').map((account) => ({ ...account, windows: requireArray(account?.windows, 'account.windows') })); } export function DrawerQuota() { const [expanded, setExpanded] = useState(false); const [loading, setLoading] = useState(false); const [loaded, setLoaded] = useState(false); const [error, setError] = useState(''); const [retryAfter, setRetryAfter] = useState(0); const [accounts, setAccounts] = useState([]); const mountedRef = useRef(true); const requestRef = useRef(null); const retryTimerRef = useRef(null); const clearRetryTimer = () => { if (retryTimerRef.current) { window.clearInterval(retryTimerRef.current); retryTimerRef.current = null; } }; const startRetryCooldown = (value) => { const seconds = Math.max(0, Math.ceil(Number(value || 0))); clearRetryTimer(); setRetryAfter(seconds); if (!seconds) { return; } retryTimerRef.current = window.setInterval(() => { setRetryAfter((current) => { if (current <= 1) { clearRetryTimer(); return 0; } return current - 1; }); }, 1000); }; useEffect(() => { return () => { mountedRef.current = false; requestRef.current?.abort(); clearRetryTimer(); }; }, []); const refresh = async (event) => { event?.preventDefault(); event?.stopPropagation(); if (loading || retryAfter > 0) { return; } setExpanded(true); setLoading(true); setError(''); const controller = new AbortController(); requestRef.current = controller; try { const result = await apiFetch('/api/quotas/codex', { signal: controller.signal }); if (!mountedRef.current || requestRef.current !== controller) { return; } startRetryCooldown(0); setAccounts(normalizeQuotaAccounts(result)); setLoaded(true); } catch (error) { if (error.name === 'AbortError' || !mountedRef.current || requestRef.current !== controller) { return; } const retrySeconds = Number(error.retryAfter || 0); if (error.status === 429 || retrySeconds > 0) { startRetryCooldown(retrySeconds); setError(retrySeconds ? `请求过快,请 ${retrySeconds} 秒后重试` : '请求过快,请稍后重试'); } else { setError('查询失败,点击刷新重试'); } setLoaded(true); } finally { if (mountedRef.current && requestRef.current === controller) { requestRef.current = null; setLoading(false); } } }; return (
{expanded ? (
{error ? ( ) : null} {!error && accounts.length ? accounts.map((account) => ( )) : null} {!loading && !error && loaded && !accounts.length ? (
暂无 Codex 凭证
) : null}
) : null}
); } function QuotaAccount({ account, onRefresh }) { const windows = account.windows; const accountStatus = account.status || 'ok'; const plan = account.plan || 'Codex'; return (
{account.label || 'Codex'} {plan}
{accountStatus === 'ok' && windows.length ? (
{windows.map((quotaWindow) => { const percent = quotaRemainingPercent(quotaWindow); return (
{quotaWindow.label} {formatQuotaPercent(quotaWindow)}
); })}
) : ( )}
); }