import { useMemo } from 'react' import { useQuery } from '@tanstack/react-query' import { Activity, AlertTriangle, HeartPulse, Timer, TrendingUp, } from 'lucide-react' import { useTranslation } from 'react-i18next' import { cn } from '@/lib/utils' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table' import { GroupBadge } from '@/components/group-badge' import { getPerfMetrics, type PerformanceGroup } from '../api' import { formatLatency, formatUptimePct, type UptimeDayPoint, } from '../lib/mock-stats' import type { PricingModel } from '../types' import { LatencyTrendChart, UptimeBarChart } from './model-details-charts' import { UptimeSparkline } from './model-details-uptime-sparkline' const COMPACT_NUMBER = new Intl.NumberFormat(undefined, { notation: 'compact', maximumFractionDigits: 1, }) function StatCard(props: { icon: React.ComponentType<{ className?: string }> label: string value: React.ReactNode hint?: string intent?: 'default' | 'warning' | 'success' }) { const Icon = props.icon const intent = props.intent ?? 'default' return (
{props.label} {props.value} {props.hint && ( {props.hint} )}
) } type PerformanceRow = { group: string avg_ttft_ms: number avg_latency_ms: number success_rate: number request_count: number } function toLatencySeries(groups: PerformanceGroup[]) { return groups.flatMap((group) => group.series .filter((point) => point.ttft_count > 0 && point.avg_ttft_ms > 0) .map((point) => ({ timestamp: new Date(point.ts * 1000).toISOString(), group: group.group, ttft_ms: point.avg_ttft_ms, })) ) } function toUptimeSeries(groups: PerformanceGroup[]): UptimeDayPoint[] { const byTs = new Map() for (const group of groups) { for (const point of group.series) { const current = byTs.get(point.ts) ?? { count: 0, success: 0 } current.count += point.count current.success += point.success_count byTs.set(point.ts, current) } } return Array.from(byTs.entries()) .sort(([a], [b]) => a - b) .map(([ts, value]) => { const uptime = value.count > 0 ? (value.success / value.count) * 100 : 0 return { date: new Date(ts * 1000).toISOString(), uptime_pct: Math.round(uptime * 100) / 100, incidents: value.success < value.count ? 1 : 0, outage_minutes: 0, } }) } function toGroupUptimeSeries(group: PerformanceGroup): UptimeDayPoint[] { return group.series.map((point) => ({ date: new Date(point.ts * 1000).toISOString(), uptime_pct: Math.round(point.success_rate * 100) / 100, incidents: point.success_count < point.count ? 1 : 0, outage_minutes: 0, })) } function weightedAverage( rows: PerformanceRow[], field: 'avg_ttft_ms' | 'avg_latency_ms' ): number { let total = 0 let count = 0 for (const row of rows) { if (row[field] <= 0 || row.request_count <= 0) continue total += row[field] * row.request_count count += row.request_count } return count > 0 ? Math.round(total / count) : 0 } export function ModelDetailsPerformance(props: { model: PricingModel }) { const { t } = useTranslation() const metricsQuery = useQuery({ queryKey: ['perf-metrics', props.model.model_name], queryFn: () => getPerfMetrics(props.model.model_name, 24), staleTime: 60 * 1000, }) const groups = metricsQuery.data?.data.groups ?? [] const performances = useMemo( () => groups.map((group) => ({ group: group.group, avg_ttft_ms: group.avg_ttft_ms, avg_latency_ms: group.avg_latency_ms, success_rate: group.success_rate, request_count: group.request_count, })), [groups] ) const latencySeries = useMemo(() => toLatencySeries(groups), [groups]) const uptimeSeries = useMemo(() => toUptimeSeries(groups), [groups]) const uptimeByGroup = useMemo>(() => { const map: Record = {} for (const group of groups) { map[group.group] = toGroupUptimeSeries(group) } return map }, [groups]) if (metricsQuery.isLoading || performances.length === 0) { return (
{t('Performance data is not yet available for this model.')}
) } const ttftValues = performances .map((p) => p.avg_ttft_ms) .filter((value) => value > 0) const bestTtft = ttftValues.length > 0 ? Math.min(...ttftValues) : 0 const avgLatency = weightedAverage(performances, 'avg_latency_ms') const totalRequests = performances.reduce((s, p) => s + p.request_count, 0) const totalSuccess = groups.reduce((s, p) => s + p.success_count, 0) const successRate = totalRequests > 0 ? (totalSuccess / totalRequests) * 100 : 0 const incidentCount = uptimeSeries.reduce((s, p) => s + p.incidents, 0) let intent: 'default' | 'warning' | 'success' = 'warning' if (successRate >= 99.9) { intent = 'success' } else if (successRate >= 99) { intent = 'default' } const headerCellClass = 'text-muted-foreground py-2 text-[10px] font-medium tracking-wider uppercase' return (
0 ? t('{{count}} incidents in the last 24 hours', { count: incidentCount, }) : t('No incidents in the last 24 hours') } intent={intent} />
{t('Group')} {t('Average TTFT')} {t('Average latency')} {t('Success rate')} {t('Request Count')} {performances.map((perf) => { const isBestTtft = perf.avg_ttft_ms === bestTtft return ( {formatLatency(perf.avg_ttft_ms)} {formatLatency(perf.avg_latency_ms)} {COMPACT_NUMBER.format(perf.request_count)} ) })}
0 ? t( 'Request success rate; {{incidents}} incident buckets in the last 24 hours', { incidents: incidentCount, } ) : t('Request success rate sampled over the last 24 hours') } accent={ incidentCount > 0 ? ( {t('{{count}} incidents', { count: incidentCount, })} ) : null } />
) } function SectionHeader(props: { icon: React.ComponentType<{ className?: string }> title: string description?: string accent?: React.ReactNode }) { const Icon = props.icon return (
{props.title}
{props.description && (

{props.description}

)}
{props.accent && (
{props.accent}
)}
) }