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>
);