first commit

This commit is contained in:
nBiqoz
2025-10-05 16:10:35 +02:00
parent 201fca4e68
commit 13cd637391
70 changed files with 7287 additions and 130 deletions

View File

@@ -0,0 +1,304 @@
"use client";
import { useState, useMemo } 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, User, Bot } from "lucide-react";
import { formatDate } from "@/lib/utils";
import {
LibreChatMessage,
LibreChatUser,
LibreChatConversation,
} from "@/lib/types";
// Définir des interfaces pour les types de contenu
interface MessageContentItem {
text?: string;
content?: string;
type?: string;
}
interface MessagePart {
text?: string;
content?: string;
type?: string;
}
interface MessageWithParts extends LibreChatMessage {
parts?: MessagePart[];
content?: MessageContentItem[] | string;
}
export function MessagesTable() {
const [page, setPage] = useState(1);
const limit = 20;
// Charger les messages
const {
data: messages = [],
total = 0,
loading: messagesLoading,
} = useCollection<LibreChatMessage>("messages", {
page,
limit,
});
// Charger les utilisateurs pour les noms
const { data: users = [] } = useCollection<LibreChatUser>("users", {
limit: 1000,
});
// Charger les conversations pour les titres
const { data: conversations = [] } = useCollection<LibreChatConversation>(
"conversations",
{
limit: 1000,
}
);
// Créer des maps pour les lookups
const userMap = useMemo(() => {
return new Map(users.map((user) => [user._id, user]));
}, [users]);
const conversationMap = useMemo(() => {
return new Map(
conversations.map((conv) => [conv.conversationId || conv._id, conv])
);
}, [conversations]);
const totalPages = Math.ceil(total / limit);
const handlePrevPage = () => {
setPage((prev) => Math.max(1, prev - 1));
};
const handleNextPage = () => {
setPage((prev) => Math.min(totalPages, prev + 1));
};
// Fonction pour extraire le contenu du message
const getMessageContent = (message: LibreChatMessage): string => {
try {
// Vérifier le champ text principal
if (message.text && typeof message.text === "string") {
return message.text.trim();
}
// Traiter le message comme ayant potentiellement des parties
const messageWithParts = message as MessageWithParts;
// Vérifier le champ content (peut être un array ou string)
if (messageWithParts.content) {
if (typeof messageWithParts.content === "string") {
return messageWithParts.content.trim();
}
if (Array.isArray(messageWithParts.content)) {
// Extraire le texte des objets content
const textContent = messageWithParts.content
.map((item: MessageContentItem | string) => {
if (typeof item === "string") return item;
if (item && typeof item === "object" && item.text)
return item.text;
if (item && typeof item === "object" && item.content)
return item.content;
return "";
})
.filter(Boolean)
.join(" ");
if (textContent.trim()) return textContent.trim();
}
}
// Vérifier les propriétés alternatives
if (messageWithParts.parts && Array.isArray(messageWithParts.parts)) {
const textContent = messageWithParts.parts
.map((part: MessagePart) => {
if (typeof part === "string") return part;
if (part && typeof part === "object" && part.text) return part.text;
return "";
})
.filter(Boolean)
.join(" ");
if (textContent.trim()) return textContent.trim();
}
return "Contenu non disponible";
} catch (error) {
console.error("Erreur lors de l'extraction du contenu:", error);
return "Erreur de lecture du contenu";
}
};
// Fonction pour obtenir le nom d'utilisateur
const getUserName = (userId: string): string => {
if (!userId || userId === "undefined") return "Utilisateur inconnu";
const user = userMap.get(userId);
return user?.name || user?.email || `Utilisateur ${userId.slice(-8)}`;
};
// Fonction pour obtenir le titre de la conversation
const getConversationTitle = (conversationId: string): string => {
if (!conversationId || conversationId === "undefined")
return "Conversation inconnue";
const conversation = conversationMap.get(conversationId);
if (conversation && conversation.title) {
return conversation.title;
}
return `Conversation ${conversationId.slice(-6)}`;
};
if (messagesLoading) {
return (
<Card>
<CardHeader>
<CardTitle>Messages</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-4">
{Array.from({ length: 5 }).map((_, i) => (
<div key={i} className="h-16 bg-muted animate-pulse rounded" />
))}
</div>
</CardContent>
</Card>
);
}
return (
<Card>
<CardHeader>
<CardTitle>Messages récents ({total})</CardTitle>
</CardHeader>
<CardContent>
<div className="rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead>ID</TableHead>
<TableHead>Conversation</TableHead>
<TableHead>Utilisateur</TableHead>
<TableHead>Rôle</TableHead>
<TableHead>Contenu</TableHead>
<TableHead>Tokens</TableHead>
<TableHead>Créé le</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{messages.map((message) => {
const content = getMessageContent(message);
const userName = getUserName(message.user);
const conversationTitle = getConversationTitle(
message.conversationId
);
const isUser = message.isCreatedByUser;
return (
<TableRow key={message._id}>
<TableCell>
<span className="font-mono text-xs">
{message._id.slice(-8)}
</span>
</TableCell>
<TableCell>
<div className="max-w-xs">
<span className="font-mono text-xs text-muted-foreground">
{message.conversationId?.slice(-8) || "N/A"}
</span>
<div className="text-sm truncate">
{conversationTitle}
</div>
</div>
</TableCell>
<TableCell>
<div className="max-w-xs">
<span className="font-mono text-xs text-muted-foreground">
{message.user?.slice(-8) || "N/A"}
</span>
<div className="text-sm truncate">{userName}</div>
</div>
</TableCell>
<TableCell>
<Badge
variant={isUser ? "default" : "secondary"}
className="flex items-center gap-1"
>
{isUser ? (
<User className="h-3 w-3" />
) : (
<Bot className="h-3 w-3" />
)}
{isUser ? "Utilisateur" : "Assistant"}
</Badge>
</TableCell>
<TableCell>
<div className="max-w-md">
<p className="text-sm truncate">
{content.length > 100
? `${content.substring(0, 100)}...`
: content}
</p>
</div>
</TableCell>
<TableCell>
{message.tokenCount > 0 && (
<Badge variant="outline" className="text-xs">
{message.tokenCount}
</Badge>
)}
</TableCell>
<TableCell>
<span className="text-sm text-muted-foreground">
{formatDate(new Date(message.createdAt))}
</span>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</div>
{/* Pagination */}
<div className="flex items-center justify-between space-x-2 py-4">
<div className="text-sm text-muted-foreground">
Page {page} sur {totalPages} ({total} éléments au total)
</div>
<div className="flex space-x-2">
<Button
variant="outline"
size="sm"
onClick={handlePrevPage}
disabled={page <= 1}
>
<ChevronLeft className="h-4 w-4" />
Précédent
</Button>
<Button
variant="outline"
size="sm"
onClick={handleNextPage}
disabled={page >= totalPages}
>
Suivant
<ChevronRight className="h-4 w-4" />
</Button>
</div>
</div>
</CardContent>
</Card>
);
}