'use client'; import React, { useState, useEffect } from 'react'; import { ShieldCheck, TrendingUp, Users, AlertTriangle, DollarSign, Activity, Edit2, Save, X, Gauge } from 'lucide-react'; import { cmsApi } from '@/lib/api'; interface TokenUsage { user_id: string; email: string; full_name: string; role: string; total_tokens: number; input_tokens: number; output_tokens: number; ai_requests: number; last_used: string; estimated_cost_usd: number; monthly_token_limit?: number; token_limit_reset_day?: number; } interface TokenStats { total_tokens: number; total_input: number; total_output: number; total_requests: number; total_cost_usd: number; top_user_tokens: number; avg_tokens_per_user: number; } interface UserLimit { user_id: string; monthly_limit: number; used_tokens: number; remaining_tokens: number; percentage_used: number; reset_day: number; } export default function AdminTokenTracking() { const [usage, setUsage] = useState([]); const [stats, setStats] = useState(null); const [loading, setLoading] = useState(true); const [filterRole, setFilterRole] = useState(''); const [sortBy, setSortBy] = useState<'total_tokens' | 'ai_requests' | 'estimated_cost_usd' | 'percentage_used'>('total_tokens'); const [editingLimit, setEditingLimit] = useState(null); const [editValue, setEditValue] = useState(0); const [userLimits, setUserLimits] = useState>({}); useEffect(() => { loadTokenUsage(); }, []); const loadTokenUsage = async () => { try { const token = localStorage.getItem('studio_token'); console.log('[TokenUsage] Token from localStorage:', token ? 'Present (studio_token)' : 'Missing'); if (!token) { console.error('[TokenUsage] No authentication token found!'); alert('No authentication token found. Please login again.'); window.location.href = '/auth/login'; return; } const response = await fetch(`${process.env.NEXT_PUBLIC_CMS_API_URL || 'http://localhost:3001'}/admin/token-usage`, { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', }, }); console.log('[TokenUsage] API Response status:', response.status); if (response.status === 401) { console.error('[TokenUsage] Unauthorized - Token may be expired'); alert('Session expired. Please login again.'); window.location.href = '/auth/login'; return; } if (response.ok) { const data = await response.json(); setUsage(data.usage || []); setStats(data.stats); // Load limits for each user const limits: Record = {}; for (const user of data.usage || []) { try { const limitResp = await fetch( `${process.env.NEXT_PUBLIC_CMS_API_URL || 'http://localhost:3001'}/admin/users/${user.user_id}/token-limit/check`, { headers: { 'Authorization': `Bearer ${localStorage.getItem('studio_token')}`, }, } ); if (limitResp.ok) { const limitData = await limitResp.json(); limits[user.user_id] = { user_id: user.user_id, monthly_limit: limitData.monthly_limit, used_tokens: limitData.used_tokens, remaining_tokens: limitData.remaining_tokens, percentage_used: limitData.monthly_limit > 0 ? Math.round((limitData.used_tokens / limitData.monthly_limit) * 100) : 0, reset_day: 1, }; } } catch (err) { console.error(`Failed to load limit for user ${user.user_id}:`, err); } } setUserLimits(limits); } } catch (error) { console.error('Failed to load token usage:', error); } finally { setLoading(false); } }; const handleUpdateLimit = async (userId: string) => { try { const response = await fetch( `${process.env.NEXT_PUBLIC_CMS_API_URL || 'http://localhost:3001'}/admin/users/${userId}/token-limit`, { method: 'PUT', headers: { 'Authorization': `Bearer ${localStorage.getItem('studio_token')}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ monthly_token_limit: editValue, token_limit_reset_day: 1, }), } ); if (response.ok) { // Reload limits const limitResp = await fetch( `${process.env.NEXT_PUBLIC_CMS_API_URL || 'http://localhost:3001'}/admin/users/${userId}/token-limit/check`, { headers: { 'Authorization': `Bearer ${localStorage.getItem('studio_token')}`, }, } ); if (limitResp.ok) { const limitData = await limitResp.json(); setUserLimits(prev => ({ ...prev, [userId]: { user_id: userId, monthly_limit: limitData.monthly_limit, used_tokens: limitData.used_tokens, remaining_tokens: limitData.remaining_tokens, percentage_used: limitData.monthly_limit > 0 ? Math.round((limitData.used_tokens / limitData.monthly_limit) * 100) : 0, reset_day: 1, }, })); } setEditingLimit(null); } } catch (error) { console.error('Failed to update limit:', error); alert('Failed to update token limit'); } }; const getLimitColor = (percentage: number) => { if (percentage >= 100) return 'text-red-600 bg-red-50 dark:bg-red-900/20'; if (percentage >= 90) return 'text-orange-600 bg-orange-50 dark:bg-orange-900/20'; if (percentage >= 80) return 'text-yellow-600 bg-yellow-50 dark:bg-yellow-900/20'; return 'text-green-600 bg-green-50 dark:bg-green-900/20'; }; const getProgressBarColor = (percentage: number) => { if (percentage >= 100) return 'bg-red-600'; if (percentage >= 90) return 'bg-orange-600'; if (percentage >= 80) return 'bg-yellow-600'; return 'bg-green-600'; }; const filteredUsage = usage .filter(u => !filterRole || u.role === filterRole) .sort((a, b) => { if (sortBy === 'percentage_used') { const aPct = userLimits[a.user_id]?.percentage_used || 0; const bPct = userLimits[b.user_id]?.percentage_used || 0; return bPct - aPct; } return b[sortBy] - a[sortBy]; }); const formatNumber = (num: number) => new Intl.NumberFormat('en-US').format(num); const formatCurrency = (num: number) => new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(num); return (
{/* Header */}

Control Global - Token Usage

Monitoreo de tokens de IA, límites mensuales y costos del sistema

{/* Stats Cards */}
{stats && ( <>

Total Tokens

{formatNumber(stats.total_tokens)}

Input: {formatNumber(stats.total_input)} | Output: {formatNumber(stats.total_output)}

Requests IA

{formatNumber(stats.total_requests)}

Avg: {formatNumber(stats.avg_tokens_per_user)} tokens/user

Costo Estimado

{formatCurrency(stats.total_cost_usd)}

Top user: {formatNumber(stats.top_user_tokens)} tokens

Usuarios Activos

{usage.length}

Monitoreando uso de IA
{/* Alerts */} {Object.values(userLimits).some(ul => ul.percentage_used >= 80) && (

Usuarios cerca del límite

{Object.values(userLimits).filter(ul => ul.percentage_used >= 80 && ul.percentage_used < 100).length} usuario(s) han usado ≥80% de su límite mensual.

)} {Object.values(userLimits).some(ul => ul.percentage_used >= 100) && (

Límite excedido

{Object.values(userLimits).filter(ul => ul.percentage_used >= 100).length} usuario(s) han excedido su límite mensual.

)} {/* Filters */}
{/* Usage Table */}

Uso por Usuario

Límites mensuales configurables
{filteredUsage.map((user) => { const limit = userLimits[user.user_id]; const percentage = limit?.percentage_used || 0; const isUnlimited = limit?.monthly_limit === 0; return ( ); })}
Usuario Rol Límite Mensual % Usado Total Tokens Requests Costo USD
{user.full_name}
{user.email}
{user.role} {editingLimit === user.user_id ? (
setEditValue(parseInt(e.target.value) || 0)} className="w-24 px-2 py-1 border border-gray-300 dark:border-gray-600 rounded text-sm dark:bg-gray-700 dark:text-white" placeholder="0 = unlimited" autoFocus />
) : (
= 100 ? 'text-red-600' : percentage >= 80 ? 'text-yellow-600' : 'text-gray-900 dark:text-white' }`}> {isUnlimited ? '∞' : formatNumber(limit?.monthly_limit || 0)} {!isUnlimited && limit && ( )}
)}
{isUnlimited ? ( Unlimited ) : (
{percentage}%
)}
1000000 ? 'text-red-600' : user.total_tokens > 500000 ? 'text-yellow-600' : 'text-gray-900 dark:text-white' }`}> {formatNumber(user.total_tokens)} {formatNumber(user.ai_requests)} {formatCurrency(user.estimated_cost_usd)}
)} {loading && (

Cargando estadísticas de tokens...

)}
); }