From e6a9d41ebdcfafe0cf22ab575b5e8c10547408c4 Mon Sep 17 00:00:00 2001 From: Biqoz Date: Thu, 27 Nov 2025 14:23:08 +0100 Subject: [PATCH] conversation clan --- .claude/settings.local.json | 4 +- app/api/collections/[collection]/route.ts | 11 + .../collections/conversations-table.tsx | 394 ++++++++++-------- 3 files changed, 243 insertions(+), 166 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 5297503..e969f39 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -10,7 +10,9 @@ "Bash(git push)", "Bash(npm install:*)", "Bash(node debug-search.js:*)", - "Bash(node update-referent.js:*)" + "Bash(node update-referent.js:*)", + "Bash(node:*)", + "Bash(curl:*)" ], "deny": [], "ask": [] diff --git a/app/api/collections/[collection]/route.ts b/app/api/collections/[collection]/route.ts index 7a5217e..62ce1bc 100644 --- a/app/api/collections/[collection]/route.ts +++ b/app/api/collections/[collection]/route.ts @@ -51,6 +51,12 @@ export async function GET( const limit = parseInt(searchParams.get("limit") || "20"); const filter = JSON.parse(searchParams.get("filter") || "{}"); + // Debug logging pour messages + if (collection === "messages") { + console.log("[API Messages] Filter reçu:", JSON.stringify(filter)); + console.log("[API Messages] Raw filter param:", searchParams.get("filter")); + } + // Gestion spéciale pour la collection users avec recherche par email ou id if (collection === "users") { const email = searchParams.get("email"); @@ -141,6 +147,11 @@ export async function GET( db.collection(collection).countDocuments(filter), ]); + // Debug logging pour messages + if (collection === "messages") { + console.log("[API Messages] Résultats:", data.length, "messages, total:", total); + } + return NextResponse.json({ data, total, diff --git a/components/collections/conversations-table.tsx b/components/collections/conversations-table.tsx index 19a7f73..6a455a3 100644 --- a/components/collections/conversations-table.tsx +++ b/components/collections/conversations-table.tsx @@ -3,14 +3,6 @@ import { useState, useMemo, useCallback, useRef, useEffect, Fragment } from "react"; import { useQuery, keepPreviousData } from "@tanstack/react-query"; 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 { Input } from "@/components/ui/input"; @@ -51,6 +43,13 @@ interface ExtendedMessage extends LibreChatMessage { [key: string]: unknown; } +// Type pour les groupes d'utilisateurs +interface UserGroup { + userId: string; + conversations: LibreChatConversation[]; + totalMessages: number; +} + // Fetcher générique async function fetchCollection( collection: string, @@ -63,36 +62,44 @@ async function fetchCollection( } }); - const response = await fetch(`/api/collections/${collection}?${searchParams}`); + const url = `/api/collections/${collection}?${searchParams}`; + console.log("[fetchCollection]", collection, "URL:", url); + + const response = await fetch(url); if (!response.ok) throw new Error(`Erreur lors du chargement de ${collection}`); - return response.json(); + const data = await response.json(); + console.log("[fetchCollection]", collection, "Results:", data.data?.length, "items"); + return data; } export function ConversationsTable() { const [page, setPage] = useState(1); const [searchInput, setSearchInput] = useState(""); - const [selectedConversationId, setSelectedConversationId] = useState(null); + const [expandedUsers, setExpandedUsers] = useState>(new Set()); + const [expandedConversation, setExpandedConversation] = useState(null); const searchInputRef = useRef(null); - const limit = 20; + const usersPerPage = 10; // Nombre de groupes d'utilisateurs par page const debouncedSearch = useDebounce(searchInput, 250); - // Reset page quand la recherche change + // Reset page et expanded states quand la recherche change useEffect(() => { setPage(1); + setExpandedUsers(new Set()); + setExpandedConversation(null); }, [debouncedSearch]); - // Query conversations avec TanStack Query + // Query conversations avec TanStack Query - limite élevée pour groupement client const { data: conversationsData, isLoading: conversationsLoading, isFetching: conversationsFetching, } = useQuery({ - queryKey: ["conversations", page, limit, debouncedSearch], + queryKey: ["conversations", debouncedSearch], queryFn: () => fetchCollection("conversations", { - page, - limit, + page: 1, + limit: 1000, // Charger beaucoup pour grouper côté client search: debouncedSearch, }), placeholderData: keepPreviousData, @@ -108,25 +115,79 @@ export function ConversationsTable() { // Query messages de la conversation sélectionnée const { data: messagesData, isLoading: messagesLoading } = useQuery({ - queryKey: ["messages", selectedConversationId], + queryKey: ["messages", expandedConversation], queryFn: () => fetchCollection("messages", { limit: 500, - filter: JSON.stringify({ conversationId: selectedConversationId }), + filter: JSON.stringify({ conversationId: expandedConversation }), }), - enabled: !!selectedConversationId, + enabled: !!expandedConversation, staleTime: 30000, }); const conversations = conversationsData?.data ?? []; const total = conversationsData?.total ?? 0; - const totalPages = conversationsData?.totalPages ?? 0; const users = usersData?.data ?? []; const messages = messagesData?.data ?? []; // Map des users pour lookup rapide const userMap = useMemo(() => new Map(users.map((u) => [u._id, u])), [users]); + // Grouper les conversations par utilisateur + const groupedByUser = useMemo((): UserGroup[] => { + const groups: Record = {}; + conversations.forEach((conv) => { + const uId = String(conv.user); + if (!groups[uId]) groups[uId] = []; + groups[uId].push(conv); + }); + return Object.entries(groups) + .map(([userId, convs]) => ({ + userId, + conversations: convs.sort( + (a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime() + ), + totalMessages: convs.reduce((sum, c) => sum + (c.messages?.length || 0), 0), + })) + .sort((a, b) => { + // Trier par date de dernière conversation + const aDate = new Date(a.conversations[0]?.updatedAt || 0).getTime(); + const bDate = new Date(b.conversations[0]?.updatedAt || 0).getTime(); + return bDate - aDate; + }); + }, [conversations]); + + // Pagination sur les groupes d'utilisateurs + const totalUserGroups = groupedByUser.length; + const totalPages = Math.ceil(totalUserGroups / usersPerPage); + const paginatedGroups = groupedByUser.slice( + (page - 1) * usersPerPage, + page * usersPerPage + ); + + // Toggle user group expansion + const toggleUserExpanded = useCallback((userId: string) => { + setExpandedUsers((prev) => { + const next = new Set(prev); + if (next.has(userId)) { + next.delete(userId); + // Fermer aussi la conversation si elle appartient à cet utilisateur + setExpandedConversation(null); + } else { + next.add(userId); + } + return next; + }); + }, []); + + // Toggle conversation expansion + const toggleConversationExpanded = useCallback((conversationId: string) => { + console.log("[Conversations] Toggle conversation:", conversationId); + setExpandedConversation((prev) => + prev === conversationId ? null : conversationId + ); + }, []); + const getUserInfo = useCallback( (userId: string) => { const user = userMap.get(userId); @@ -236,164 +297,168 @@ export function ConversationsTable() { ) : ( <> -
- - - - Utilisateur - Titre - Endpoint - Msgs - Statut - Date - - - - {conversations.map((conv) => { - const userInfo = getUserInfo(String(conv.user)); - const msgCount = conv.messages?.length || 0; - const isSelected = selectedConversationId === conv.conversationId; + {/* Liste des groupes d'utilisateurs */} +
+ {paginatedGroups.map((group) => { + const userInfo = getUserInfo(group.userId); + const isUserExpanded = expandedUsers.has(group.userId); - return ( - - - setSelectedConversationId( - isSelected ? null : conv.conversationId - ) - } - > - -
- + {/* Header du groupe utilisateur */} + + + {/* Liste des conversations de l'utilisateur */} + {isUserExpanded && ( +
+ {group.conversations.map((conv) => { + const msgCount = conv.messages?.length || 0; + const isConvExpanded = expandedConversation === conv.conversationId; + + return ( + +
+
+ + {String(conv.endpoint).slice(0, 8)} + + + {msgCount} msg{msgCount > 1 ? "s" : ""} + + + {conv.isArchived ? "Archivée" : "Active"} + + + {formatDate(conv.updatedAt)} + +
+ - {/* Messages inline sous la row */} - {isSelected && ( - - -
- {messagesLoading ? ( -
- - Chargement des messages... -
- ) : messages.length === 0 ? ( -

- Aucun message dans cette conversation -

- ) : ( -
- {messages - .sort( - (a, b) => - new Date(a.createdAt).getTime() - - new Date(b.createdAt).getTime() - ) - .map((msg) => { - const content = getMessageContent(msg); - const isUser = msg.isCreatedByUser; + {/* Messages de la conversation */} + {isConvExpanded && ( +
+ {messagesLoading ? ( +
+ + Chargement des messages... +
+ ) : messages.length === 0 ? ( +

+ Aucun message dans cette conversation +

+ ) : ( +
+ {messages + .sort( + (a, b) => + new Date(a.createdAt).getTime() - + new Date(b.createdAt).getTime() + ) + .map((msg) => { + const content = getMessageContent(msg); + const isUser = msg.isCreatedByUser; - return ( -
-
- {isUser ? ( - - ) : ( - - )} -
-
-
- - {isUser ? "Utilisateur" : "Assistant"} - - - {formatDate(msg.createdAt)} - - {msg.tokenCount > 0 && ( - - ({msg.tokenCount} tokens) - + return ( +
+
+ {isUser ? ( + + ) : ( + )}
-
- {content} +
+
+ + {isUser ? "Utilisateur" : "Assistant"} + + + {formatDate(msg.createdAt)} + + {msg.tokenCount > 0 && ( + + ({msg.tokenCount} tokens) + + )} +
+
+ {content} +
-
- ); - })} -
- )} -
- - - )} - - ); - })} - -
+ ); + })} +
+ )} + + )} + + ); + })} + + )} + + ); + })} {/* Pagination */} {totalPages > 1 && (

- Page {page} / {totalPages} + Page {page} / {totalPages} ({totalUserGroups} utilisateurs)

); }