"use client"; import { useState } from "react"; import { useCollection } from "@/hooks/useCollection"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { ChevronLeft, ChevronRight, Users, MessageSquare, Calendar, X, User, Bot, } from "lucide-react"; import { formatDate } from "@/lib/utils"; import { LibreChatConversation, LibreChatUser, LibreChatMessage, } from "@/lib/types"; // Types pour les messages étendus interface ExtendedMessage extends LibreChatMessage { content?: Array<{ type: string; text: string }> | string; message?: Record; parts?: Array; metadata?: { text?: string }; [key: string]: unknown; } export function ConversationsTable() { const [page, setPage] = useState(1); const [selectedConversationId, setSelectedConversationId] = useState< string | null >(null); const [selectedUserId, setSelectedUserId] = useState(null); const limit = 10; // Charger toutes les conversations pour le groupement côté client const { data: conversations = [], total = 0, loading, } = useCollection("conversations", { limit: 1000, page: 1, // Remplacer skip par page }); const { data: users = [] } = useCollection("users", { limit: 1000, }); // Charger les messages seulement si une conversation est sélectionnée const { data: messages = [] } = useCollection("messages", { limit: 1000, filter: selectedConversationId ? { conversationId: selectedConversationId } : {}, }); const userMap = new Map(users.map((user) => [user._id, user])); const getUserDisplayName = (userId: string): string => { if (userId === "unknown") return "Utilisateur inconnu"; const user = userMap.get(userId); if (user) { return ( user.name || user.username || user.email || `Utilisateur ${userId.slice(-8)}` ); } return `Utilisateur ${userId.slice(-8)}`; }; const getUserEmail = (userId: string): string | null => { if (userId === "unknown") return null; const user = userMap.get(userId); return user?.email || null; }; // Fonction améliorée pour extraire le contenu du message const getMessageContent = (message: LibreChatMessage): string => { // Fonction helper pour nettoyer le texte const cleanText = (text: string): string => { return text.trim().replace(/\n\s*\n/g, "\n"); }; // 1. Vérifier le tableau content (structure LibreChat) const messageObj = message as ExtendedMessage; if (messageObj.content && Array.isArray(messageObj.content)) { for (const contentItem of messageObj.content) { if ( contentItem && typeof contentItem === "object" && contentItem.text ) { return cleanText(contentItem.text); } } } // 2. Essayer le champ text principal if ( message.text && typeof message.text === "string" && message.text.trim() ) { return cleanText(message.text); } // 3. Essayer le champ content (format legacy string) if ( messageObj.content && typeof messageObj.content === "string" && messageObj.content.trim() ) { return cleanText(messageObj.content); } // 4. Vérifier s'il y a des propriétés imbriquées if (message.message && typeof message.message === "object") { const nestedMessage = message.message as Record; if (nestedMessage.content && typeof nestedMessage.content === "string") { return cleanText(nestedMessage.content); } if (nestedMessage.text && typeof nestedMessage.text === "string") { return cleanText(nestedMessage.text); } } // 5. Vérifier les propriétés spécifiques à LibreChat // Parfois le contenu est dans une propriété 'parts' if ( messageObj.parts && Array.isArray(messageObj.parts) && messageObj.parts.length > 0 ) { const firstPart = messageObj.parts[0]; if (typeof firstPart === "string") { return cleanText(firstPart); } if (firstPart && typeof firstPart === "object" && firstPart.text) { return cleanText(firstPart.text); } } // 6. Vérifier si c'est un message avec des métadonnées if (messageObj.metadata && messageObj.metadata.text) { return cleanText(messageObj.metadata.text); } // 7. Vérifier les propriétés alternatives const alternativeFields = ["body", "messageText", "textContent", "data"]; for (const field of alternativeFields) { const value = messageObj[field]; if (value && typeof value === "string" && value.trim()) { return cleanText(value); } } // Debug: afficher la structure du message si aucun contenu n'est trouvé console.log("Message sans contenu trouvé:", { messageId: message.messageId, isCreatedByUser: message.isCreatedByUser, keys: Object.keys(messageObj), content: messageObj.content, text: messageObj.text, }); return "Contenu non disponible"; }; const handleShowMessages = (conversationId: string, userId: string) => { if ( selectedConversationId === conversationId && selectedUserId === userId ) { setSelectedConversationId(null); setSelectedUserId(null); } else { setSelectedConversationId(conversationId); setSelectedUserId(userId); } }; const handleCloseMessages = () => { setSelectedConversationId(null); setSelectedUserId(null); }; const getStatus = (conversation: LibreChatConversation) => { if (conversation.isArchived) return "archived"; return "active"; }; const getStatusLabel = (status: string) => { switch (status) { case "archived": return "Archivée"; case "active": return "Active"; default: return "Inconnue"; } }; const getStatusVariant = (status: string) => { switch (status) { case "archived": return "outline" as const; case "active": return "default" as const; default: return "secondary" as const; } }; if (loading) { return (
Chargement des conversations...
); } // Grouper les conversations par utilisateur const groupedConversations = conversations.reduce((acc, conversation) => { const userId = conversation.user || "unknown"; if (!acc[userId]) { acc[userId] = []; } acc[userId].push(conversation); return acc; }, {} as Record); // Pagination des groupes d'utilisateurs const totalPages = Math.ceil( Object.keys(groupedConversations).length / limit ); const skip = (page - 1) * limit; const userIds = Object.keys(groupedConversations).slice(skip, skip + limit); return (
Conversations par utilisateur

{Object.keys(groupedConversations).length} utilisateurs •{" "} {conversations.length} conversations au total

{userIds.map((userId) => { const conversations = groupedConversations[userId]; const totalMessages = conversations.reduce( (sum, conv) => sum + (conv.messages?.length || 0), 0 ); const activeConversations = conversations.filter( (conv) => !conv.isArchived ).length; const archivedConversations = conversations.filter( (conv) => conv.isArchived ).length; const userName = getUserDisplayName(userId); const userEmail = getUserEmail(userId); return (
{userId === "unknown" ? "unknown" : userId.slice(-8)}
{userName} {userEmail && ( {userEmail} )}
{conversations.length} conversation {conversations.length > 1 ? "s" : ""} {activeConversations > 0 && ( {activeConversations} actives )} {archivedConversations > 0 && ( {archivedConversations} archivées )}
{totalMessages} message{totalMessages > 1 ? "s" : ""}
Dernière:{" "} {formatDate( new Date( Math.max( ...conversations.map((c) => new Date(c.updatedAt).getTime() ) ) ) )}
ID Titre Endpoint Modèle Messages Statut Créée le {conversations .sort( (a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime() ) .map((conversation) => { const status = getStatus(conversation); const messageCount = conversation.messages?.length || 0; return ( {String(conversation._id).slice(-8)} {String(conversation.title) || "Sans titre"} {String(conversation.endpoint).slice(0, 20)} {String(conversation.endpoint).length > 20 ? "..." : ""} {String(conversation.model)} handleShowMessages( conversation.conversationId, userId ) } > {messageCount} {getStatusLabel(status)} {formatDate(conversation.createdAt)} ); })}
{/* Section des messages pour cet utilisateur */} {selectedConversationId && selectedUserId === userId && (

Messages de la conversation

Conversation ID: {selectedConversationId}

{messages.length === 0 ? (

Aucun message trouvé pour cette conversation

) : ( messages .sort( (a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime() ) .map((message) => { const content = getMessageContent(message); return (
{message.isCreatedByUser ? ( ) : ( )}
{message.isCreatedByUser ? "Utilisateur" : "Assistant"} {formatDate(message.createdAt)} {message.tokenCount > 0 && ( {message.tokenCount} tokens )} {message._id.slice(-8)}
{content}
{message.error && ( Erreur )}
); }) )}
)}
); })}
{totalPages > 1 && (

Page {page} sur {totalPages} • {total} conversations au total

)}
); }