"use client" import Link from "next/link" import { useMemo } from "react" import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts" import type { ComponentType } from "react" import { CalendarDays, Coins, Gauge, TrendingUp } from "lucide-react" import { SettingsSectionHeader } from "@/components/gmail/settings/settings-section-header" import { SettingsSyncBanner } from "@/components/gmail/settings/settings-sync-banner" import { SettingsCard } from "@/components/settings/settings-kit" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { ChartContainer, ChartTooltip, ChartTooltipContent, type ChartConfig, } from "@/components/ui/chart" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table" import { useAdminAIUsage, useAdminAIPricing, } from "@/lib/api/hooks/use-admin-ai-usage" import { formatAiCostEUR } from "@/lib/api/hooks/use-ai-queries" import { cn } from "@/lib/utils" const chartConfig = { cost: { label: "Coût org", color: "hsl(var(--primary))", }, } satisfies ChartConfig function formatPricePerMTok(eur: number): string { return new Intl.NumberFormat("fr-FR", { style: "currency", currency: "EUR", minimumFractionDigits: 2, maximumFractionDigits: 2, }).format(eur) } function formatShortDate(isoDate: string): string { const date = new Date(`${isoDate}T12:00:00`) return date.toLocaleDateString("fr-FR", { day: "numeric", month: "short" }) } function StatCard({ label, value, hint, icon: Icon, }: { label: string value: string hint?: string icon: ComponentType<{ className?: string }> }) { return (

{label}

{value}

{hint ?

{hint}

: null}
) } function RankedRow({ rank, label, sublabel, value, sharePct, }: { rank: number label: string sublabel?: string value: string sharePct: number }) { return (
{rank}

{label}

{sublabel ? (

{sublabel}

) : null}
{value}
) } function EmptyPanel({ message }: { message: string }) { return (

{message}

) } export function AiUsageSection() { const { data, isFetching, isError, refetch } = useAdminAIUsage("org") const { data: pricing } = useAdminAIPricing() const dailySeries = data?.daily_series ?? [] const topUsers = data?.top_users ?? [] const topModels = data?.top_models ?? [] const pricingRows = pricing ?? [] const chartData = useMemo( () => dailySeries.map((day) => ({ date: day.date, label: formatShortDate(day.date), cost: day.cost_org_micro_eur / 1_000_000, requests: day.requests, })), [dailySeries] ) const maxUserCost = useMemo( () => Math.max(...topUsers.map((u) => u.cost_org_micro_eur), 1), [topUsers] ) const maxModelCost = useMemo( () => Math.max(...topModels.map((m) => m.cost_micro_eur), 1), [topModels] ) return ( <> refetch()} />
{data ? (
{dailySeries.length > 0 ? ( new Intl.NumberFormat("fr-FR", { style: "currency", currency: "EUR", notation: "compact", maximumFractionDigits: 1, }).format(v) } /> { const row = payload?.[0]?.payload as { date?: string } | undefined return row?.date ? new Date(`${row.date}T12:00:00`).toLocaleDateString("fr-FR", { weekday: "short", day: "numeric", month: "long", }) : "" }} formatter={(value, _name, item) => { const requests = (item.payload as { requests?: number }).requests ?? 0 return (
{chartConfig.cost.label} {formatAiCostEUR(Number(value) * 1_000_000)} {requests > 0 ? ` · ${requests} req.` : ""}
) }} /> } />
) : null}
{topUsers.length === 0 ? ( ) : (
{topUsers.map((u, index) => ( ))}
)}
{topModels.length === 0 ? ( ) : (
{topModels.map((m, index) => ( 1 ? "s" : ""}`} value={formatAiCostEUR(m.cost_micro_eur)} sharePct={Math.round((m.cost_micro_eur / maxModelCost) * 100)} /> ))}
)}
{pricingRows.length > 0 ? (
Modèle Input / 1M Output / 1M {pricingRows.map((p) => (
{p.model_id} {p.provider_type ? ( {p.provider_type} ) : null}
{formatPricePerMTok(p.input_eur_per_mtok)} {formatPricePerMTok(p.output_eur_per_mtok)}
))}
) : null}
) : isFetching ? (

Chargement de la consommation…

) : null} ) }