calcul accurate
This commit is contained in:
@@ -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<DashboardUser[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
@@ -57,20 +41,12 @@ export function DashboardUsersList() {
|
||||
useCollection<LibreChatConversation>("conversations", { limit: 1000 });
|
||||
const { data: balances, loading: balancesLoading } =
|
||||
useCollection<LibreChatBalance>("balances", { limit: 1000 });
|
||||
const { data: messages, loading: messagesLoading } =
|
||||
useCollection<LibreChatMessage>("messages", { limit: 1000 });
|
||||
const { data: tokens, loading: tokensLoading } =
|
||||
useCollection<TokenDocument>("tokens", { limit: 1000 });
|
||||
const { data: toolcalls, loading: toolcallsLoading } =
|
||||
useCollection<ToolcallDocument>("toolcalls", { limit: 1000 });
|
||||
// Transactions = vraie source de consommation de tokens
|
||||
const { data: transactions, loading: transactionsLoading } =
|
||||
useCollection<TransactionDocument>("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<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) => {
|
||||
// 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,
|
||||
]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user