calcul accurate
This commit is contained in:
@@ -11,7 +11,6 @@ import {
|
|||||||
LibreChatUser,
|
LibreChatUser,
|
||||||
LibreChatConversation,
|
LibreChatConversation,
|
||||||
LibreChatBalance,
|
LibreChatBalance,
|
||||||
LibreChatMessage,
|
|
||||||
} from "@/lib/types";
|
} from "@/lib/types";
|
||||||
|
|
||||||
interface DashboardUser {
|
interface DashboardUser {
|
||||||
@@ -22,30 +21,15 @@ interface DashboardUser {
|
|||||||
credits: number;
|
credits: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interfaces pour les collections tokens et toolcalls
|
// Interface pour les transactions (vraie source de consommation)
|
||||||
interface TokenDocument {
|
interface TransactionDocument {
|
||||||
_id: string;
|
_id: string;
|
||||||
user?: string;
|
user: unknown; // ObjectId dans MongoDB
|
||||||
userId?: string;
|
rawAmount?: number;
|
||||||
amount?: number;
|
tokenType?: string;
|
||||||
tokens?: number;
|
model?: string;
|
||||||
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() {
|
export function DashboardUsersList() {
|
||||||
const [topUsers, setTopUsers] = useState<DashboardUser[]>([]);
|
const [topUsers, setTopUsers] = useState<DashboardUser[]>([]);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
@@ -57,20 +41,12 @@ export function DashboardUsersList() {
|
|||||||
useCollection<LibreChatConversation>("conversations", { limit: 1000 });
|
useCollection<LibreChatConversation>("conversations", { limit: 1000 });
|
||||||
const { data: balances, loading: balancesLoading } =
|
const { data: balances, loading: balancesLoading } =
|
||||||
useCollection<LibreChatBalance>("balances", { limit: 1000 });
|
useCollection<LibreChatBalance>("balances", { limit: 1000 });
|
||||||
const { data: messages, loading: messagesLoading } =
|
// Transactions = vraie source de consommation de tokens
|
||||||
useCollection<LibreChatMessage>("messages", { limit: 1000 });
|
const { data: transactions, loading: transactionsLoading } =
|
||||||
const { data: tokens, loading: tokensLoading } =
|
useCollection<TransactionDocument>("transactions", { limit: 10000 });
|
||||||
useCollection<TokenDocument>("tokens", { limit: 1000 });
|
|
||||||
const { data: toolcalls, loading: toolcallsLoading } =
|
|
||||||
useCollection<ToolcallDocument>("toolcalls", { limit: 1000 });
|
|
||||||
|
|
||||||
const processUsers = useCallback(() => {
|
const processUsers = useCallback(() => {
|
||||||
if (
|
if (!users?.length || !conversations?.length || !balances?.length) {
|
||||||
!users?.length ||
|
|
||||||
!conversations?.length ||
|
|
||||||
!balances?.length ||
|
|
||||||
!messages?.length
|
|
||||||
) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,153 +54,45 @@ export function DashboardUsersList() {
|
|||||||
console.log("Users:", users.length);
|
console.log("Users:", users.length);
|
||||||
console.log("Conversations:", conversations.length);
|
console.log("Conversations:", conversations.length);
|
||||||
console.log("Balances:", balances.length);
|
console.log("Balances:", balances.length);
|
||||||
console.log("Messages:", messages.length);
|
console.log("Transactions:", transactions?.length || 0);
|
||||||
console.log("Tokens collection:", tokens?.length || 0);
|
|
||||||
console.log("Toolcalls collection:", toolcalls?.length || 0);
|
|
||||||
|
|
||||||
const processedUsers: DashboardUser[] = [];
|
const processedUsers: DashboardUser[] = [];
|
||||||
|
|
||||||
|
// Créer une map des transactions par utilisateur (user est un ObjectId, on compare en string)
|
||||||
|
const tokensByUser = new Map<string, number>();
|
||||||
|
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) => {
|
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(
|
const userConversations = conversations.filter(
|
||||||
(conv: LibreChatConversation) => conv.user === user._id
|
(conv: LibreChatConversation) => String(conv.user) === userId
|
||||||
);
|
);
|
||||||
|
|
||||||
// Obtenir les messages de l'utilisateur
|
// Obtenir les tokens depuis les transactions (vraie consommation)
|
||||||
const userMessages = messages.filter(
|
const tokensFromTransactions = tokensByUser.get(userId) || 0;
|
||||||
(msg: LibreChatMessage) => msg.user === user._id
|
|
||||||
|
// 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
|
// Ajouter l'utilisateur s'il a consommé des tokens
|
||||||
const totalTokensFromMessages = userMessages.reduce(
|
if (tokensFromTransactions > 0) {
|
||||||
(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) {
|
|
||||||
processedUsers.push({
|
processedUsers.push({
|
||||||
userId: user._id,
|
userId: userId,
|
||||||
userName:
|
userName: user.name || user.username || user.email || "Utilisateur inconnu",
|
||||||
user.name || user.username || user.email || "Utilisateur inconnu",
|
|
||||||
conversations: userConversations.length,
|
conversations: userConversations.length,
|
||||||
tokens: finalTokens,
|
tokens: tokensFromTransactions,
|
||||||
credits: credits,
|
credits: credits,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -239,26 +107,18 @@ export function DashboardUsersList() {
|
|||||||
totalUsers: users.length,
|
totalUsers: users.length,
|
||||||
usersWithActivity: processedUsers.length,
|
usersWithActivity: processedUsers.length,
|
||||||
top5Users: sortedUsers.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);
|
setTopUsers(sortedUsers);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}, [users, conversations, balances, messages, tokens, toolcalls]);
|
}, [users, conversations, balances, transactions]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const allDataLoaded =
|
const allDataLoaded =
|
||||||
!usersLoading &&
|
!usersLoading &&
|
||||||
!conversationsLoading &&
|
!conversationsLoading &&
|
||||||
!balancesLoading &&
|
!balancesLoading &&
|
||||||
!messagesLoading &&
|
!transactionsLoading;
|
||||||
!tokensLoading &&
|
|
||||||
!toolcallsLoading;
|
|
||||||
|
|
||||||
if (allDataLoaded) {
|
if (allDataLoaded) {
|
||||||
processUsers();
|
processUsers();
|
||||||
@@ -269,9 +129,7 @@ export function DashboardUsersList() {
|
|||||||
usersLoading,
|
usersLoading,
|
||||||
conversationsLoading,
|
conversationsLoading,
|
||||||
balancesLoading,
|
balancesLoading,
|
||||||
messagesLoading,
|
transactionsLoading,
|
||||||
tokensLoading,
|
|
||||||
toolcallsLoading,
|
|
||||||
processUsers,
|
processUsers,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
38
debug-tokens.js
Normal file
38
debug-tokens.js
Normal file
@@ -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);
|
||||||
Reference in New Issue
Block a user