/* 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 { useNavigate, useParams, useSearch } from '@tanstack/react-router' import { ArrowLeft, CalendarClock, Code2, FileText, HeartPulse, Info, Layers, Maximize2, Sparkles, } from 'lucide-react' import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import { CopyButton } from '@/components/copy-button' import { StaticDataTable } from '@/components/data-table' import { Button } from '@/components/design-system/button' import { Tabs, TabsContent, TabsList, TabsTrigger, } from '@/components/design-system/tabs' import { GroupBadge } from '@/components/group-badge' import { PublicLayout } from '@/components/layout' import { PageTransition } from '@/components/page-transition' import { StatusBadge } from '@/components/status-badge' import { Skeleton } from '@/components/ui/skeleton' import { getPerfMetrics } from '@/features/performance-metrics/api' import { formatLatency, formatThroughput, formatUptimePct, getSuccessRateTextClass, } from '@/features/performance-metrics/lib/format' import { getLobeIcon } from '@/lib/lobe-icon' import { cn } from '@/lib/utils' import { DEFAULT_TOKEN_UNIT, QUOTA_TYPE_VALUES } from '../constants' import { usePricingData } from '../hooks/use-pricing-data' import { getDynamicPriceEntries, getDynamicPricingSummary, getDynamicPricingTiers, isDynamicPricingModel, } from '../lib/dynamic-price' import { parseTags } from '../lib/filters' import { getAvailableGroups, isTokenBasedModel } from '../lib/model-helpers' import { formatFixedPrice, formatGroupPrice } from '../lib/price' import type { ModelCapability, PriceType, PricingModel, TokenUnit, } from '../types' import { DynamicPricingBreakdown } from './dynamic-pricing-breakdown' import { ModelDetailsApi } from './model-details-api' import { ModelDetailsPerformance } from './model-details-performance' // ---------------------------------------------------------------------------- // Local UI helpers // ---------------------------------------------------------------------------- function SectionTitle(props: { children: React.ReactNode description?: string }) { return (

{props.children}

{props.description && (

{props.description}

)}
) } const CAPABILITY_LABEL_KEYS: Record = { function_calling: 'Function calling', streaming: 'Streaming', vision: 'Vision', json_mode: 'JSON mode', structured_output: 'Structured output', reasoning: 'Reasoning', tools: 'Tools', system_prompt: 'System prompt', web_search: 'Web search', code_interpreter: 'Code interpreter', caching: 'Prompt caching', embeddings: 'Embeddings', } const MODALITY_LABEL_KEYS: Record = { text: 'Text', image: 'Image', audio: 'Audio', video: 'Video', file: 'File', } const TOKEN_FORMAT = new Intl.NumberFormat(undefined, { maximumFractionDigits: 1, }) function formatCatalogTokenCount(tokens: number): string { if (!Number.isFinite(tokens) || tokens <= 0) return '' if (tokens >= 1_000_000) { return `${TOKEN_FORMAT.format(tokens / 1_000_000)}M` } if (tokens >= 1_000) { return `${TOKEN_FORMAT.format(tokens / 1_000)}K` } return TOKEN_FORMAT.format(tokens) } function formatCatalogYearMonth(value?: string): string { if (!value) return '' const [yearStr, monthStr] = value.split('-') const year = Number(yearStr) const month = Number(monthStr) if (!Number.isFinite(year) || !Number.isFinite(month)) return value const date = new Date(Date.UTC(year, month - 1, 1)) return date.toLocaleString(undefined, { year: 'numeric', month: 'short' }) } function normalizeCatalogItems(items?: readonly string[]): string[] { if (!items) return [] return items.filter((item) => item.trim().length > 0) } function OverviewMetric(props: { label: string value: React.ReactNode valueClassName?: string }) { return (
{props.label}
{props.value}
) } function OverviewSummaryGrid(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 successRates = groups .map((group) => group.success_rate) .filter((rate) => Number.isFinite(rate)) const successRate = successRates.length > 0 ? successRates.reduce((sum, rate) => sum + rate, 0) / successRates.length : Number.NaN const tpsValues = groups .map((group) => group.avg_tps) .filter((value) => value > 0) const avgTps = tpsValues.length > 0 ? tpsValues.reduce((sum, value) => sum + value, 0) / tpsValues.length : 0 const latencyValues = groups .map((group) => group.avg_latency_ms) .filter((value) => value > 0) const avgLatency = latencyValues.length > 0 ? Math.round( latencyValues.reduce((sum, value) => sum + value, 0) / latencyValues.length ) : 0 return (
) } function CatalogPillList(props: { items: string[] }) { return (
{props.items.map((item) => ( {item} ))}
) } function CatalogTextValue(props: { children: React.ReactNode }) { return ( {props.children} ) } function CatalogInfoCell(props: { label: string; children: React.ReactNode }) { return (
{props.label} {props.children}
) } function ModalityLabels(props: { items: string[] }) { const { t } = useTranslation() if (props.items.length === 0) return null return ( {props.items.map((item) => ( {t(MODALITY_LABEL_KEYS[item] ?? item)} ))} ) } function ModelBackendQuickStats(props: { model: PricingModel }) { const { t } = useTranslation() const model = props.model const inputModalities = normalizeCatalogItems(model.input_modalities) const outputModalities = normalizeCatalogItems(model.output_modalities) const contextLength = model.context_length ?? 0 const maxOutput = model.max_output_tokens ?? 0 const knowledgeCutoff = formatCatalogYearMonth(model.knowledge_cutoff) const releaseDate = formatCatalogYearMonth(model.release_date) const stats: { key: string icon: React.ComponentType<{ className?: string }> label: string value: React.ReactNode hint?: string }[] = [] if (contextLength > 0) { stats.push({ key: 'context', icon: Layers, label: t('Context'), value: formatCatalogTokenCount(contextLength), hint: t('Maximum input window'), }) } if (maxOutput > 0) { stats.push({ key: 'max-output', icon: Maximize2, label: t('Max output'), value: formatCatalogTokenCount(maxOutput), hint: t('Maximum tokens per response'), }) } if (inputModalities.length > 0 || outputModalities.length > 0) { stats.push({ key: 'modalities', icon: FileText, label: t('Modalities'), value: ( {inputModalities.length > 0 && outputModalities.length > 0 && ( โ†’ )} ), }) } if (knowledgeCutoff) { stats.push({ key: 'knowledge', icon: Sparkles, label: t('Knowledge cutoff'), value: knowledgeCutoff, }) } if (releaseDate) { stats.push({ key: 'release', icon: CalendarClock, label: t('Released'), value: releaseDate, }) } if (stats.length === 0) return null return (
{stats.map((stat) => { const Icon = stat.icon return (
{stat.label} {stat.value} {stat.hint && ( {stat.hint} )}
) })}
) } function ModelBackendSignalsSection(props: { model: PricingModel }) { const { t } = useTranslation() const capabilities = normalizeCatalogItems(props.model.capabilities) const inputModalities = normalizeCatalogItems(props.model.input_modalities) const outputModalities = normalizeCatalogItems(props.model.output_modalities) if ( capabilities.length === 0 && inputModalities.length === 0 && outputModalities.length === 0 ) { return null } return (
{t('Capabilities')}
{capabilities.length > 0 && ( t( CAPABILITY_LABEL_KEYS[capability as ModelCapability] ?? capability ) )} /> )} {(inputModalities.length > 0 || outputModalities.length > 0) && (
{inputModalities.length > 0 && (
{t('Input')}
)} {outputModalities.length > 0 && (
{t('Output')}
)}
)}
) } function ModelBackendProviderSection(props: { model: PricingModel }) { const { t } = useTranslation() const model = props.model const groups = normalizeCatalogItems(model.enable_groups) const endpoints = normalizeCatalogItems(model.supported_endpoint_types) const tags = parseTags(model.tags) const cells: React.ReactNode[] = [] if (model.vendor_name) { cells.push( {model.vendor_name} ) } cells.push( {model.quota_type === QUOTA_TYPE_VALUES.TOKEN ? t('Token-based') : t('Per Request')} ) if (groups.length > 0) { cells.push(
{groups.map((group) => ( ))}
) } if (endpoints.length > 0) { cells.push( ) } if (tags.length > 0) { cells.push( ) } if (model.parameter_count) { cells.push( {model.parameter_count} ) } if (cells.length === 0) return null return (
{t('Model')}
{cells}
) } function ModelBackendDetailsSection(props: { model: PricingModel }) { return ( <> ) } // ---------------------------------------------------------------------------- // Model header (always visible above the detail sections) // ---------------------------------------------------------------------------- function ModelHeader(props: { model: PricingModel }) { const { t } = useTranslation() const model = props.model const modelIconKey = model.icon || model.vendor_icon const modelIcon = modelIconKey ? getLobeIcon(modelIconKey, 28) : null const description = model.description || model.vendor_description || null const tags = parseTags(model.tags) const endpoints = normalizeCatalogItems(model.supported_endpoint_types) const isSpecialExpression = model.billing_mode === 'tiered_expr' && Boolean(model.billing_expr) && getDynamicPricingTiers(model).length === 0 return (
{modelIcon || ( {model.model_name?.charAt(0).toUpperCase() || '?'} )}

{model.model_name}

{model.billing_mode === 'tiered_expr' && model.billing_expr && ( {isSpecialExpression ? t('Special billing expression') : t('Dynamic Pricing')} )}
{model.vendor_name && {model.vendor_name}} {model.vendor_name && ( ยท )} {model.quota_type === QUOTA_TYPE_VALUES.TOKEN ? t('Token-based') : t('Per Request')}
{description && (

{description}

)} {(tags.length > 0 || endpoints.length > 0) && (
{tags.map((tag) => ( {tag} ))} {endpoints.map((endpoint) => ( {endpoint} ))}
)}
) } // ---------------------------------------------------------------------------- // Base price card (used in the Overview tab) // ---------------------------------------------------------------------------- function PriceSection(props: { model: PricingModel priceRate: number usdExchangeRate: number tokenUnit: TokenUnit showRechargePrice: boolean }) { const { t } = useTranslation() const isTokenBased = isTokenBasedModel(props.model) const tokenUnitLabel = props.tokenUnit === 'K' ? '1K' : '1M' const baseGroupKey = '_base' const baseGroupRatioMap = { [baseGroupKey]: 1 } const dynamicSummary = getDynamicPricingSummary(props.model, { tokenUnit: props.tokenUnit, showRechargePrice: props.showRechargePrice, priceRate: props.priceRate, usdExchangeRate: props.usdExchangeRate, groupRatioMultiplier: 1, }) const primaryPriceTypes: { label: string; type: PriceType }[] = [ { label: t('Input'), type: 'input' }, { label: t('Output'), type: 'output' }, ] const secondaryPriceTypes: { label: string type: PriceType available: boolean }[] = [ { label: t('Cached input'), type: 'cache', available: props.model.cache_ratio != null, }, { label: t('Cache write'), type: 'create_cache', available: props.model.create_cache_ratio != null, }, { label: t('Image input'), type: 'image', available: props.model.image_ratio != null, }, { label: t('Audio input'), type: 'audio_input', available: props.model.audio_ratio != null, }, { label: t('Audio output'), type: 'audio_output', available: props.model.audio_ratio != null && props.model.audio_completion_ratio != null, }, ] if (dynamicSummary) { if (dynamicSummary.isSpecialExpression) { return (
{t('Pricing')}
{t('Special billing expression')}

{t('Unable to parse structured pricing')}

{t('Raw expression')}
{dynamicSummary.rawExpression}
) } const priceRows = [ ...dynamicSummary.primaryEntries, ...dynamicSummary.secondaryEntries, ] return (
{t('Pricing')}
{t('Text tokens')} {t('Prices shown per')} {tokenUnitLabel} {t('tokens')}
{priceRows.map((entry) => (
{t(entry.shortLabel)} {entry.formatted}
))}
) } if (!isTokenBased) { return (
{t('Pricing')}
{t('Per request')} {formatFixedPrice( props.model, baseGroupKey, props.showRechargePrice, props.priceRate, props.usdExchangeRate, baseGroupRatioMap )}
) } const secondaryItems = secondaryPriceTypes.filter((p) => p.available) const priceRows = [ ...primaryPriceTypes, ...secondaryItems.map((item) => ({ label: item.label, type: item.type, })), ] return (
{t('Pricing')}
{t('Text tokens')} {t('Prices shown per')} {tokenUnitLabel} {t('tokens')}
{priceRows.map((item) => (
{item.label} {formatGroupPrice( props.model, baseGroupKey, item.type, props.tokenUnit, props.showRechargePrice, props.priceRate, props.usdExchangeRate, baseGroupRatioMap )}
))}
) } // ---------------------------------------------------------------------------- // Auto group chain (used inside group pricing section) // ---------------------------------------------------------------------------- function AutoGroupChain(props: { model: PricingModel; autoGroups: string[] }) { const { t } = useTranslation() const modelEnableGroups = Array.isArray(props.model.enable_groups) ? props.model.enable_groups : [] const autoChain = props.autoGroups.filter((g) => modelEnableGroups.includes(g) ) if (autoChain.length === 0) return null return (
{t('Auto Group Chain')} โ†’ {autoChain.map((g, idx) => ( {idx < autoChain.length - 1 && ( โ†’ )} ))}
) } type DynamicPriceOptions = Parameters[1] type DynamicPricingTier = ReturnType[number] type DynamicFormattedPricesByTier = Map> function getDynamicPriceFields( tiers: DynamicPricingTier[], options: DynamicPriceOptions ) { return [ ...new Map( tiers .flatMap((tier) => getDynamicPriceEntries(tier, options)) .map((entry) => [entry.field, entry]) ).values(), ] } function getDynamicFormattedPricesByTier( tiers: DynamicPricingTier[], options: DynamicPriceOptions ): DynamicFormattedPricesByTier { return new Map( tiers.map((tier) => [ tier, new Map( getDynamicPriceEntries(tier, options).map((entry) => [ entry.field, entry.formatted, ]) ), ]) ) } // ---------------------------------------------------------------------------- // Group pricing table // ---------------------------------------------------------------------------- function GroupPricingSection(props: { model: PricingModel groupRatio: Record usableGroup: Record autoGroups: string[] priceRate: number usdExchangeRate: number tokenUnit: TokenUnit showRechargePrice?: boolean }) { const { t } = useTranslation() const showRechargePrice = props.showRechargePrice ?? false const availableGroups = useMemo( () => getAvailableGroups(props.model, props.usableGroup || {}), [props.model, props.usableGroup] ) const isTokenBased = isTokenBasedModel(props.model) const tokenUnitLabel = props.tokenUnit === 'K' ? '1K' : '1M' const extraPriceTypes = useMemo(() => { const types: { label: string; type: PriceType }[] = [] if (props.model.cache_ratio != null) { types.push({ label: t('Cache'), type: 'cache' }) } if (props.model.create_cache_ratio != null) { types.push({ label: t('Cache Write'), type: 'create_cache' }) } if (props.model.image_ratio != null) { types.push({ label: t('Image'), type: 'image' }) } if (props.model.audio_ratio != null) { types.push({ label: t('Audio In'), type: 'audio_input' }) } if ( props.model.audio_ratio != null && props.model.audio_completion_ratio != null ) { types.push({ label: t('Audio Out'), type: 'audio_output' }) } return types }, [props.model, t]) if (availableGroups.length === 0) { return (
{t('Pricing by Group')}

{t( 'This model is not available in any group, or no group pricing information is configured.' )}

) } const thClass = 'text-muted-foreground py-2 text-xs font-medium' if (isDynamicPricingModel(props.model)) { const dynamicTiers = getDynamicPricingTiers(props.model) if (dynamicTiers.length === 0) { return (
{t('Pricing by Group')}
{t('Special billing expression')}

{t( 'Group prices cannot be expanded because this expression is not a standard tiered pricing expression.' )}

{t('Raw expression')}
{props.model.billing_expr}
) } const priceFields = getDynamicPriceFields(dynamicTiers, { tokenUnit: props.tokenUnit, showRechargePrice, priceRate: props.priceRate, usdExchangeRate: props.usdExchangeRate, groupRatioMultiplier: 1, }) const formattedPricesByGroup = new Map( availableGroups.map((group) => { const ratio = props.groupRatio[group] || 1 return [ group, getDynamicFormattedPricesByTier(dynamicTiers, { tokenUnit: props.tokenUnit, showRechargePrice, priceRate: props.priceRate, usdExchangeRate: props.usdExchangeRate, groupRatioMultiplier: ratio, }), ] as const }) ) return (
{t('Pricing by Group')}
{availableGroups.map((group) => { const ratio = props.groupRatio[group] || 1 const formattedPricesByTier = formattedPricesByGroup.get(group) ?? new Map>() return (
{ratio}x
`${group}-${tier.label || tierIndex}` } columns={[ { id: 'tier', header: t('Tier'), className: thClass, cellClassName: 'text-muted-foreground py-2.5', cell: (tier) => tier.label || t('Default'), }, ...priceFields.map((fieldEntry) => ({ id: fieldEntry.field, header: t(fieldEntry.shortLabel), className: `${thClass} text-right`, cellClassName: 'py-2.5 text-right font-mono', cell: (tier: (typeof dynamicTiers)[number]) => formattedPricesByTier .get(tier) ?.get(fieldEntry.field) ?? '-', })), ]} />
) })}

{t('Prices shown per')} {tokenUnitLabel} tokens

) } const renderGroupPrice = (group: string, type: PriceType) => formatGroupPrice( props.model, group, type, props.tokenUnit, showRechargePrice, props.priceRate, props.usdExchangeRate, props.groupRatio ) const renderFixedGroupPrice = (group: string) => formatFixedPrice( props.model, group, showRechargePrice, props.priceRate, props.usdExchangeRate, props.groupRatio ) return (
{t('Pricing by Group')} group} columns={[ { id: 'group', header: t('Group'), className: thClass, cellClassName: 'py-2.5', cell: (group) => , }, { id: 'ratio', header: t('Ratio'), className: thClass, cellClassName: 'text-muted-foreground py-2.5 font-mono', cell: (group) => `${props.groupRatio[group] || 1}x`, }, ...(isTokenBased ? [ { id: 'input', header: t('Input'), className: `${thClass} text-right`, cellClassName: 'py-2.5 text-right font-mono', cell: (group: string) => renderGroupPrice(group, 'input'), }, { id: 'output', header: t('Output'), className: `${thClass} text-right`, cellClassName: 'py-2.5 text-right font-mono', cell: (group: string) => renderGroupPrice(group, 'output'), }, ...extraPriceTypes.map((ep) => ({ id: ep.type, header: ep.label, className: `${thClass} text-right`, cellClassName: 'py-2.5 text-right font-mono', cell: (group: string) => renderGroupPrice(group, ep.type), })), ] : [ { id: 'price', header: t('Price'), className: `${thClass} text-right`, cellClassName: 'py-2.5 text-right font-mono', cell: renderFixedGroupPrice, }, ]), ]} />
{isTokenBased && (

{t('Prices shown per')} {tokenUnitLabel} tokens

)}
) } const TAB_VALUES = ['overview', 'performance', 'api'] as const type TabValue = (typeof TAB_VALUES)[number] const TAB_META: Record< TabValue, { icon: React.ComponentType<{ className?: string }>; labelKey: string } > = { overview: { icon: Info, labelKey: 'Overview' }, performance: { icon: HeartPulse, labelKey: 'Performance' }, api: { icon: Code2, labelKey: 'API' }, } export interface ModelDetailsContentProps { model: PricingModel groupRatio: Record usableGroup: Record endpointMap: Record autoGroups: string[] priceRate: number usdExchangeRate: number tokenUnit: TokenUnit showRechargePrice?: boolean } export function ModelDetailsContent(props: ModelDetailsContentProps) { const { t } = useTranslation() const showRechargePrice = props.showRechargePrice ?? false const isDynamic = props.model.billing_mode === 'tiered_expr' && Boolean(props.model.billing_expr) return (
{TAB_VALUES.map((value) => { const Icon = TAB_META[value].icon return ( ) })} {isDynamic && ( )}
) } export function ModelDetails() { const { t } = useTranslation() const { modelId } = useParams({ from: '/pricing/$modelId/' }) const search = useSearch({ from: '/pricing/$modelId/' }) const navigate = useNavigate() const { models, groupRatio, usableGroup, endpointMap, autoGroups, isLoading, priceRate, usdExchangeRate, } = usePricingData() const tokenUnit: TokenUnit = search.tokenUnit === 'K' ? 'K' : DEFAULT_TOKEN_UNIT const model = useMemo(() => { if (!models || !modelId) return null return models.find((m) => m.model_name === modelId) || null }, [models, modelId]) const handleBack = () => { navigate({ to: '/pricing', search }) } if (isLoading) { return (
{['stats-a', 'stats-b', 'stats-c', 'stats-d'].map((key) => ( ))}
{['block-a', 'block-b', 'block-c', 'block-d'].map((key) => ( ))}
) } if (!model) { return (

{t('Model not found')}

{t("The model you're looking for doesn't exist.")}

) } return ( ) || {} } /> ) }