diff --git a/ui/bun.lock b/ui/bun.lock index c2e29de1..2756da98 100644 --- a/ui/bun.lock +++ b/ui/bun.lock @@ -1,6 +1,5 @@ { "lockfileVersion": 1, - "configVersion": 0, "workspaces": { "": { "name": "ui", diff --git a/ui/src/components/account/flow-viz/account-card.tsx b/ui/src/components/account/flow-viz/account-card.tsx index 110d6f8a..c5773214 100644 --- a/ui/src/components/account/flow-viz/account-card.tsx +++ b/ui/src/components/account/flow-viz/account-card.tsx @@ -9,11 +9,16 @@ import { getMinClaudeQuota, getModelsWithTiers, groupModelsByTier, + getMinCodexQuota, + getMinGeminiQuota, + getCodexResetTime, + getGeminiResetTime, type ModelTier, } from '@/lib/utils'; import { PRIVACY_BLUR_CLASS } from '@/contexts/privacy-context'; import { GripVertical, Loader2, Clock, Pause, Play } from 'lucide-react'; import { useAccountQuota } from '@/hooks/use-cliproxy-stats'; +import type { CodexQuotaResult, GeminiCliQuotaResult, QuotaResult } from '@/lib/api-client'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import { Button } from '@/components/ui/button'; @@ -89,19 +94,36 @@ export function AccountCard({ const borderColor = getBorderColorStyle(zone, account.color); const connectorPosition = CONNECTOR_POSITION_MAP[zone]; - // Quota for AGY accounts - const isAgy = account.provider === 'agy'; + // Quota for CLIProxy accounts (agy, codex, gemini) + const isCliproxyProvider = ['agy', 'codex', 'gemini'].includes(account.provider); const { data: quota, isLoading: quotaLoading } = useAccountQuota( account.provider, account.id, - isAgy + isCliproxyProvider ); - // Show minimum quota of Claude models (primary), fallback to min of all models - const minQuota = quota?.success ? getMinClaudeQuota(quota.models) : null; + + // Get provider-specific minimum quota + const getProviderMinQuota = () => { + if (!quota?.success) return null; + switch (account.provider) { + case 'agy': + return getMinClaudeQuota((quota as QuotaResult).models); + case 'codex': + return getMinCodexQuota((quota as CodexQuotaResult).windows); + case 'gemini': + return getMinGeminiQuota((quota as GeminiCliQuotaResult).buckets); + default: + return null; + } + }; + const minQuota = getProviderMinQuota(); // Tier badge (AGY only) - show P for Pro, U for Ultra const showTierBadge = - isAgy && account.tier && account.tier !== 'unknown' && account.tier !== 'free'; + account.provider === 'agy' && + account.tier && + account.tier !== 'unknown' && + account.tier !== 'free'; return (
- {/* Quota bar for AGY accounts */} - {isAgy && ( + {/* Quota bar for CLIProxy accounts (agy, codex, gemini) */} + {isCliproxyProvider && (Model Quotas:
- {(() => { - const tiered = getModelsWithTiers(quota?.models || []); - const groups = groupModelsByTier(tiered); - const tierOrder: ModelTier[] = ['primary', 'gemini-3', 'gemini-2', 'other']; - return tierOrder.map((tier, idx) => { - const models = groups.get(tier); - if (!models || models.length === 0) return null; - const isFirst = tierOrder - .slice(0, idx) - .every((t) => !groups.get(t)?.length); - return ( -Model Quotas:
+ {(() => { + const tiered = getModelsWithTiers((quota as QuotaResult)?.models || []); + const groups = groupModelsByTier(tiered); + const tierOrder: ModelTier[] = ['primary', 'gemini-3', 'gemini-2', 'other']; + return tierOrder.map((tier, idx) => { + const models = groups.get(tier); + if (!models || models.length === 0) return null; + const isFirst = tierOrder + .slice(0, idx) + .every((t) => !groups.get(t)?.length); + return ( +Rate Limits:
+ {(quota as CodexQuotaResult)?.windows?.map((w) => ( +Buckets:
+ {(quota as GeminiCliQuotaResult)?.buckets?.map((b) => ( +Plan: {planType}
} + {windows.map((w) => ( +Model Quotas:
+ {renderAgyTooltip()} + {nextReset && ( +Rate Limit Windows:
+ {renderCodexTooltip()} + {nextReset && ( +Model Buckets:
+ {renderGeminiTooltip()} + {nextReset && ( +Model Quotas:
- {(() => { - const tiered = getModelsWithTiers(quota?.models || []); - const groups = groupModelsByTier(tiered); - const tierOrder: ModelTier[] = ['primary', 'gemini-3', 'gemini-2', 'other']; - return tierOrder.map((tier, idx) => { - const models = groups.get(tier); - if (!models || models.length === 0) return null; - const isFirst = tierOrder - .slice(0, idx) - .every((t) => !groups.get(t)?.length); - return ( -