"use client"; import { useState, useEffect, useCallback } from "react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Users } from "lucide-react"; import Link from "next/link"; import { useCollection } from "@/hooks/useCollection"; import { LibreChatUser, LibreChatConversation, LibreChatBalance, LibreChatMessage, } from "@/lib/types"; interface DashboardUser { userId: string; userName: string; conversations: number; tokens: number; credits: number; } // Interfaces pour les collections tokens et toolcalls interface TokenDocument { _id: string; user?: string; userId?: string; amount?: number; tokens?: number; count?: number; } interface ToolcallDocument { _id: string; user?: string; userId?: string; tokens?: number; tokenCount?: number; } const isValidDate = (value: unknown): value is string | number | Date => { if (!value) return false; const date = new Date(value as string | number | Date); return !isNaN(date.getTime()); }; export function DashboardUsersList() { const [topUsers, setTopUsers] = useState([]); const [isLoading, setIsLoading] = useState(true); // Récupérer toutes les données nécessaires const { data: users, loading: usersLoading } = useCollection("users"); const { data: conversations, loading: conversationsLoading } = useCollection("conversations"); const { data: balances, loading: balancesLoading } = useCollection("balances"); const { data: messages, loading: messagesLoading } = useCollection("messages"); const { data: tokens, loading: tokensLoading } = useCollection("tokens"); const { data: toolcalls, loading: toolcallsLoading } = useCollection("toolcalls"); const processUsers = useCallback(() => { if ( !users?.length || !conversations?.length || !balances?.length || !messages?.length ) { return; } console.log("🔄 Processing users data..."); console.log("Users:", users.length); console.log("Conversations:", conversations.length); console.log("Balances:", balances.length); console.log("Messages:", messages.length); console.log("Tokens collection:", tokens?.length || 0); console.log("Toolcalls collection:", toolcalls?.length || 0); const processedUsers: DashboardUser[] = []; users.forEach((user: LibreChatUser) => { // Obtenir les conversations de l'utilisateur const userConversations = conversations.filter( (conv: LibreChatConversation) => conv.user === user._id ); // Obtenir les messages de l'utilisateur const userMessages = messages.filter( (msg: LibreChatMessage) => msg.user === user._id ); // Calculer les tokens depuis les messages const totalTokensFromMessages = userMessages.reduce( (sum: number, msg: LibreChatMessage) => sum + (msg.tokenCount || 0), 0 ); // Calculer les tokens depuis les conversations de l'utilisateur const userConversationIds = userConversations.map( (conv) => conv.conversationId ); const conversationMessages = messages.filter((msg: LibreChatMessage) => userConversationIds.includes(msg.conversationId) ); const totalTokensFromConversations = conversationMessages.reduce( (sum: number, msg: LibreChatMessage) => sum + (msg.tokenCount || 0), 0 ); // Vérifier les collections tokens et toolcalls let tokensFromTokensCollection = 0; let tokensFromToolcalls = 0; if (tokens?.length) { const userTokens = tokens.filter( (token: TokenDocument) => token.user === user._id || token.userId === user._id ); tokensFromTokensCollection = userTokens.reduce( (sum: number, token: TokenDocument) => { return sum + (token.amount || token.tokens || token.count || 0); }, 0 ); } if (toolcalls?.length) { const userToolcalls = toolcalls.filter( (toolcall: ToolcallDocument) => toolcall.user === user._id || toolcall.userId === user._id ); tokensFromToolcalls = userToolcalls.reduce( (sum: number, toolcall: ToolcallDocument) => { return sum + (toolcall.tokens || toolcall.tokenCount || 0); }, 0 ); } // Obtenir les balances de l'utilisateur const userBalances = balances.filter( (balance: LibreChatBalance) => balance.user === user._id ); // Trier par date de mise à jour (plus récent en premier) const sortedBalances = userBalances.sort((a, b) => { const dateA = a.updatedAt || a.createdAt; const dateB = b.updatedAt || b.createdAt; // Vérifier que les dates sont valides avant de les comparer if (isValidDate(dateA) && isValidDate(dateB)) { return ( new Date(dateB as string | number | Date).getTime() - new Date(dateA as string | number | Date).getTime() ); } // Si seulement une date existe et est valide, la privilégier if (isValidDate(dateA) && !isValidDate(dateB)) return -1; if (!isValidDate(dateA) && isValidDate(dateB)) return 1; // Si aucune date n'existe ou n'est valide, garder l'ordre actuel return 0; }); // Prendre la balance la plus récente const latestBalance = sortedBalances[0]; const credits = latestBalance ? latestBalance.tokenCredits || 0 : 0; // Calculer les tokens consommés depuis les crédits const INITIAL_CREDITS = 3000000; const creditsUsed = INITIAL_CREDITS - credits; const tokensFromCredits = creditsUsed > 0 ? creditsUsed : 0; // Prendre la valeur la plus élevée (plus précise) const totalTokens = Math.max( totalTokensFromMessages, totalTokensFromConversations, tokensFromTokensCollection, tokensFromToolcalls, tokensFromCredits ); // Log de débogage très détaillé console.log(`👤 User ${user.name || user.email}:`, { conversations: userConversations.length, userMessages: userMessages.length, conversationMessages: conversationMessages.length, tokensFromMessages: totalTokensFromMessages, tokensFromConversations: totalTokensFromConversations, tokensFromTokensCollection: tokensFromTokensCollection, tokensFromToolcalls: tokensFromToolcalls, currentCredits: credits, creditsUsed: creditsUsed, tokensFromCredits: tokensFromCredits, finalTokens: totalTokens, messagesSample: userMessages.slice(0, 2).map((m) => ({ tokenCount: m.tokenCount, model: m.model, isCreatedByUser: m.isCreatedByUser, conversationId: m.conversationId, })), conversationsSample: userConversations.slice(0, 2).map((c) => ({ conversationId: c.conversationId, messagesCount: c.messages?.length || 0, })), }); // Ajouter l'utilisateur seulement s'il a des données significatives if (userConversations.length > 0 || totalTokens > 0 || credits > 0) { processedUsers.push({ userId: user._id, userName: user.name || user.username || user.email || "Utilisateur inconnu", conversations: userConversations.length, tokens: totalTokens, credits: credits, }); } }); // Trier par tokens consommés (décroissant) et prendre les 5 premiers const sortedUsers = processedUsers .sort((a, b) => b.tokens - a.tokens) .slice(0, 5); console.log("✅ Top 5 users processed:", sortedUsers); setTopUsers(sortedUsers); setIsLoading(false); }, [users, conversations, balances, messages, tokens, toolcalls]); useEffect(() => { const allDataLoaded = !usersLoading && !conversationsLoading && !balancesLoading && !messagesLoading && !tokensLoading && !toolcallsLoading; if (allDataLoaded) { processUsers(); } else { setIsLoading(true); } }, [ usersLoading, conversationsLoading, balancesLoading, messagesLoading, tokensLoading, toolcallsLoading, processUsers, ]); if (isLoading) { return ( Top 5 utilisateurs
{[...Array(5)].map((_, i) => (
))}
); } if (topUsers.length === 0) { return ( Top 5 utilisateurs
Aucun utilisateur trouvé
); } return ( Top 5 utilisateurs
{topUsers.map((user, index) => (
{index + 1}

{user.userName}

{user.conversations} conversation {user.conversations !== 1 ? "s" : ""}

{user.tokens.toLocaleString()} tokens

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

))}
); }