import { memo, useMemo } from 'react' import { ChevronRight } from 'lucide-react' import { useTranslation } from 'react-i18next' import { getLobeIcon } from '@/lib/lobe-icon' import { cn } from '@/lib/utils' import { DEFAULT_TOKEN_UNIT } from '../constants' import { parseTiersFromExpr, splitBillingExprAndRequestRules, tryParseRequestRuleExpr, SOURCE_TIME, } from '../lib/billing-expr' import { parseTags } from '../lib/filters' import { isTokenBasedModel } from '../lib/model-helpers' import { formatPrice, formatRequestPrice } from '../lib/price' import type { PricingModel, TokenUnit } from '../types' export interface ModelRowProps { model: PricingModel onClick: () => void priceRate?: number usdExchangeRate?: number tokenUnit?: TokenUnit showRechargePrice?: boolean } interface DynamicPricingHints { tierCount: number hasTimeCondition: boolean hasRequestCondition: boolean } /** * Extract at-a-glance hints from a tiered billing expression. * * The full breakdown lives in `DynamicPricingBreakdown`; here we only need a * minimal summary (tier count + condition presence) so that users scanning * the list can tell *what kind* of dynamic pricing applies before clicking * through to the model details page. */ function summarizeTieredExpr( expr: string | null | undefined ): DynamicPricingHints { if (!expr) { return { tierCount: 0, hasTimeCondition: false, hasRequestCondition: false } } const split = splitBillingExprAndRequestRules(expr) const tiers = parseTiersFromExpr(split.billingExpr) const ruleGroups = tryParseRequestRuleExpr(split.requestRuleExpr || '') || [] let hasTimeCondition = false let hasRequestCondition = false for (const group of ruleGroups) { for (const condition of group.conditions) { if (condition.source === SOURCE_TIME) { hasTimeCondition = true } else { hasRequestCondition = true } } } return { tierCount: tiers.length, hasTimeCondition, hasRequestCondition, } } function PriceLabel(props: { label: string; value: string; muted?: boolean }) { return (
{props.label} {props.value}
) } export const ModelRow = memo(function ModelRow(props: ModelRowProps) { const { t } = useTranslation() const model = props.model const priceRate = props.priceRate ?? 1 const usdExchangeRate = props.usdExchangeRate ?? 1 const tokenUnit = props.tokenUnit ?? DEFAULT_TOKEN_UNIT const showRechargePrice = props.showRechargePrice ?? false const isTokenBased = isTokenBasedModel(model) const vendorIcon = model.vendor_icon ? getLobeIcon(model.vendor_icon, 20) : null const tags = parseTags(model.tags) const tokenUnitLabel = tokenUnit === 'K' ? '1K' : '1M' const hasCachedPrice = isTokenBased && model.cache_ratio != null const isDynamicPricing = model.billing_mode === 'tiered_expr' && Boolean(model.billing_expr) const dynamicHints = useMemo( () => (isDynamicPricing ? summarizeTieredExpr(model.billing_expr) : null), [isDynamicPricing, model.billing_expr] ) return ( ) })