clean
This commit is contained in:
@@ -14,7 +14,7 @@ import {
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||||
import { formatDate, formatCurrency } from "@/lib/utils";
|
import { formatDate } from "@/lib/utils";
|
||||||
import { LibreChatTransaction, LibreChatUser } from "@/lib/types";
|
import { LibreChatTransaction, LibreChatUser } from "@/lib/types";
|
||||||
|
|
||||||
// Interface étendue pour les transactions avec description optionnelle
|
// Interface étendue pour les transactions avec description optionnelle
|
||||||
@@ -53,29 +53,24 @@ export function TransactionsTable() {
|
|||||||
return user?.name || user?.email || `Utilisateur ${userId.slice(-8)}`;
|
return user?.name || user?.email || `Utilisateur ${userId.slice(-8)}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Fonction pour formater le montant en euros
|
|
||||||
const formatAmount = (rawAmount: number): string => {
|
|
||||||
// Convertir les tokens en euros (exemple: 1000 tokens = 1 euro)
|
|
||||||
const euros = rawAmount / 1000;
|
|
||||||
return formatCurrency(euros);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fonction pour obtenir la description
|
// Fonction pour obtenir la description
|
||||||
const getDescription = (transaction: LibreChatTransaction): string => {
|
const getDescription = (transaction: LibreChatTransaction): string => {
|
||||||
const transactionWithDesc = transaction as TransactionWithDescription;
|
const transactionWithDesc = transaction as TransactionWithDescription;
|
||||||
|
|
||||||
if (transactionWithDesc.description &&
|
if (
|
||||||
typeof transactionWithDesc.description === 'string' &&
|
transactionWithDesc.description &&
|
||||||
transactionWithDesc.description !== "undefined") {
|
typeof transactionWithDesc.description === "string" &&
|
||||||
|
transactionWithDesc.description !== "undefined"
|
||||||
|
) {
|
||||||
return transactionWithDesc.description;
|
return transactionWithDesc.description;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Générer une description basée sur le type et le montant
|
// Générer une description basée sur le type et le montant
|
||||||
const amount = Math.abs(Number(transaction.rawAmount) || 0);
|
const amount = Math.abs(Number(transaction.rawAmount) || 0);
|
||||||
if (amount > 0) {
|
if (amount > 0) {
|
||||||
return `Consommation de ${amount.toLocaleString()} tokens`;
|
return `Consommation de ${amount.toLocaleString()} tokens`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return "Transaction sans description";
|
return "Transaction sans description";
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -111,7 +106,6 @@ export function TransactionsTable() {
|
|||||||
<TableHead>ID</TableHead>
|
<TableHead>ID</TableHead>
|
||||||
<TableHead>Utilisateur</TableHead>
|
<TableHead>Utilisateur</TableHead>
|
||||||
<TableHead>Type</TableHead>
|
<TableHead>Type</TableHead>
|
||||||
<TableHead>Montant</TableHead>
|
|
||||||
<TableHead>Tokens</TableHead>
|
<TableHead>Tokens</TableHead>
|
||||||
<TableHead>Description</TableHead>
|
<TableHead>Description</TableHead>
|
||||||
<TableHead>Date</TableHead>
|
<TableHead>Date</TableHead>
|
||||||
@@ -151,11 +145,6 @@ export function TransactionsTable() {
|
|||||||
{isCredit ? "Crédit" : "Débit"}
|
{isCredit ? "Crédit" : "Débit"}
|
||||||
</Badge>
|
</Badge>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
|
||||||
<span className="font-semibold">
|
|
||||||
{formatAmount(transaction.rawAmount)}
|
|
||||||
</span>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{tokenAmount > 0 && (
|
{tokenAmount > 0 && (
|
||||||
<Badge variant="outline" className="text-xs">
|
<Badge variant="outline" className="text-xs">
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import { useCollection } from "@/hooks/useCollection";
|
|||||||
import {
|
import {
|
||||||
LibreChatUser,
|
LibreChatUser,
|
||||||
LibreChatConversation,
|
LibreChatConversation,
|
||||||
LibreChatTransaction,
|
|
||||||
LibreChatBalance,
|
LibreChatBalance,
|
||||||
LibreChatMessage,
|
LibreChatMessage,
|
||||||
} from "@/lib/types";
|
} from "@/lib/types";
|
||||||
@@ -23,15 +22,28 @@ interface DashboardUser {
|
|||||||
credits: number;
|
credits: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fonction utilitaire pour valider et convertir les dates
|
// 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 => {
|
const isValidDate = (value: unknown): value is string | number | Date => {
|
||||||
if (!value) return false;
|
if (!value) return false;
|
||||||
if (value instanceof Date) return !isNaN(value.getTime());
|
const date = new Date(value as string | number | Date);
|
||||||
if (typeof value === 'string' || typeof value === 'number') {
|
return !isNaN(date.getTime());
|
||||||
const date = new Date(value);
|
|
||||||
return !isNaN(date.getTime());
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function DashboardUsersList() {
|
export function DashboardUsersList() {
|
||||||
@@ -39,23 +51,32 @@ export function DashboardUsersList() {
|
|||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
// Récupérer toutes les données nécessaires
|
// Récupérer toutes les données nécessaires
|
||||||
const { data: users, loading: usersLoading } = useCollection<LibreChatUser>("users");
|
const { data: users, loading: usersLoading } =
|
||||||
const { data: conversations, loading: conversationsLoading } = useCollection<LibreChatConversation>("conversations");
|
useCollection<LibreChatUser>("users");
|
||||||
const { data: transactions, loading: transactionsLoading } = useCollection<LibreChatTransaction>("transactions");
|
const { data: conversations, loading: conversationsLoading } =
|
||||||
const { data: balances, loading: balancesLoading } = useCollection<LibreChatBalance>("balances");
|
useCollection<LibreChatConversation>("conversations");
|
||||||
const { data: messages, loading: messagesLoading } = useCollection<LibreChatMessage>("messages");
|
const { data: balances, loading: balancesLoading } =
|
||||||
const { data: tokens, loading: tokensLoading } = useCollection("tokens");
|
useCollection<LibreChatBalance>("balances");
|
||||||
const { data: toolcalls, loading: toolcallsLoading } = useCollection("toolcalls");
|
const { data: messages, loading: messagesLoading } =
|
||||||
|
useCollection<LibreChatMessage>("messages");
|
||||||
|
const { data: tokens, loading: tokensLoading } =
|
||||||
|
useCollection<TokenDocument>("tokens");
|
||||||
|
const { data: toolcalls, loading: toolcallsLoading } =
|
||||||
|
useCollection<ToolcallDocument>("toolcalls");
|
||||||
|
|
||||||
const processUsers = useCallback(() => {
|
const processUsers = useCallback(() => {
|
||||||
if (!users?.length || !conversations?.length || !balances?.length || !messages?.length) {
|
if (
|
||||||
|
!users?.length ||
|
||||||
|
!conversations?.length ||
|
||||||
|
!balances?.length ||
|
||||||
|
!messages?.length
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("🔄 Processing users data...");
|
console.log("🔄 Processing users data...");
|
||||||
console.log("Users:", users.length);
|
console.log("Users:", users.length);
|
||||||
console.log("Conversations:", conversations.length);
|
console.log("Conversations:", conversations.length);
|
||||||
console.log("Transactions:", transactions?.length || 0);
|
|
||||||
console.log("Balances:", balances.length);
|
console.log("Balances:", balances.length);
|
||||||
console.log("Messages:", messages.length);
|
console.log("Messages:", messages.length);
|
||||||
console.log("Tokens collection:", tokens?.length || 0);
|
console.log("Tokens collection:", tokens?.length || 0);
|
||||||
@@ -81,11 +102,13 @@ export function DashboardUsersList() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Calculer les tokens depuis les conversations de l'utilisateur
|
// Calculer les tokens depuis les conversations de l'utilisateur
|
||||||
const userConversationIds = userConversations.map(conv => conv.conversationId);
|
const userConversationIds = userConversations.map(
|
||||||
const conversationMessages = messages.filter(
|
(conv) => conv.conversationId
|
||||||
(msg: LibreChatMessage) => userConversationIds.includes(msg.conversationId)
|
|
||||||
);
|
);
|
||||||
|
const conversationMessages = messages.filter((msg: LibreChatMessage) =>
|
||||||
|
userConversationIds.includes(msg.conversationId)
|
||||||
|
);
|
||||||
|
|
||||||
const totalTokensFromConversations = conversationMessages.reduce(
|
const totalTokensFromConversations = conversationMessages.reduce(
|
||||||
(sum: number, msg: LibreChatMessage) => sum + (msg.tokenCount || 0),
|
(sum: number, msg: LibreChatMessage) => sum + (msg.tokenCount || 0),
|
||||||
0
|
0
|
||||||
@@ -96,17 +119,29 @@ export function DashboardUsersList() {
|
|||||||
let tokensFromToolcalls = 0;
|
let tokensFromToolcalls = 0;
|
||||||
|
|
||||||
if (tokens?.length) {
|
if (tokens?.length) {
|
||||||
const userTokens = tokens.filter((token: any) => token.user === user._id || token.userId === user._id);
|
const userTokens = tokens.filter(
|
||||||
tokensFromTokensCollection = userTokens.reduce((sum: number, token: any) => {
|
(token: TokenDocument) =>
|
||||||
return sum + (token.amount || token.tokens || token.count || 0);
|
token.user === user._id || token.userId === user._id
|
||||||
}, 0);
|
);
|
||||||
|
tokensFromTokensCollection = userTokens.reduce(
|
||||||
|
(sum: number, token: TokenDocument) => {
|
||||||
|
return sum + (token.amount || token.tokens || token.count || 0);
|
||||||
|
},
|
||||||
|
0
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toolcalls?.length) {
|
if (toolcalls?.length) {
|
||||||
const userToolcalls = toolcalls.filter((toolcall: any) => toolcall.user === user._id || toolcall.userId === user._id);
|
const userToolcalls = toolcalls.filter(
|
||||||
tokensFromToolcalls = userToolcalls.reduce((sum: number, toolcall: any) => {
|
(toolcall: ToolcallDocument) =>
|
||||||
return sum + (toolcall.tokens || toolcall.tokenCount || 0);
|
toolcall.user === user._id || toolcall.userId === user._id
|
||||||
}, 0);
|
);
|
||||||
|
tokensFromToolcalls = userToolcalls.reduce(
|
||||||
|
(sum: number, toolcall: ToolcallDocument) => {
|
||||||
|
return sum + (toolcall.tokens || toolcall.tokenCount || 0);
|
||||||
|
},
|
||||||
|
0
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Obtenir les balances de l'utilisateur
|
// Obtenir les balances de l'utilisateur
|
||||||
@@ -118,16 +153,19 @@ export function DashboardUsersList() {
|
|||||||
const sortedBalances = userBalances.sort((a, b) => {
|
const sortedBalances = userBalances.sort((a, b) => {
|
||||||
const dateA = a.updatedAt || a.createdAt;
|
const dateA = a.updatedAt || a.createdAt;
|
||||||
const dateB = b.updatedAt || b.createdAt;
|
const dateB = b.updatedAt || b.createdAt;
|
||||||
|
|
||||||
// Vérifier que les dates sont valides avant de les comparer
|
// Vérifier que les dates sont valides avant de les comparer
|
||||||
if (isValidDate(dateA) && isValidDate(dateB)) {
|
if (isValidDate(dateA) && isValidDate(dateB)) {
|
||||||
return new Date(dateB as string | number | Date).getTime() - new Date(dateA as string | number | Date).getTime();
|
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
|
// 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;
|
||||||
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
|
// Si aucune date n'existe ou n'est valide, garder l'ordre actuel
|
||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
@@ -163,23 +201,24 @@ export function DashboardUsersList() {
|
|||||||
creditsUsed: creditsUsed,
|
creditsUsed: creditsUsed,
|
||||||
tokensFromCredits: tokensFromCredits,
|
tokensFromCredits: tokensFromCredits,
|
||||||
finalTokens: totalTokens,
|
finalTokens: totalTokens,
|
||||||
messagesSample: userMessages.slice(0, 2).map(m => ({
|
messagesSample: userMessages.slice(0, 2).map((m) => ({
|
||||||
tokenCount: m.tokenCount,
|
tokenCount: m.tokenCount,
|
||||||
model: m.model,
|
model: m.model,
|
||||||
isCreatedByUser: m.isCreatedByUser,
|
isCreatedByUser: m.isCreatedByUser,
|
||||||
conversationId: m.conversationId
|
conversationId: m.conversationId,
|
||||||
})),
|
})),
|
||||||
conversationsSample: userConversations.slice(0, 2).map(c => ({
|
conversationsSample: userConversations.slice(0, 2).map((c) => ({
|
||||||
conversationId: c.conversationId,
|
conversationId: c.conversationId,
|
||||||
messagesCount: c.messages?.length || 0
|
messagesCount: c.messages?.length || 0,
|
||||||
}))
|
})),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Ajouter l'utilisateur seulement s'il a des données significatives
|
// Ajouter l'utilisateur seulement s'il a des données significatives
|
||||||
if (userConversations.length > 0 || totalTokens > 0 || credits > 0) {
|
if (userConversations.length > 0 || totalTokens > 0 || credits > 0) {
|
||||||
processedUsers.push({
|
processedUsers.push({
|
||||||
userId: user._id,
|
userId: user._id,
|
||||||
userName: user.name || user.username || user.email || 'Utilisateur inconnu',
|
userName:
|
||||||
|
user.name || user.username || user.email || "Utilisateur inconnu",
|
||||||
conversations: userConversations.length,
|
conversations: userConversations.length,
|
||||||
tokens: totalTokens,
|
tokens: totalTokens,
|
||||||
credits: credits,
|
credits: credits,
|
||||||
@@ -195,17 +234,31 @@ export function DashboardUsersList() {
|
|||||||
console.log("✅ Top 5 users processed:", sortedUsers);
|
console.log("✅ Top 5 users processed:", sortedUsers);
|
||||||
setTopUsers(sortedUsers);
|
setTopUsers(sortedUsers);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}, [users, conversations, transactions, balances, messages, tokens, toolcalls]);
|
}, [users, conversations, balances, messages, tokens, toolcalls]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const allDataLoaded = !usersLoading && !conversationsLoading && !balancesLoading && !messagesLoading && !tokensLoading && !toolcallsLoading;
|
const allDataLoaded =
|
||||||
|
!usersLoading &&
|
||||||
|
!conversationsLoading &&
|
||||||
|
!balancesLoading &&
|
||||||
|
!messagesLoading &&
|
||||||
|
!tokensLoading &&
|
||||||
|
!toolcallsLoading;
|
||||||
|
|
||||||
if (allDataLoaded) {
|
if (allDataLoaded) {
|
||||||
processUsers();
|
processUsers();
|
||||||
} else {
|
} else {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
}
|
}
|
||||||
}, [usersLoading, conversationsLoading, balancesLoading, messagesLoading, tokensLoading, toolcallsLoading, processUsers]);
|
}, [
|
||||||
|
usersLoading,
|
||||||
|
conversationsLoading,
|
||||||
|
balancesLoading,
|
||||||
|
messagesLoading,
|
||||||
|
tokensLoading,
|
||||||
|
toolcallsLoading,
|
||||||
|
processUsers,
|
||||||
|
]);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
@@ -222,7 +275,10 @@ export function DashboardUsersList() {
|
|||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{[...Array(5)].map((_, i) => (
|
{[...Array(5)].map((_, i) => (
|
||||||
<div key={i} className="flex items-center justify-between p-3 border rounded-lg">
|
<div
|
||||||
|
key={i}
|
||||||
|
className="flex items-center justify-between p-3 border rounded-lg"
|
||||||
|
>
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<div className="w-8 h-8 bg-gray-200 rounded-full animate-pulse"></div>
|
<div className="w-8 h-8 bg-gray-200 rounded-full animate-pulse"></div>
|
||||||
<div>
|
<div>
|
||||||
@@ -250,7 +306,7 @@ export function DashboardUsersList() {
|
|||||||
<Users className="h-5 w-5" />
|
<Users className="h-5 w-5" />
|
||||||
Top 5 utilisateurs
|
Top 5 utilisateurs
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<Link href="/users">
|
<Link href="/users">
|
||||||
<Button variant="outline" size="sm">
|
<Button variant="outline" size="sm">
|
||||||
Voir tous
|
Voir tous
|
||||||
</Button>
|
</Button>
|
||||||
@@ -286,13 +342,17 @@ export function DashboardUsersList() {
|
|||||||
className="flex items-center justify-between p-3 border rounded-lg hover:bg-gray-50 transition-colors"
|
className="flex items-center justify-between p-3 border rounded-lg hover:bg-gray-50 transition-colors"
|
||||||
>
|
>
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<Badge variant="secondary" className="w-6 h-6 rounded-full p-0 flex items-center justify-center text-xs">
|
<Badge
|
||||||
|
variant="secondary"
|
||||||
|
className="w-6 h-6 rounded-full p-0 flex items-center justify-center text-xs"
|
||||||
|
>
|
||||||
{index + 1}
|
{index + 1}
|
||||||
</Badge>
|
</Badge>
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium text-sm">{user.userName}</p>
|
<p className="font-medium text-sm">{user.userName}</p>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
{user.conversations} conversation{user.conversations !== 1 ? 's' : ''}
|
{user.conversations} conversation
|
||||||
|
{user.conversations !== 1 ? "s" : ""}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -310,4 +370,4 @@ export function DashboardUsersList() {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user