From 414253aff0ebbe23b69146d4d2fd955d07178dd9 Mon Sep 17 00:00:00 2001 From: Biqoz Date: Thu, 27 Nov 2025 15:12:57 +0100 Subject: [PATCH] calcul accurate --- components/dashboard/dashboard-users-list.tsx | 226 ++++-------------- debug-tokens.js | 38 +++ 2 files changed, 80 insertions(+), 184 deletions(-) create mode 100644 debug-tokens.js diff --git a/components/dashboard/dashboard-users-list.tsx b/components/dashboard/dashboard-users-list.tsx index 443fc3a..694ed71 100644 --- a/components/dashboard/dashboard-users-list.tsx +++ b/components/dashboard/dashboard-users-list.tsx @@ -11,7 +11,6 @@ import { LibreChatUser, LibreChatConversation, LibreChatBalance, - LibreChatMessage, } from "@/lib/types"; interface DashboardUser { @@ -22,30 +21,15 @@ interface DashboardUser { credits: number; } -// Interfaces pour les collections tokens et toolcalls -interface TokenDocument { +// Interface pour les transactions (vraie source de consommation) +interface TransactionDocument { _id: string; - user?: string; - userId?: string; - amount?: number; - tokens?: number; - count?: number; + user: unknown; // ObjectId dans MongoDB + rawAmount?: number; + tokenType?: string; + model?: string; } -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); @@ -57,20 +41,12 @@ export function DashboardUsersList() { useCollection("conversations", { limit: 1000 }); const { data: balances, loading: balancesLoading } = useCollection("balances", { limit: 1000 }); - const { data: messages, loading: messagesLoading } = - useCollection("messages", { limit: 1000 }); - const { data: tokens, loading: tokensLoading } = - useCollection("tokens", { limit: 1000 }); - const { data: toolcalls, loading: toolcallsLoading } = - useCollection("toolcalls", { limit: 1000 }); + // Transactions = vraie source de consommation de tokens + const { data: transactions, loading: transactionsLoading } = + useCollection("transactions", { limit: 10000 }); const processUsers = useCallback(() => { - if ( - !users?.length || - !conversations?.length || - !balances?.length || - !messages?.length - ) { + if (!users?.length || !conversations?.length || !balances?.length) { return; } @@ -78,153 +54,45 @@ export function DashboardUsersList() { 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); + console.log("Transactions:", transactions?.length || 0); const processedUsers: DashboardUser[] = []; + // Créer une map des transactions par utilisateur (user est un ObjectId, on compare en string) + const tokensByUser = new Map(); + if (transactions?.length) { + transactions.forEach((tx: TransactionDocument) => { + // tx.user est un ObjectId, on le convertit en string pour la comparaison + const txUserId = String(tx.user); + const tokens = Math.abs(tx.rawAmount || 0); + tokensByUser.set(txUserId, (tokensByUser.get(txUserId) || 0) + tokens); + }); + } + users.forEach((user: LibreChatUser) => { - // Obtenir les conversations de l'utilisateur + const userId = user._id; + + // Obtenir les conversations de l'utilisateur (user dans conversations est un string) const userConversations = conversations.filter( - (conv: LibreChatConversation) => conv.user === user._id + (conv: LibreChatConversation) => String(conv.user) === userId ); - // Obtenir les messages de l'utilisateur - const userMessages = messages.filter( - (msg: LibreChatMessage) => msg.user === user._id + // Obtenir les tokens depuis les transactions (vraie consommation) + const tokensFromTransactions = tokensByUser.get(userId) || 0; + + // Obtenir le balance de l'utilisateur + const userBalance = balances.find( + (balance: LibreChatBalance) => String(balance.user) === userId ); + const credits = userBalance?.tokenCredits || 0; - // 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 réellement consommés depuis les messages (approche principale) - const totalTokens = Math.max( - totalTokensFromMessages, - totalTokensFromConversations, - tokensFromTokensCollection, - tokensFromToolcalls - ); - - // Calculer les tokens depuis les crédits seulement si on n'a pas de données de messages - const INITIAL_CREDITS = 3000000; - const creditsUsed = INITIAL_CREDITS - credits; - const tokensFromCredits = creditsUsed > 0 ? creditsUsed : 0; - - // Si on n'a pas de tokens depuis les messages mais qu'on a une consommation de crédits significative - const finalTokens = totalTokens > 0 ? totalTokens : - (tokensFromCredits > 0 && tokensFromCredits < INITIAL_CREDITS) ? tokensFromCredits : 0; - - // 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: finalTokens, - willBeIncluded: finalTokens > 0, - 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 s'il a consommé des tokens (éviter les faux positifs de 3M tokens) - if (finalTokens > 0 && finalTokens < INITIAL_CREDITS) { + // Ajouter l'utilisateur s'il a consommé des tokens + if (tokensFromTransactions > 0) { processedUsers.push({ - userId: user._id, - userName: - user.name || user.username || user.email || "Utilisateur inconnu", + userId: userId, + userName: user.name || user.username || user.email || "Utilisateur inconnu", conversations: userConversations.length, - tokens: finalTokens, + tokens: tokensFromTransactions, credits: credits, }); } @@ -239,26 +107,18 @@ export function DashboardUsersList() { totalUsers: users.length, usersWithActivity: processedUsers.length, top5Users: sortedUsers.length, - allProcessedUsers: processedUsers.map(u => ({ - name: u.userName, - conversations: u.conversations, - tokens: u.tokens, - credits: u.credits - })) }); - console.log("✅ Top 5 users processed:", sortedUsers); + console.log("✅ Top 5 users:", sortedUsers); setTopUsers(sortedUsers); setIsLoading(false); - }, [users, conversations, balances, messages, tokens, toolcalls]); + }, [users, conversations, balances, transactions]); useEffect(() => { const allDataLoaded = !usersLoading && !conversationsLoading && !balancesLoading && - !messagesLoading && - !tokensLoading && - !toolcallsLoading; + !transactionsLoading; if (allDataLoaded) { processUsers(); @@ -269,9 +129,7 @@ export function DashboardUsersList() { usersLoading, conversationsLoading, balancesLoading, - messagesLoading, - tokensLoading, - toolcallsLoading, + transactionsLoading, processUsers, ]); diff --git a/debug-tokens.js b/debug-tokens.js new file mode 100644 index 0000000..90853ba --- /dev/null +++ b/debug-tokens.js @@ -0,0 +1,38 @@ +const { MongoClient } = require('mongodb'); + +async function debug() { + const uri = 'mongodb://7qV5rRanD6UwANNO:NK1EFfaKpJZcTQlmm7pDUUI7Yk7yqxN6@51.254.197.189:27017/librechat?authSource=admin'; + const client = new MongoClient(uri); + await client.connect(); + const db = client.db('librechat'); + + const usersToCheck = ['Kleyntssens', 'Lilou Guillaume', 'Isabelle De Witte', 'Flavie Pochet']; + + console.log('=== VERIFICATION TOKENS DEPUIS TRANSACTIONS ===\n'); + + for (const searchName of usersToCheck) { + const user = await db.collection('users').findOne({ name: { $regex: searchName, $options: 'i' } }); + + if (!user) { + console.log('NOT FOUND:', searchName); + continue; + } + + const convCount = await db.collection('conversations').countDocuments({ user: user._id.toString() }); + const txs = await db.collection('transactions').find({ user: user._id }).toArray(); + const tokensFromTx = txs.reduce((sum, t) => sum + Math.abs(t.rawAmount || 0), 0); + const balance = await db.collection('balances').findOne({ user: user._id }); + const credits = balance?.tokenCredits || 0; + + console.log('---'); + console.log('User:', user.name); + console.log('Conversations:', convCount); + console.log('Tokens (VRAI depuis transactions):', tokensFromTx.toLocaleString()); + console.log('Credits:', credits.toLocaleString()); + console.log('Nb transactions:', txs.length); + } + + await client.close(); +} + +debug().catch(console.error);