Some checks are pending
E2E / Playwright e2e (push) Waiting to run
- Replaced hardcoded "Agenda" labels with dynamic ULTICAL_APP_NAME in various components for consistency. - Introduced new AiUsageSection and CompteAiUsageSection components to track AI usage and costs. - Updated settings and metadata to reflect changes in AI cost policies and usage limits. - Enhanced user interface elements for better accessibility and user experience across admin settings.
85 lines
2.6 KiB
TypeScript
85 lines
2.6 KiB
TypeScript
"use client"
|
|
|
|
import Link from "next/link"
|
|
import {
|
|
aiQuotaUsedMonthPct,
|
|
formatAiCostEUR,
|
|
type AiQuota,
|
|
} from "@/lib/api/hooks/use-ai-queries"
|
|
import { cn } from "@/lib/utils"
|
|
|
|
export function AiSpendBar({
|
|
quota,
|
|
compact = false,
|
|
className,
|
|
}: {
|
|
quota: AiQuota
|
|
compact?: boolean
|
|
className?: string
|
|
}) {
|
|
const pct = aiQuotaUsedMonthPct(quota)
|
|
const warnPct = quota.warn_threshold_pct || 80
|
|
const nearLimit = pct != null && pct >= warnPct
|
|
const isBYOK = !quota.billing_scope_org
|
|
|
|
if (isBYOK) {
|
|
const byokTotal = (quota.by_provider_keys ?? []).reduce(
|
|
(sum, k) => sum + k.cost_month_micro_eur,
|
|
quota.cost_used_month_micro_eur
|
|
)
|
|
return (
|
|
<div className={cn("space-y-1", className)}>
|
|
<div className="flex items-center justify-between gap-2 text-xs text-muted-foreground">
|
|
<span>Clé personnelle</span>
|
|
<span className="tabular-nums">{formatAiCostEUR(byokTotal)} / mois</span>
|
|
</div>
|
|
{!compact && (quota.by_provider_keys?.length ?? 0) > 0 ? (
|
|
<div className="space-y-0.5 pl-2 text-[10px] text-muted-foreground">
|
|
{quota.by_provider_keys!.slice(0, 3).map((k) => (
|
|
<div key={k.fingerprint} className="flex justify-between gap-2">
|
|
<span className="truncate">{k.label}</span>
|
|
<span className="tabular-nums shrink-0">{formatAiCostEUR(k.cost_month_micro_eur)}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className={cn("space-y-1", className)}>
|
|
<div className="flex items-center justify-between gap-2 text-xs text-muted-foreground">
|
|
<span className={cn(nearLimit && "text-amber-600 dark:text-amber-400")}>
|
|
IA {nearLimit ? "· proche du plafond" : ""}
|
|
</span>
|
|
<span className="tabular-nums">
|
|
{formatAiCostEUR(quota.cost_used_month_micro_eur)}
|
|
{quota.cost_limit_month_micro_eur
|
|
? ` / ${formatAiCostEUR(quota.cost_limit_month_micro_eur)}`
|
|
: ""}
|
|
</span>
|
|
</div>
|
|
{pct != null ? (
|
|
<div className="h-1.5 overflow-hidden rounded-full bg-muted">
|
|
<div
|
|
className={cn(
|
|
"h-full rounded-full transition-all",
|
|
nearLimit ? "bg-amber-500" : "bg-primary"
|
|
)}
|
|
style={{ width: `${pct}%` }}
|
|
/>
|
|
</div>
|
|
) : null}
|
|
{!compact ? (
|
|
<Link
|
|
href="/compte/usage-ia"
|
|
className="text-[10px] text-muted-foreground underline-offset-2 hover:underline"
|
|
>
|
|
Détail de consommation
|
|
</Link>
|
|
) : null}
|
|
</div>
|
|
)
|
|
}
|