This commit is contained in:
nBiqoz
2025-10-06 19:16:20 +02:00
parent 96dd721fcb
commit 0f2adca44a
23 changed files with 1569 additions and 248 deletions

View File

@@ -9,14 +9,23 @@ import {
CartesianGrid,
Tooltip,
ResponsiveContainer,
Cell,
} from "recharts";
interface ModelDistributionChartProps {
title: string;
subtitle?: string;
data: Array<{
name: string;
value: number;
color?: string;
models?: Array<{
name: string;
value: number;
}>;
}>;
showLegend?: boolean;
totalTokens?: number;
}
interface TooltipPayload {
@@ -24,6 +33,11 @@ interface TooltipPayload {
payload: {
name: string;
value: number;
color?: string;
models?: Array<{
name: string;
value: number;
}>;
};
}
@@ -32,63 +46,211 @@ interface CustomTooltipProps {
payload?: TooltipPayload[];
}
const CustomTooltip = ({ active, payload }: CustomTooltipProps) => {
if (active && payload && payload.length) {
return (
<div style={{
backgroundColor: "hsl(var(--background))",
border: "1px solid hsl(var(--border))",
borderRadius: "8px",
padding: "8px",
fontSize: "12px"
}}>
<p style={{ margin: 0, color: "#ff0000" }}>
{`${payload[0].value.toLocaleString()} tokens`}
</p>
<p style={{ margin: 0, color: "#ff0000" }}>
{payload[0].payload.name}
</p>
</div>
);
}
// Couleurs par fournisseur selon l'image
const providerColors: { [key: string]: string } = {
Anthropic: "#7C3AED", // Violet vif
OpenAI: "#059669", // Vert turquoise vif
"Mistral AI": "#D97706", // Orange vif
Meta: "#DB2777", // Rose/Magenta vif
Google: "#2563EB", // Bleu vif
Cohere: "#0891B2", // Cyan vif
};
// Fonction pour regrouper les modèles par fournisseur
const groupByProvider = (modelData: Array<{ name: string; value: number }>) => {
const providerMap: {
[key: string]: {
value: number;
models: Array<{ name: string; value: number }>;
};
} = {};
modelData.forEach((model) => {
let provider = "";
// Déterminer le fournisseur basé sur le nom du modèle
if (
model.name.toLowerCase().includes("claude") ||
model.name.toLowerCase().includes("anthropic")
) {
provider = "Anthropic";
} else if (
model.name.toLowerCase().includes("gpt") ||
model.name.toLowerCase().includes("openai")
) {
provider = "OpenAI";
} else if (model.name.toLowerCase().includes("mistral")) {
provider = "Mistral AI";
} else if (
model.name.toLowerCase().includes("llama") ||
model.name.toLowerCase().includes("meta")
) {
provider = "Meta";
} else if (
model.name.toLowerCase().includes("palm") ||
model.name.toLowerCase().includes("gemini") ||
model.name.toLowerCase().includes("google")
) {
provider = "Google";
} else if (model.name.toLowerCase().includes("cohere")) {
provider = "Cohere";
} else {
provider = "Autres";
}
if (!providerMap[provider]) {
providerMap[provider] = { value: 0, models: [] };
}
providerMap[provider].value += model.value;
providerMap[provider].models.push(model);
});
return Object.entries(providerMap).map(([name, data]) => ({
name,
value: data.value,
models: data.models,
color: providerColors[name] || "#6B7280",
}));
};
const CustomTooltip = () => {
return null;
};
export function ModelDistributionChart({
title,
subtitle,
data,
totalTokens,
}: ModelDistributionChartProps) {
// Si les données sont déjà groupées par fournisseur, les utiliser directement
// Sinon, les regrouper automatiquement
const groupedData = data[0]?.models ? data : groupByProvider(data);
// Créer une liste de tous les modèles avec leurs couleurs
const allModels = groupedData.flatMap((provider) =>
provider.models?.map((model) => ({
name: model.name,
color: provider.color,
value: model.value
})) || []
);
return (
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">
{title}
</CardTitle>
{subtitle && (
<p className="text-xs text-muted-foreground mt-1">{subtitle}</p>
)}
</CardHeader>
<CardContent className="pt-0">
<ResponsiveContainer width="100%" height={200}>
<BarChart data={data}>
<BarChart
data={groupedData}
margin={{ top: 10, right: 10, left: 10, bottom: 10 }}
>
<CartesianGrid strokeDasharray="3 3" className="stroke-muted/20" />
<XAxis
dataKey="name"
axisLine={false}
tickLine={false}
className="text-xs fill-muted-foreground"
tick={false}
angle={-45}
textAnchor="end"
height={60}
interval={0}
/>
<YAxis
axisLine={false}
tickLine={false}
className="text-xs fill-muted-foreground"
tickFormatter={(value) => {
if (value >= 1000000) return `${(value / 1000000).toFixed(1)}M`;
if (value >= 1000) return `${(value / 1000).toFixed(1)}K`;
return value.toString();
}}
/>
<Tooltip content={<CustomTooltip />} />
<Bar
dataKey="value"
fill="#000000"
radius={[4, 4, 0, 0]}
/>
<Bar dataKey="value" radius={[2, 2, 0, 0]}>
{groupedData.map((entry, index) => (
<Cell key={`cell-${index}`} fill={entry.color} />
))}
</Bar>
</BarChart>
</ResponsiveContainer>
{/* Petites cartes légères pour chaque provider */}
<div className="mt-4 grid grid-cols-2 gap-3">
{groupedData.map((item, index) => (
<div
key={index}
className="p-3 rounded-lg border border-muted/20 bg-muted/5 hover:bg-muted/10 transition-colors"
style={{
borderLeftColor: item.color,
borderLeftWidth: "3px",
borderLeftStyle: "solid",
}}
>
<div className="flex items-center gap-2 mb-1">
<div
className="w-2 h-2 rounded-full"
style={{ backgroundColor: item.color }}
></div>
<h3
className="text-sm font-medium"
style={{ color: item.color }}
>
{item.name}
</h3>
</div>
<p className="text-lg font-semibold text-foreground">
{item.value.toLocaleString()}
</p>
<p className="text-xs text-muted-foreground">tokens</p>
</div>
))}
</div>
{/* Total général */}
{totalTokens && (
<div className="mt-4 pt-3 border-t border-muted/20 text-center">
<p className="text-sm text-muted-foreground">
Total général:{" "}
<span className="font-semibold text-foreground">
{totalTokens.toLocaleString()}
</span>{" "}
tokens
</p>
</div>
)}
{/* Légende dynamique des modèles */}
{allModels.length > 0 && (
<div className="mt-4 pt-3 border-t border-muted/20">
<h4 className="text-sm font-medium text-muted-foreground mb-3 text-center">
Modèles utilisés
</h4>
<div className="flex flex-wrap justify-center gap-x-4 gap-y-2">
{allModels
.sort((a, b) => b.value - a.value) // Trier par usage décroissant
.map((model, index) => (
<div key={index} className="flex items-center gap-2">
<div
className="w-3 h-3 rounded-full flex-shrink-0"
style={{ backgroundColor: model.color }}
></div>
<span className="text-xs text-muted-foreground">
{model.name}
</span>
</div>
))}
</div>
</div>
)}
</CardContent>
</Card>
);

View File

@@ -44,12 +44,12 @@ export function RealUserActivityChart() {
{
name: "Utilisateurs actifs",
value: activity.activeUsers,
color: "#22c55e", // Vert clair pour actifs
color: "#000000", // Noir pour actifs
},
{
name: "Utilisateurs inactifs",
value: activity.inactiveUsers,
color: "#ef4444", // Rouge pour inactifs
color: "#666666", // Gris pour inactifs
},
];
@@ -62,7 +62,7 @@ export function RealUserActivityChart() {
Activité des utilisateurs
</CardTitle>
<p className="text-sm text-muted-foreground">
Actifs = connectés dans les 7 derniers jours
Actifs = connectés dans les 30 derniers jours
</p>
</CardHeader>
<CardContent>
@@ -74,8 +74,10 @@ export function RealUserActivityChart() {
cy="50%"
innerRadius={60}
outerRadius={100}
paddingAngle={5}
paddingAngle={2}
dataKey="value"
stroke="#ffffff"
strokeWidth={2}
>
{data.map((entry, index) => (
<Cell key={`cell-${index}`} fill={entry.color} />
@@ -86,17 +88,20 @@ export function RealUserActivityChart() {
backgroundColor: "hsl(var(--background))",
border: "1px solid hsl(var(--border))",
borderRadius: "8px",
fontSize: "12px"
}}
formatter={(value: number) => [
`${value} utilisateurs (${((value / total) * 100).toFixed(
1
)}%)`,
`${value} utilisateurs (${((value / total) * 100).toFixed(1)}%)`,
"",
]}
/>
<Legend
wrapperStyle={{
paddingTop: "20px",
fontSize: "12px"
}}
formatter={(value, entry) => (
<span style={{ color: entry.color }}>
<span style={{ color: entry.color, fontWeight: 500 }}>
{value}: {entry.payload?.value} (
{((entry.payload?.value / total) * 100).toFixed(1)}%)
</span>

View File

@@ -31,8 +31,8 @@ export function SimpleStatsChart({ title, data, color = "hsl(var(--primary))" }:
<AreaChart data={data}>
<defs>
<linearGradient id="colorGradient" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor={color} stopOpacity={0.3}/>
<stop offset="95%" stopColor={color} stopOpacity={0}/>
<stop offset="5%" stopColor={color} stopOpacity={0.8}/>
<stop offset="95%" stopColor={color} stopOpacity={0.2}/>
</linearGradient>
</defs>
<CartesianGrid strokeDasharray="3 3" className="stroke-muted/20" />
@@ -46,6 +46,11 @@ export function SimpleStatsChart({ title, data, color = "hsl(var(--primary))" }:
axisLine={false}
tickLine={false}
className="text-xs fill-muted-foreground"
tickFormatter={(value) => {
if (value >= 1000000) return `${(value / 1000000).toFixed(1)}M`;
if (value >= 1000) return `${(value / 1000).toFixed(1)}K`;
return value.toString();
}}
/>
<Tooltip
contentStyle={{
@@ -54,12 +59,16 @@ export function SimpleStatsChart({ title, data, color = "hsl(var(--primary))" }:
borderRadius: '8px',
fontSize: '12px'
}}
formatter={(value: number) => [
value >= 1000 ? `${(value / 1000).toFixed(1)}K tokens` : `${value} tokens`,
'Tokens consommés'
]}
/>
<Area
type="monotone"
dataKey="value"
stroke={color}
strokeWidth={2}
strokeWidth={3}
fill="url(#colorGradient)"
/>
</AreaChart>

View File

@@ -20,12 +20,12 @@ export function UserActivityChart({ activeUsers, inactiveUsers }: UserActivityCh
{
name: 'Utilisateurs actifs',
value: activeUsers,
color: '#22c55e' // Vert clair pour actifs
color: '#000000' // Noir pour actifs
},
{
name: 'Utilisateurs inactifs',
value: inactiveUsers,
color: '#ef4444' // Rouge pour inactifs
color: '#666666' // Gris pour inactifs
},
];
@@ -36,7 +36,7 @@ export function UserActivityChart({ activeUsers, inactiveUsers }: UserActivityCh
<CardHeader>
<CardTitle className="text-base font-medium">Activité des utilisateurs</CardTitle>
<p className="text-sm text-muted-foreground">
Actifs = connectés dans les 7 derniers jours
Actifs = connectés dans les 30 derniers jours
</p>
</CardHeader>
<CardContent>
@@ -48,8 +48,10 @@ export function UserActivityChart({ activeUsers, inactiveUsers }: UserActivityCh
cy="50%"
innerRadius={60}
outerRadius={100}
paddingAngle={5}
paddingAngle={2}
dataKey="value"
stroke="#ffffff"
strokeWidth={2}
>
{data.map((entry, index) => (
<Cell key={`cell-${index}`} fill={entry.color} />
@@ -60,16 +62,22 @@ export function UserActivityChart({ activeUsers, inactiveUsers }: UserActivityCh
backgroundColor: 'hsl(var(--background))',
border: '1px solid hsl(var(--border))',
borderRadius: '8px',
fontSize: '12px'
}}
formatter={(value: number) => [
`${value} utilisateurs (${((value / total) * 100).toFixed(1)}%)`,
''
'',
]}
/>
<Legend
<Legend
wrapperStyle={{
paddingTop: "20px",
fontSize: "12px"
}}
formatter={(value, entry) => (
<span style={{ color: entry.color }}>
{value}: {entry.payload?.value} ({((entry.payload?.value / total) * 100).toFixed(1)}%)
<span style={{ color: entry.color, fontWeight: 500 }}>
{value}: {entry.payload?.value} (
{((entry.payload?.value / total) * 100).toFixed(1)}%)
</span>
)}
/>