"use client"; import { useCallback, useEffect, useMemo, useState } from "react"; import { ChevronDown } from "lucide-react"; import { useAuth } from "@/auth/clerk"; import type { ProviderCredentialRead, ProviderUsageLiveRead, SubscriptionWindowRead, } from "@/api/generated/model"; import { getProviderUsageLiveApiV1ProviderCredentialsCredentialIdUsageGet, listProviderCredentialsApiV1ProviderCredentialsGet, } from "@/api/generated/provider-credentials/provider-credentials"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; type NavbarProviderId = "anthropic" | "openai"; type ProviderNavbarItem = { providerId: NavbarProviderId; label: string; status: "Active" | "Inactive" | "Not configured" | "Syncing"; sessionRemaining: number | null; modelsRemaining: number | null; reset: string; }; const NAVBAR_PROVIDER_IDS: NavbarProviderId[] = ["anthropic", "openai"]; function providerShortLabel(provider: string): string { if (provider === "anthropic") return "Claude"; if (provider === "openai") return "GPT"; return provider; } function clampPct(pct: number): number { return Math.max(0, Math.min(100, pct)); } function fmtResetMs(ms: number | null | undefined): string { if (ms == null || ms <= 0) return "< 1m"; const s = Math.floor(ms / 1000); if (s < 60) return `${s}s`; const m = Math.floor(s / 60); if (m < 60) return `${m}m ${s % 60}s`; const h = Math.floor(m / 60); return `${h}h ${m % 60}m`; } function remainingPct(window: SubscriptionWindowRead | null): number | null { if (!window) return null; return clampPct(100 - window.pct_used); } function remainingColor(pct: number | null): string { if (pct == null) return "bg-[color:var(--text-quiet)]"; if (pct <= 10) return "bg-[color:var(--danger)]"; if (pct <= 25) return "bg-[color:var(--warning)]"; return "bg-[color:var(--success)]"; } function findSessionWindow(usage: ProviderUsageLiveRead | null | undefined) { const windows = usage?.subscription_windows ?? []; return ( windows.find((window) => { const key = `${window.key} ${window.label}`.toLowerCase(); return ( key.includes("current") || key.includes("session") || key.includes("five") || key.includes("hour") ); }) ?? windows[0] ?? null ); } function findModelsWindow( usage: ProviderUsageLiveRead | null | undefined, sessionWindow: SubscriptionWindowRead | null, ) { const windows = usage?.subscription_windows ?? []; return ( windows.find((window) => { if (window.key === sessionWindow?.key) return false; const key = `${window.key} ${window.label}`.toLowerCase(); return key.includes("all") || key.includes("model") || key.includes("week"); }) ?? windows.find((window) => window.key !== sessionWindow?.key) ?? null ); } function resetLabel( sessionWindow: SubscriptionWindowRead | null, modelsWindow: SubscriptionWindowRead | null, ): string { const resetInMs = sessionWindow?.reset_in_ms ?? modelsWindow?.reset_in_ms; return resetInMs == null ? "-" : fmtResetMs(resetInMs); } function formatRemaining(pct: number | null): string { return pct == null ? "-" : `${Math.round(pct)}% left`; } function providerStatusColor(status: ProviderNavbarItem["status"]): string { if (status === "Active") return "bg-[color:var(--success)]"; if (status === "Syncing") return "bg-[color:var(--accent)]"; if (status === "Inactive") return "bg-[color:var(--warning)]"; return "bg-[color:var(--text-quiet)]"; } function MiniRemainingBar({ pct, className = "w-10", }: { pct: number | null; className?: string; }) { if (pct == null) return null; const width = clampPct(pct); return ( ); } function buildProviderNavbarItems({ credentials, usageByCredentialId, isLoading, }: { credentials: ProviderCredentialRead[]; usageByCredentialId: Record; isLoading: boolean; }): ProviderNavbarItem[] { return NAVBAR_PROVIDER_IDS.map((providerId) => { const providerCredentials = credentials .filter((cred) => cred.provider === providerId) .sort((a, b) => a.account_key.localeCompare(b.account_key)); const activeCredential = providerCredentials.find( (cred) => cred.active && (cred.has_api_key || cred.has_session_key || Boolean(cred.base_url)), ) ?? null; const credential = activeCredential ?? providerCredentials[0] ?? null; const usage = activeCredential ? usageByCredentialId[activeCredential.id] : null; const status: ProviderNavbarItem["status"] = activeCredential ? isLoading && usage == null ? "Syncing" : "Active" : credential ? "Inactive" : "Not configured"; const sessionWindow = findSessionWindow(usage); const modelsWindow = findModelsWindow(usage, sessionWindow); const sessionRemaining = remainingPct(sessionWindow); const modelsRemaining = remainingPct(modelsWindow); return { providerId, label: providerShortLabel(providerId), status, sessionRemaining, modelsRemaining, reset: resetLabel(sessionWindow, modelsWindow), }; }); } function ProviderInlineStatus({ item, compact = false, }: { item: ProviderNavbarItem; compact?: boolean; }) { return ( {item.label} {item.status} | Session{" "} {formatRemaining(item.sessionRemaining)} | Models{" "} {formatRemaining(item.modelsRemaining)} | Reset {item.reset} ); } export function ProviderNavbarStatus() { const { isSignedIn } = useAuth(); const [credentials, setCredentials] = useState([]); const [usageByCredentialId, setUsageByCredentialId] = useState< Record >({}); const [isUsageLoading, setIsUsageLoading] = useState(false); const usageCredentials = useMemo(() => { return credentials .filter( (cred) => NAVBAR_PROVIDER_IDS.includes(cred.provider as NavbarProviderId) && cred.active && (cred.has_api_key || cred.has_session_key || Boolean(cred.base_url)), ) .sort((a, b) => { const providerDelta = NAVBAR_PROVIDER_IDS.indexOf(a.provider as NavbarProviderId) - NAVBAR_PROVIDER_IDS.indexOf(b.provider as NavbarProviderId); if (providerDelta !== 0) return providerDelta; return a.account_key.localeCompare(b.account_key); }); }, [credentials]); const loadCredentials = useCallback(async () => { try { const res = await listProviderCredentialsApiV1ProviderCredentialsGet(); if (res.status === 200) setCredentials(res.data); } catch { setCredentials([]); } }, []); useEffect(() => { if (!isSignedIn) return; const initialLoad = window.setTimeout(() => { void loadCredentials(); }, 0); window.addEventListener("provider-credentials-updated", loadCredentials); const interval = window.setInterval(() => { void loadCredentials(); }, 60_000); return () => { window.clearTimeout(initialLoad); window.removeEventListener("provider-credentials-updated", loadCredentials); window.clearInterval(interval); }; }, [isSignedIn, loadCredentials]); useEffect(() => { if (!isSignedIn || usageCredentials.length === 0) { const resetUsage = window.setTimeout(() => { setUsageByCredentialId({}); setIsUsageLoading(false); }, 0); return () => window.clearTimeout(resetUsage); } let cancelled = false; const fetchUsage = async (refresh = false) => { setIsUsageLoading(true); const pairs = await Promise.all( usageCredentials.map(async (cred) => { try { const res = await getProviderUsageLiveApiV1ProviderCredentialsCredentialIdUsageGet( cred.id, refresh ? { refresh: true } : undefined, ); return [cred.id, res.status === 200 ? res.data : null] as const; } catch { return [cred.id, null] as const; } }), ); if (!cancelled) { setUsageByCredentialId(Object.fromEntries(pairs)); setIsUsageLoading(false); } }; const initialFetch = window.setTimeout(() => { void fetchUsage(); }, 0); const interval = window.setInterval(() => { void fetchUsage(true); }, 60_000); return () => { cancelled = true; window.clearTimeout(initialFetch); window.clearInterval(interval); }; }, [isSignedIn, usageCredentials]); const items = buildProviderNavbarItems({ credentials, usageByCredentialId, isLoading: isUsageLoading, }); const activeCount = items.filter( (item) => item.status === "Active" || item.status === "Syncing", ).length; const primaryRemaining = items .flatMap((item) => [item.sessionRemaining, item.modelsRemaining]) .filter((pct): pct is number => pct != null) .sort((a, b) => a - b)[0] ?? null; return ( {items.map((item, index) => ( {index > 0 ? ( ) : null} ))} 0 ? "bg-[color:var(--success)]" : "bg-[color:var(--text-quiet)]" }`} /> Providers {activeCount} active {formatRemaining(primaryRemaining)} {items.map((item) => ( ))} ); }