import { useMemo } from 'react' import { useParams, useNavigate, useSearch } from '@tanstack/react-router' import { ArrowLeft } from 'lucide-react' import { useTranslation } from 'react-i18next' import { getLobeIcon } from '@/lib/lobe-icon' import { Button } from '@/components/ui/button' import { Skeleton } from '@/components/ui/skeleton' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table' import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, } from '@/components/ui/sheet' import { CopyButton } from '@/components/copy-button' import { GroupBadge } from '@/components/group-badge' import { PublicLayout } from '@/components/layout' import { DEFAULT_TOKEN_UNIT, QUOTA_TYPE_VALUES } from '../constants' import { usePricingData } from '../hooks/use-pricing-data' import { parseTags } from '../lib/filters' import { getAvailableGroups, replaceModelInPath, isTokenBasedModel, } from '../lib/model-helpers' import { getDynamicPriceEntries, getDynamicPricingSummary, getDynamicPricingTiers, isDynamicPricingModel, } from '../lib/dynamic-price' import { formatGroupPrice, formatFixedPrice } from '../lib/price' import type { PricingModel, TokenUnit, PriceType } from '../types' import { DynamicPricingBreakdown } from './dynamic-pricing-breakdown' function SectionTitle(props: { children: React.ReactNode }) { return (

{props.children}

) } 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} ))}
)}
) } function PriceSection(props: { model: PricingModel priceRate: number usdExchangeRate: number tokenUnit: TokenUnit showRechargePrice: boolean }) { const { t } = useTranslation() const { model, priceRate, usdExchangeRate, tokenUnit, showRechargePrice, } = props const isTokenBased = isTokenBasedModel(model) const tokenUnitLabel = tokenUnit === 'K' ? '1K' : '1M' const baseGroupKey = '_base' const baseGroupRatioMap = { [baseGroupKey]: 1 } const dynamicSummary = getDynamicPricingSummary(model, { tokenUnit, showRechargePrice, priceRate, 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: model.cache_ratio != null, }, { label: t('Cache write'), type: 'create_cache', available: model.create_cache_ratio != null, }, { label: t('Image input'), type: 'image', available: model.image_ratio != null, }, { label: t('Audio input'), type: 'audio_input', available: model.audio_ratio != null, }, { label: t('Audio output'), type: 'audio_output', available: model.audio_ratio != null && 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( model, baseGroupKey, showRechargePrice, priceRate, usdExchangeRate, baseGroupRatioMap )}
) } const secondaryItems = secondaryPriceTypes.filter((p) => p.available) const renderPrice = (type: PriceType) => ( <> {formatGroupPrice( model, baseGroupKey, type, tokenUnit, showRechargePrice, priceRate, 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)}
))}
)}
) } function EndpointsSection(props: { model: PricingModel endpointMap: Record }) { const { t } = useTranslation() const { model, endpointMap } = props const endpoints = useMemo(() => { const types = model.supported_endpoint_types || [] return types.map((type) => { const info = endpointMap[type] || {} let path = info.path || '' if (path.includes('{model}')) { path = replaceModelInPath(path, model.model_name || '') } return { type, path, method: info.method || 'POST' } }) }, [model, endpointMap]) if (endpoints.length === 0) return null return (
{t('API Endpoints')}
{endpoints.map(({ type, path, method }) => (
{type} {path && ( {path} )}
{path && ( {method} )}
))}
) } 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 && ( )} ))}
) } function GroupPricingSection(props: { model: PricingModel groupRatio: Record usableGroup: Record autoGroups: string[] priceRate: number usdExchangeRate: number tokenUnit: TokenUnit showRechargePrice?: boolean }) { const { t } = useTranslation() const { model, groupRatio, usableGroup, autoGroups, priceRate, usdExchangeRate, tokenUnit, showRechargePrice = false, } = props const availableGroups = useMemo( () => getAvailableGroups(model, usableGroup || {}), [model, usableGroup] ) const isTokenBased = isTokenBasedModel(model) const tokenUnitLabel = tokenUnit === 'K' ? '1K' : '1M' const extraPriceTypes = useMemo(() => { const types: { label: string; type: PriceType }[] = [] if (model.cache_ratio != null) types.push({ label: t('Cache'), type: 'cache' }) if (model.create_cache_ratio != null) types.push({ label: t('Cache Write'), type: 'create_cache' }) if (model.image_ratio != null) types.push({ label: t('Image'), type: 'image' }) if (model.audio_ratio != null) types.push({ label: t('Audio In'), type: 'audio_input' }) if (model.audio_ratio != null && model.audio_completion_ratio != null) types.push({ label: t('Audio Out'), type: 'audio_output' }) return types }, [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(model)) { const dynamicTiers = getDynamicPricingTiers(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')}
{model.billing_expr}
) } const priceFields = Array.from( new Map( dynamicTiers .flatMap((tier) => getDynamicPriceEntries(tier, { tokenUnit, showRechargePrice, priceRate, usdExchangeRate, groupRatioMultiplier: 1, }) ) .map((entry) => [entry.field, entry]) ).values() ) return (
{t('Pricing by Group')}
{availableGroups.map((group) => { const ratio = groupRatio[group] || 1 return (
{ratio}x
{t('Tier')} {priceFields.map((entry) => ( {t(entry.shortLabel)} ))} {dynamicTiers.map((tier, tierIndex) => { const entries = getDynamicPriceEntries(tier, { tokenUnit, showRechargePrice, priceRate, 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 = groupRatio[group] || 1 return ( {ratio}x {isTokenBased ? ( <> {formatGroupPrice( model, group, 'input', tokenUnit, showRechargePrice, priceRate, usdExchangeRate, groupRatio )} {formatGroupPrice( model, group, 'output', tokenUnit, showRechargePrice, priceRate, usdExchangeRate, groupRatio )} {extraPriceTypes.map((ep) => ( {formatGroupPrice( model, group, ep.type, tokenUnit, showRechargePrice, priceRate, usdExchangeRate, groupRatio )} ))} ) : ( {formatFixedPrice( model, group, showRechargePrice, priceRate, usdExchangeRate, groupRatio )} )} ) })}
{isTokenBased && (

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

)}
) } 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 { model, groupRatio, usableGroup, endpointMap, autoGroups, priceRate, usdExchangeRate, tokenUnit, showRechargePrice = false, } = props return ( <> {model.billing_mode === 'tiered_expr' && model.billing_expr && (
)} ) } 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: 3 }).map((_, i) => (
))}
) } if (!model) { return (

{t('Model not found')}

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

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