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 (
)
})