import { useMemo } from 'react' import { useQuery } from '@tanstack/react-query' import { useNavigate, useParams, useSearch } from '@tanstack/react-router' import { ArrowLeft, Code2, HeartPulse, Info, Timer } from 'lucide-react' import { useTranslation } from 'react-i18next' import { getLobeIcon } from '@/lib/lobe-icon' import { cn } from '@/lib/utils' import { Button } from '@/components/ui/button' import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, } from '@/components/ui/sheet' import { Skeleton } from '@/components/ui/skeleton' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { CopyButton } from '@/components/copy-button' import { GroupBadge } from '@/components/group-badge' import { PublicLayout } from '@/components/layout' import { getPerfMetrics } from '@/features/performance-metrics/api' import { formatLatency, formatThroughput, formatUptimePct, } from '@/features/performance-metrics/lib/format' 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 { inferModelMetadata } from '../lib/model-metadata' import { formatFixedPrice, formatGroupPrice } from '../lib/price' import type { Modality, ModelCapability, PriceType, PricingModel, TokenUnit, } from '../types' import { DynamicPricingBreakdown } from './dynamic-pricing-breakdown' import { ModelDetailsApi, ModelDetailsProviderInfo } from './model-details-api' import { ModalityIcons } from './model-details-modalities' import { ModelDetailsPerformance } from './model-details-performance' import { ModelDetailsQuickStats } from './model-details-quick-stats' // ---------------------------------------------------------------------------- // Local UI helpers // ---------------------------------------------------------------------------- function SectionTitle(props: { children: React.ReactNode }) { return (

{props.children}

) } 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', } function CompactCapabilityList(props: { capabilities: ModelCapability[] }) { const { t } = useTranslation() if (props.capabilities.length === 0) { return ( {t('No capabilities reported for this model.')} ) } return (
{props.capabilities.map((capability) => ( {t(CAPABILITY_LABEL_KEYS[capability] ?? capability)} ))}
) } function CompactModalities(props: { input: Modality[]; output: Modality[] }) { const { t } = useTranslation() return (
{t('Input')}
{t('Output')}
) } function ModelSignalsSection(props: { capabilities: ModelCapability[] input: Modality[] output: Modality[] }) { const { t } = useTranslation() return (
{t('Capabilities')} / {t('Supported modalities')}
) } function OverviewMetric(props: { icon: React.ComponentType<{ className?: string }> label: string value: React.ReactNode intent?: 'default' | 'warning' | 'success' }) { const Icon = props.icon const intent = props.intent ?? 'default' 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 let successIntent: 'default' | 'warning' | 'success' = 'warning' if (successRate >= 99.9) { successIntent = 'success' } else if (successRate >= 99) { successIntent = 'default' } 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 (
) } // ---------------------------------------------------------------------------- // Model header (always visible above the detail sections) // ---------------------------------------------------------------------------- function ModelHeader(props: { model: PricingModel }) { const { t } = useTranslation() const model = props.model const vendorIcon = model.vendor_icon ? getLobeIcon(model.vendor_icon, 20) : null const description = model.description || model.vendor_description || null const tags = parseTags(model.tags) const isSpecialExpression = model.billing_mode === 'tiered_expr' && Boolean(model.billing_expr) && getDynamicPricingTiers(model).length === 0 return (
{vendorIcon}

{model.model_name}

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

{description}

)} {tags.length > 0 && (
{tags.map((tag) => ( {tag} ))}
)}
) } // ---------------------------------------------------------------------------- // 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('Base Price')}
{t('Special billing expression')}

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

{t('Raw expression')}
{dynamicSummary.rawExpression}
) } return (
{t('Base Price')} {dynamicSummary.primaryEntries.length > 0 ? (
{dynamicSummary.primaryEntries.map((entry) => (
{t(entry.shortLabel)}
{entry.formatted} / {tokenUnitLabel}
))}
) : (

{t('Dynamic Pricing')}

)} {dynamicSummary.secondaryEntries.length > 0 && (
{dynamicSummary.secondaryEntries.map((entry) => (
{t(entry.shortLabel)} {entry.formatted} / {tokenUnitLabel}
))}
)}
) } if (!isTokenBased) { return (
{t('Base Price')}
{t('Per request')} {formatFixedPrice( props.model, baseGroupKey, props.showRechargePrice, props.priceRate, props.usdExchangeRate, baseGroupRatioMap )}
) } const secondaryItems = secondaryPriceTypes.filter((p) => p.available) const renderPrice = (type: PriceType) => ( <> {formatGroupPrice( props.model, baseGroupKey, type, props.tokenUnit, props.showRechargePrice, props.priceRate, props.usdExchangeRate, baseGroupRatioMap )} / {tokenUnitLabel} ) return (
{t('Base Price')}
{primaryPriceTypes.map((item) => (
{item.label}
{renderPrice(item.type)}
))}
{secondaryItems.length > 0 && (
{secondaryItems.map((item) => (
{item.label} {renderPrice(item.type)}
))}
)}
) } // ---------------------------------------------------------------------------- // 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 && ( )} ))}
) } // ---------------------------------------------------------------------------- // 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-[10px] font-medium tracking-wider uppercase' 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 = Array.from( new Map( dynamicTiers .flatMap((tier) => getDynamicPriceEntries(tier, { tokenUnit: props.tokenUnit, showRechargePrice, priceRate: props.priceRate, usdExchangeRate: props.usdExchangeRate, groupRatioMultiplier: 1, }) ) .map((entry) => [entry.field, entry]) ).values() ) return (
{t('Pricing by Group')}
{availableGroups.map((group) => { const ratio = props.groupRatio[group] || 1 return (
{ratio}x
{t('Tier')} {priceFields.map((entry) => ( {t(entry.shortLabel)} ))} {dynamicTiers.map((tier, tierIndex) => { const entries = getDynamicPriceEntries(tier, { tokenUnit: props.tokenUnit, showRechargePrice, priceRate: props.priceRate, usdExchangeRate: props.usdExchangeRate, groupRatioMultiplier: ratio, }) const entryMap = new Map( entries.map((entry) => [entry.field, entry]) ) return ( {tier.label || t('Default')} {priceFields.map((fieldEntry) => { const entry = entryMap.get(fieldEntry.field) return ( {entry?.formatted ?? '-'} ) })} ) })}
) })}

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

) } return (
{t('Pricing by Group')}
{t('Group')} {t('Ratio')} {isTokenBased ? ( <> {t('Input')} {t('Output')} {extraPriceTypes.map((ep) => ( {ep.label} ))} ) : ( {t('Price')} )} {availableGroups.map((group) => { const ratio = props.groupRatio[group] || 1 return ( {ratio}x {isTokenBased ? ( <> {formatGroupPrice( props.model, group, 'input', props.tokenUnit, showRechargePrice, props.priceRate, props.usdExchangeRate, props.groupRatio )} {formatGroupPrice( props.model, group, 'output', props.tokenUnit, showRechargePrice, props.priceRate, props.usdExchangeRate, props.groupRatio )} {extraPriceTypes.map((ep) => ( {formatGroupPrice( props.model, group, ep.type, props.tokenUnit, showRechargePrice, props.priceRate, props.usdExchangeRate, props.groupRatio )} ))} ) : ( {formatFixedPrice( props.model, group, showRechargePrice, props.priceRate, props.usdExchangeRate, props.groupRatio )} )} ) })}
{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 metadata = useMemo(() => inferModelMetadata(props.model), [props.model]) 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 ( {t(TAB_META[value].labelKey)} ) })}
{t('Pricing')} {isDynamic && ( )}
) } // ---------------------------------------------------------------------------- // Drawer & page wrappers // ---------------------------------------------------------------------------- export interface ModelDetailsDrawerProps extends ModelDetailsContentProps { open: boolean onOpenChange: (open: boolean) => void } export function ModelDetailsDrawer(props: ModelDetailsDrawerProps) { const { t } = useTranslation() const { open, onOpenChange, ...contentProps } = props return ( {props.model.model_name} {t('Model details')}
) } 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 (
{Array.from({ length: 4 }).map((_, i) => ( ))}
{Array.from({ length: 4 }).map((_, i) => ( ))}
) } if (!model) { return (

{t('Model not found')}

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

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