"use client"; import { useState, useEffect, useCallback, useRef } from "react"; import Image from "next/image"; import ProviderLimitCard from "./ProviderLimitCard"; import QuotaTable from "./QuotaTable"; import { parseQuotaData, calculatePercentage } from "./utils"; import Card from "@/shared/components/Card"; import Button from "@/shared/components/Button"; import { CardSkeleton } from "@/shared/components/Loading"; import { USAGE_SUPPORTED_PROVIDERS } from "@/shared/constants/providers"; const REFRESH_INTERVAL_MS = 60000; // 60 seconds export default function ProviderLimits() { const [connections, setConnections] = useState([]); const [quotaData, setQuotaData] = useState({}); const [loading, setLoading] = useState({}); const [errors, setErrors] = useState({}); const [autoRefresh, setAutoRefresh] = useState(true); const [lastUpdated, setLastUpdated] = useState(null); const [refreshingAll, setRefreshingAll] = useState(false); const [countdown, setCountdown] = useState(60); const [initialLoading, setInitialLoading] = useState(true); const intervalRef = useRef(null); const countdownRef = useRef(null); // Fetch all provider connections const fetchConnections = useCallback(async () => { try { const response = await fetch("/api/providers/client"); if (!response.ok) throw new Error("Failed to fetch connections"); const data = await response.json(); const connectionList = data.connections || []; setConnections(connectionList); return connectionList; } catch (error) { console.error("Error fetching connections:", error); setConnections([]); return []; } }, []); // Fetch quota for a specific connection const fetchQuota = useCallback(async (connectionId, provider) => { setLoading((prev) => ({ ...prev, [connectionId]: true })); setErrors((prev) => ({ ...prev, [connectionId]: null })); try { console.log(`[ProviderLimits] Fetching quota for ${provider} (${connectionId})`); const response = await fetch(`/api/usage/${connectionId}`); if (!response.ok) { const errorData = await response.json().catch(() => ({})); const errorMsg = errorData.error || response.statusText; // Handle different error types gracefully if (response.status === 404) { // Connection not found - skip silently console.warn(`[ProviderLimits] Connection not found for ${provider}, skipping`); return; } if (response.status === 401) { // Auth error - show message instead of throwing console.warn(`[ProviderLimits] Auth error for ${provider}:`, errorMsg); setQuotaData((prev) => ({ ...prev, [connectionId]: { quotas: [], message: errorMsg, }, })); return; } throw new Error(`HTTP ${response.status}: ${errorMsg}`); } const data = await response.json(); console.log(`[ProviderLimits] Got quota for ${provider}:`, data); // Parse quota data using provider-specific parser const parsedQuotas = parseQuotaData(provider, data); setQuotaData((prev) => ({ ...prev, [connectionId]: { quotas: parsedQuotas, plan: data.plan || null, message: data.message || null, raw: data, }, })); } catch (error) { console.error(`[ProviderLimits] Error fetching quota for ${provider} (${connectionId}):`, error); setErrors((prev) => ({ ...prev, [connectionId]: error.message || "Failed to fetch quota", })); } finally { setLoading((prev) => ({ ...prev, [connectionId]: false })); } }, []); // Refresh quota for a specific provider const refreshProvider = useCallback( async (connectionId, provider) => { await fetchQuota(connectionId, provider); setLastUpdated(new Date()); }, [fetchQuota] ); // Refresh all providers const refreshAll = useCallback(async () => { if (refreshingAll) return; setRefreshingAll(true); setCountdown(60); try { const conns = await fetchConnections(); // Filter only supported OAuth providers const oauthConnections = conns.filter( (conn) => USAGE_SUPPORTED_PROVIDERS.includes(conn.provider) && conn.authType === "oauth" ); // Fetch quota for supported OAuth connections only await Promise.all( oauthConnections.map((conn) => fetchQuota(conn.id, conn.provider)) ); setLastUpdated(new Date()); } catch (error) { console.error("Error refreshing all providers:", error); } finally { setRefreshingAll(false); } }, [refreshingAll, fetchConnections, fetchQuota]); // Initial load useEffect(() => { const initializeData = async () => { setInitialLoading(true); await refreshAll(); setInitialLoading(false); }; initializeData(); }, []); // eslint-disable-line react-hooks/exhaustive-deps // Auto-refresh interval useEffect(() => { if (!autoRefresh) { if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = null; } if (countdownRef.current) { clearInterval(countdownRef.current); countdownRef.current = null; } return; } // Main refresh interval intervalRef.current = setInterval(() => { refreshAll(); }, REFRESH_INTERVAL_MS); // Countdown interval countdownRef.current = setInterval(() => { setCountdown((prev) => { if (prev <= 1) return 60; return prev - 1; }); }, 1000); return () => { if (intervalRef.current) clearInterval(intervalRef.current); if (countdownRef.current) clearInterval(countdownRef.current); }; }, [autoRefresh, refreshAll]); // Pause auto-refresh when tab is hidden (Page Visibility API) useEffect(() => { const handleVisibilityChange = () => { if (document.hidden) { if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = null; } if (countdownRef.current) { clearInterval(countdownRef.current); countdownRef.current = null; } } else if (autoRefresh) { // Resume auto-refresh when tab becomes visible intervalRef.current = setInterval(refreshAll, REFRESH_INTERVAL_MS); countdownRef.current = setInterval(() => { setCountdown((prev) => (prev <= 1 ? 60 : prev - 1)); }, 1000); } }; document.addEventListener("visibilitychange", handleVisibilityChange); return () => { document.removeEventListener("visibilitychange", handleVisibilityChange); }; }, [autoRefresh, refreshAll]); // Format last updated time const formatLastUpdated = useCallback(() => { if (!lastUpdated) return "Never"; const now = new Date(); const diffMs = now - lastUpdated; const diffMinutes = Math.floor(diffMs / (1000 * 60)); const diffHours = Math.floor(diffMinutes / 60); const diffDays = Math.floor(diffHours / 24); if (diffDays > 0) return `${diffDays}d ago`; if (diffHours > 0) return `${diffHours}h ago`; if (diffMinutes > 0) return `${diffMinutes}m ago`; return "Just now"; }, [lastUpdated]); // Filter only supported providers const filteredConnections = connections.filter((conn) => USAGE_SUPPORTED_PROVIDERS.includes(conn.provider) && conn.authType === "oauth" ); // Sort providers: antigravity first, then kiro, then others alphabetically const sortedConnections = [...filteredConnections].sort((a, b) => { const getProviderPriority = (provider) => { if (provider === "antigravity") return 1; if (provider === "kiro") return 2; return 3; }; const priorityA = getProviderPriority(a.provider); const priorityB = getProviderPriority(b.provider); if (priorityA !== priorityB) { return priorityA - priorityB; } // Same priority: sort alphabetically return a.provider.localeCompare(b.provider); }); // Calculate summary stats const totalProviders = sortedConnections.length; const activeWithLimits = Object.values(quotaData).filter( (data) => data?.quotas?.length > 0 ).length; // Count low quotas (remaining < 30%) const lowQuotasCount = Object.values(quotaData).reduce((count, data) => { if (!data?.quotas) return count; const hasLowQuota = data.quotas.some((quota) => { const percentage = calculatePercentage(quota.used, quota.total); return percentage < 30 && quota.total > 0; }); return count + (hasLowQuota ? 1 : 0); }, 0); // Initial loading state if (initialLoading) { return (
); } // Empty state if (sortedConnections.length === 0) { return (
cloud_off

No Providers Connected

Connect to providers with OAuth to track your API quota limits and usage.

); } return (
{/* Header Controls */}

Provider Limits

Last updated: {formatLastUpdated()}
{/* Auto-refresh toggle */} {/* Refresh all button */}
{/* Provider Cards Grid */}
{sortedConnections.map((conn) => { const quota = quotaData[conn.id]; const isLoading = loading[conn.id]; const error = errors[conn.id]; // Use table layout for all providers return (
{conn.provider}

{conn.provider}

{conn.name && (

{conn.name}

)}
{isLoading ? (
progress_activity
) : error ? (
error

{error}

) : quota?.message ? (

{quota.message}

) : ( )}
); })}
); }