"use client"; import { useState, useEffect, useCallback } from "react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Users, MessageSquare, DollarSign, Activity } from "lucide-react"; import { useCollection } from "@/hooks/useCollection"; import { LibreChatUser, LibreChatConversation, LibreChatTransaction, LibreChatBalance, } from "@/lib/types"; interface UsageStats { totalUsers: number; activeUsers: number; totalConversations: number; totalMessages: number; totalTokensConsumed: number; totalCreditsUsed: number; averageTokensPerUser: number; topUsers: Array<{ userId: string; userName: string; conversations: number; tokens: number; credits: number; }>; } export function UsageAnalytics() { const [stats, setStats] = useState(null); const [loading, setLoading] = useState(true); const { data: users = [] } = useCollection("users", { limit: 1000 }); const { data: conversations = [] } = useCollection("conversations", { limit: 1000 }); const { data: transactions = [] } = useCollection("transactions", { limit: 1000 }); const { data: balances = [] } = useCollection("balances", { limit: 1000 }); const calculateStats = useCallback(() => { if (!users.length) { return; } setLoading(true); // Console log pour débugger les données balances console.log("=== DONNÉES BALANCES RÉCUPÉRÉES ==="); console.log("Nombre total d'entrées balances:", balances.length); console.log("Toutes les entrées balances:", balances); // NOUVEAU : Console log pour débugger les utilisateurs console.log("=== DONNÉES UTILISATEURS ==="); console.log("Nombre total d'utilisateurs:", users.length); console.log("Premiers 5 utilisateurs:", users.slice(0, 5)); // Analyser les doublons const userCounts = new Map(); balances.forEach(balance => { const userId = balance.user; userCounts.set(userId, (userCounts.get(userId) || 0) + 1); }); const duplicateUsers = Array.from(userCounts.entries()).filter(([_, count]) => count > 1); console.log("Utilisateurs avec plusieurs entrées:", duplicateUsers); // Afficher quelques exemples d'entrées console.log("Premières 5 entrées:", balances.slice(0, 5)); // Calculer le total brut (avec doublons) const totalBrut = balances.reduce((sum, balance) => sum + (balance.tokenCredits || 0), 0); console.log("Total brut (avec doublons potentiels):", totalBrut); // NOUVEAU : Identifier les utilisateurs fantômes console.log("=== ANALYSE DES UTILISATEURS FANTÔMES ==="); const userIds = new Set(users.map(user => user._id)); const balanceUserIds = balances.map(balance => balance.user); const phantomUsers = balanceUserIds.filter(userId => !userIds.has(userId)); const uniquePhantomUsers = [...new Set(phantomUsers)]; console.log("Utilisateurs fantômes (ont des balances mais n'existent plus):", uniquePhantomUsers); console.log("Nombre d'utilisateurs fantômes:", uniquePhantomUsers.length); // Calculer les crédits des utilisateurs fantômes const phantomCredits = balances .filter(balance => uniquePhantomUsers.includes(balance.user)) .reduce((sum, balance) => sum + (balance.tokenCredits || 0), 0); console.log("Crédits des utilisateurs fantômes:", phantomCredits); console.log("Crédits des vrais utilisateurs:", totalBrut - phantomCredits); // Calculer les utilisateurs actifs (5 dernières minutes) const fiveMinutesAgo = new Date(); fiveMinutesAgo.setMinutes(fiveMinutesAgo.getMinutes() - 5); const activeUsers = users.filter((user) => { const lastActivity = new Date(user.updatedAt || user.createdAt); return lastActivity >= fiveMinutesAgo; }).length; // CORRECTION : Créer une map des crédits par utilisateur en évitant les doublons const creditsMap = new Map(); // Grouper les balances par utilisateur const balancesByUser = new Map(); balances.forEach((balance) => { const userId = balance.user; if (!balancesByUser.has(userId)) { balancesByUser.set(userId, []); } balancesByUser.get(userId)!.push(balance); }); // Pour chaque utilisateur, prendre seulement la dernière entrée balancesByUser.forEach((userBalances, userId) => { if (userBalances.length > 0) { // Trier par date de mise à jour (plus récent en premier) const sortedBalances = userBalances.sort((a, b) => { const aDate = new Date((a.updatedAt as string) || (a.createdAt as string) || 0); const bDate = new Date((b.updatedAt as string) || (b.createdAt as string) || 0); return bDate.getTime() - aDate.getTime(); }); // Prendre la plus récente const latestBalance = sortedBalances[0]; creditsMap.set(userId, latestBalance.tokenCredits || 0); } }); // Initialiser les stats par utilisateur const userStats = new Map< string, { userName: string; conversations: number; tokens: number; credits: number; } >(); users.forEach((user) => { userStats.set(user._id, { userName: user.name || user.email || "Utilisateur inconnu", conversations: 0, tokens: 0, credits: creditsMap.get(user._id) || 0, }); }); // Calculer les conversations par utilisateur conversations.forEach((conv) => { const userStat = userStats.get(conv.user); if (userStat) { userStat.conversations++; } }); // Calculer les tokens par utilisateur depuis les transactions let totalTokensConsumed = 0; transactions.forEach((transaction) => { const userStat = userStats.get(transaction.user); if (userStat && transaction.rawAmount) { const tokens = Math.abs(Number(transaction.rawAmount) || 0); userStat.tokens += tokens; totalTokensConsumed += tokens; } }); // CORRECTION : Calculer le total des crédits depuis la map corrigée const totalCreditsUsed = Array.from(creditsMap.values()).reduce( (sum, credits) => sum + credits, 0 ); // Tous les utilisateurs triés par tokens puis conversations const allUsers = Array.from(userStats.entries()) .map(([userId, stats]) => ({ userId, ...stats, })) .sort((a, b) => { // Trier d'abord par tokens, puis par conversations si tokens égaux if (b.tokens !== a.tokens) { return b.tokens - a.tokens; } return b.conversations - a.conversations; }); const totalMessages = conversations.reduce( (sum, conv) => sum + (Array.isArray(conv.messages) ? conv.messages.length : 0), 0 ); setStats({ totalUsers: users.length, activeUsers, totalConversations: conversations.length, totalMessages, totalTokensConsumed, totalCreditsUsed, averageTokensPerUser: users.length > 0 ? totalTokensConsumed / users.length : 0, topUsers: allUsers, // Afficher tous les utilisateurs }); setLoading(false); }, [users, conversations, transactions, balances]); useEffect(() => { calculateStats(); }, [calculateStats]); if (loading || !stats) { return (
{Array.from({ length: 4 }).map((_, i) => (
))}
); } return (
Utilisateurs totaux
{stats.totalUsers}

{stats.activeUsers} actifs cette semaine

Conversations
{stats.totalConversations}

{stats.totalMessages} messages au total

Tokens consommés
{stats.totalTokensConsumed.toLocaleString()}

{Math.round(stats.averageTokensPerUser)} par utilisateur

Crédits totaux
{stats.totalCreditsUsed.toLocaleString()}

crédits disponibles

Tous les utilisateurs
{stats.topUsers.map((user, index) => (
#{index + 1}

{user.userName}

{user.conversations} conversations

{user.tokens.toLocaleString()} tokens

{user.credits.toLocaleString()} crédits

))}
); }