"use client"; import { ArrowDownIcon, ArrowUpIcon } from "lucide-react"; import { useTranslations } from "next-intl"; import { Card, CardContent } from "../../../shadcnui"; import { cn } from "../../../utils"; import type { TokenUsageReportSummaryInterface } from "../data/tokenusage-report-summary.interface"; import type { ReportMetric } from "../data/tokenusage-report.types"; import { useUsageFormatters } from "../lib/formatters"; import { metricValue, percentageDelta, type TokenUsageMetrics } from "../lib/metrics"; /** * The caller's own credit position, as the host app reads it from its * CurrentUserContext. The package has no access to that context, so the numbers * arrive as a prop. */ export type TokenUsageBalances = { monthlyCredits: number; availableMonthlyCredits: number; availableExtraCredits: number; }; type Props = { /** Two rows: window "current" and "previous". */ summary: TokenUsageReportSummaryInterface[]; metric: ReportMetric; /** The caller's own balances, read from CurrentUserContext by the container. */ balances: TokenUsageBalances | null; }; const ZERO: TokenUsageMetrics = { cost: 0, credits: 0, tokensIn: 0, tokensOut: 0, cached: 0, calls: 0 }; /** * The KPI header of the self-service token-usage page. * * One lead tile carries the number the page exists to answer — how much was * spent in the period — with its delta against the equal-length preceding * window. Three supporting tiles give it context: what is left this month, what * extra sits behind that, and how many calls produced the spend. * * Credits are fractional. Customer-facing BALANCES are floored to whole credits * (Math.max(0, Math.floor(v))) so nobody is told they have 715.4 of something * indivisible; the percentage arithmetic behind the colour keeps the raw floats. * Spend is NOT floored — it is a measurement, not a wallet. */ export function TokenUsageReportTiles({ summary, metric, balances }: Props) { const t = useTranslations(); const { decimal, metricValue: formatValue } = useUsageFormatters(); const rowFor = (window: string): TokenUsageMetrics => summary.find((row) => row.window === window) ?? ZERO; const current = rowFor("current"); const previous = rowFor("previous"); const monthlyPercentage = balances && balances.monthlyCredits > 0 ? (balances.availableMonthlyCredits / balances.monthlyCredits) * 100 : 0; // Three bands, not four: the previous implementation had a dead branch where // >= 25 and >= 5 both returned the same class. const monthlyColor = monthlyPercentage > 75 ? "text-success" : monthlyPercentage >= 5 ? "text-warning" : "text-destructive"; const whole = (value: number) => decimal(Math.max(0, Math.floor(value)), 0); return (