/*
Copyright (C) 2023-2026 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
For commercial licensing, please contact support@quantumnous.com
*/
import { useQuery } from '@tanstack/react-query'
import { AlertTriangle, HeartPulse, Timer } from 'lucide-react'
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import {
StaticDataTable,
staticDataTableClassNames as tableStyles,
} from '@/components/data-table'
import { GroupBadge } from '@/components/group-badge'
import { getPerfMetrics } from '@/features/performance-metrics/api'
import {
formatLatency,
formatThroughput,
formatUptimePct,
getSuccessRateTextClass,
} from '@/features/performance-metrics/lib/format'
import type { PerformanceGroup } from '@/features/performance-metrics/types'
import { cn } from '@/lib/utils'
import { type UptimeDayPoint } from '../lib/mock-stats'
import type { PricingModel } from '../types'
import { LatencyTrendChart, UptimeTrendChart } from './model-details-charts'
import { UptimeSparkline } from './model-details-uptime-sparkline'
function StatCard(props: {
icon: React.ComponentType<{ className?: string }>
label: string
value: React.ReactNode
hint?: string
valueClassName?: string
}) {
const Icon = props.icon
return (
{props.label}
{props.value}
{props.hint && (
{props.hint}
)}
)
}
type PerformanceRow = {
group: string
avg_ttft_ms: number
avg_latency_ms: number
success_rate: number
avg_tps: number
}
function toUptimePct(value: number): number {
if (!Number.isFinite(value)) return 0
const clamped = Math.min(100, Math.max(0, value))
return Math.round(clamped * 100) / 100
}
function toLatencySeries(groups: PerformanceGroup[]) {
const byTs = new Map()
for (const group of groups) {
for (const point of group.series) {
if (point.avg_ttft_ms <= 0) continue
const current = byTs.get(point.ts) ?? []
current.push(point.avg_ttft_ms)
byTs.set(point.ts, current)
}
}
return Array.from(byTs.entries())
.sort(([a], [b]) => a - b)
.map(([ts, values]) => ({
timestamp: new Date(ts * 1000).toISOString(),
group: 'latency',
ttft_ms: Math.round(
values.reduce((sum, value) => sum + value, 0) / values.length
),
}))
}
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) ?? { rates: [], incidents: 0 }
if (Number.isFinite(point.success_rate)) {
const successRate = toUptimePct(point.success_rate)
current.rates.push(successRate)
if (successRate < 100) current.incidents += 1
}
byTs.set(point.ts, current)
}
}
return Array.from(byTs.entries())
.sort(([a], [b]) => a - b)
.map(([ts, value]) => {
const uptime =
value.rates.length > 0
? value.rates.reduce((sum, rate) => sum + rate, 0) /
value.rates.length
: 0
return {
date: new Date(ts * 1000).toISOString(),
uptime_pct: toUptimePct(uptime),
incidents: value.incidents,
outage_minutes: 0,
}
})
}
function toGroupUptimeSeries(group: PerformanceGroup): UptimeDayPoint[] {
return group.series.map((point) => {
const successRate = toUptimePct(point.success_rate)
return {
date: new Date(point.ts * 1000).toISOString(),
uptime_pct: successRate,
incidents: successRate < 100 ? 1 : 0,
outage_minutes: 0,
}
})
}
function average(
rows: PerformanceRow[],
field: 'avg_ttft_ms' | 'avg_latency_ms'
) {
const values = rows.map((row) => row[field]).filter((value) => value > 0)
if (values.length === 0) return 0
return Math.round(
values.reduce((sum, value) => sum + value, 0) / values.length
)
}
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 = useMemo(
() => metricsQuery.data?.data.groups ?? [],
[metricsQuery.data]
)
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,
avg_tps: group.avg_tps,
})),
[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 tpsValues = performances
.map((p) => p.avg_tps)
.filter((value) => value > 0)
const avgTps =
tpsValues.length > 0
? tpsValues.reduce((sum, value) => sum + value, 0) / tpsValues.length
: 0
const avgLatency = average(performances, 'avg_latency_ms')
const successRates = performances
.map((perf) => perf.success_rate)
.filter((value) => Number.isFinite(value))
const successRate =
successRates.length > 0
? successRates.reduce((sum, value) => sum + value, 0) /
successRates.length
: 0
const incidentCount = uptimeSeries.reduce((s, p) => s + p.incidents, 0)
return (
0
? t('{{count}} incidents in the last 24 hours', {
count: incidentCount,
})
: t('No incidents in the last 24 hours')
}
valueClassName={getSuccessRateTextClass(successRate)}
/>
perf.group}
columns={[
{
id: 'group',
header: t('Group'),
className: tableStyles.compactHeaderCell,
cellClassName: tableStyles.compactCell,
cell: (perf) => ,
},
{
id: 'tps',
header: 'TPS',
className: tableStyles.compactHeaderCellRight,
cellClassName: tableStyles.compactNumericCell,
cell: (perf) => formatThroughput(perf.avg_tps),
},
{
id: 'ttft',
header: t('Average TTFT'),
className: tableStyles.compactHeaderCellRight,
cellClassName: tableStyles.compactNumericCell,
cell: (perf) => formatLatency(perf.avg_ttft_ms),
},
{
id: 'latency',
header: t('Average latency'),
className: tableStyles.compactHeaderCellRight,
cellClassName: tableStyles.compactMutedNumericCell,
cell: (perf) => formatLatency(perf.avg_latency_ms),
},
{
id: 'success',
header: t('Success rate'),
className: cn(tableStyles.compactHeaderCell, 'min-w-[180px]'),
cellClassName: tableStyles.compactCell,
cell: (perf) => (
),
},
]}
/>
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}
)}
)
}