feat(pricing): enhance dynamic pricing calculations with group selection support

This commit is contained in:
CaIon
2026-07-08 15:11:32 +08:00
parent 28e0115a08
commit 97bbb7c8cd
8 changed files with 221 additions and 160 deletions
@@ -36,6 +36,7 @@ export interface ModelCardGridProps {
usdExchangeRate?: number
tokenUnit?: TokenUnit
showRechargePrice?: boolean
selectedGroup?: string
}
export function ModelCardGrid(props: ModelCardGridProps) {
@@ -81,6 +82,7 @@ export function ModelCardGrid(props: ModelCardGridProps) {
priceRate={props.priceRate}
usdExchangeRate={props.usdExchangeRate}
showRechargePrice={props.showRechargePrice}
selectedGroup={props.selectedGroup}
perf={perfMap.get(model.model_name || '')}
onClick={() => props.onModelClick(model.model_name || '')}
/>
+112 -91
View File
@@ -17,7 +17,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import { ChevronRight, Copy } from 'lucide-react'
import { memo } from 'react'
import { memo, type ReactNode } from 'react'
import { useTranslation } from 'react-i18next'
import { StatusBadge } from '@/components/status-badge'
@@ -43,6 +43,7 @@ export interface ModelCardProps {
usdExchangeRate?: number
tokenUnit?: TokenUnit
showRechargePrice?: boolean
selectedGroup?: string
perf?: ModelPerfBadgeData
}
@@ -71,7 +72,10 @@ export const ModelCard = memo(function ModelCard(props: ModelCardProps) {
showRechargePrice,
priceRate,
usdExchangeRate,
groupRatioMultiplier: getDynamicDisplayGroupRatio(props.model),
groupRatioMultiplier: getDynamicDisplayGroupRatio(
props.model,
props.selectedGroup
),
})
: null
@@ -87,6 +91,111 @@ export const ModelCard = memo(function ModelCard(props: ModelCardProps) {
copyToClipboard(props.model.model_name || '')
}
let priceSummary: ReactNode
if (dynamicSummary) {
if (dynamicSummary.isSpecialExpression) {
priceSummary = (
<span className='min-w-0'>
<span className='text-amber-700 dark:text-amber-300'>
{t('Special billing expression')}
</span>
<code className='text-muted-foreground/70 mt-0.5 line-clamp-1 block font-mono text-[11px] break-all'>
{dynamicSummary.rawExpression}
</code>
</span>
)
} else if (dynamicSummary.primaryEntries.length > 0) {
priceSummary = (
<>
{dynamicSummary.primaryEntries.map((entry) => (
<span
key={entry.key}
className='text-muted-foreground whitespace-nowrap'
>
{t(entry.shortLabel)}{' '}
<span className='text-foreground font-mono font-semibold'>
{entry.formatted}
</span>
/{tokenUnitLabel}
</span>
))}
</>
)
} else {
priceSummary = (
<span className='text-muted-foreground text-xs'>
{t('Dynamic Pricing')}
</span>
)
}
} else if (isTokenBased) {
priceSummary = (
<>
<span className='text-muted-foreground whitespace-nowrap'>
{t('Input')}{' '}
<span className='text-foreground font-mono font-semibold'>
{formatPrice(
props.model,
'input',
tokenUnit,
showRechargePrice,
priceRate,
usdExchangeRate,
props.selectedGroup
)}
</span>
/{tokenUnitLabel}
</span>
<span className='text-muted-foreground whitespace-nowrap'>
{t('Output')}{' '}
<span className='text-foreground font-mono font-semibold'>
{formatPrice(
props.model,
'output',
tokenUnit,
showRechargePrice,
priceRate,
usdExchangeRate,
props.selectedGroup
)}
</span>
/{tokenUnitLabel}
</span>
{hasCachedPrice && (
<span className='text-muted-foreground/60 whitespace-nowrap'>
{t('Cached')}{' '}
<span className='font-mono'>
{formatPrice(
props.model,
'cache',
tokenUnit,
showRechargePrice,
priceRate,
usdExchangeRate,
props.selectedGroup
)}
</span>
</span>
)}
</>
)
} else {
priceSummary = (
<span className='text-muted-foreground whitespace-nowrap'>
<span className='text-foreground font-mono font-semibold'>
{formatRequestPrice(
props.model,
showRechargePrice,
priceRate,
usdExchangeRate,
props.selectedGroup
)}
</span>{' '}
/ {t('request')}
</span>
)
}
return (
<div
className={cn(
@@ -109,95 +218,7 @@ export const ModelCard = memo(function ModelCard(props: ModelCardProps) {
{props.model.model_name}
</h3>
<div className='mt-0.5 flex flex-wrap items-baseline gap-x-2 gap-y-0.5 text-xs sm:mt-1 sm:gap-x-3'>
{dynamicSummary ? (
dynamicSummary.isSpecialExpression ? (
<span className='min-w-0'>
<span className='text-amber-700 dark:text-amber-300'>
{t('Special billing expression')}
</span>
<code className='text-muted-foreground/70 mt-0.5 line-clamp-1 block font-mono text-[11px] break-all'>
{dynamicSummary.rawExpression}
</code>
</span>
) : dynamicSummary.primaryEntries.length > 0 ? (
<>
{dynamicSummary.primaryEntries.map((entry) => (
<span
key={entry.key}
className='text-muted-foreground whitespace-nowrap'
>
{t(entry.shortLabel)}{' '}
<span className='text-foreground font-mono font-semibold'>
{entry.formatted}
</span>
/{tokenUnitLabel}
</span>
))}
</>
) : (
<span className='text-muted-foreground text-xs'>
{t('Dynamic Pricing')}
</span>
)
) : isTokenBased ? (
<>
<span className='text-muted-foreground whitespace-nowrap'>
{t('Input')}{' '}
<span className='text-foreground font-mono font-semibold'>
{formatPrice(
props.model,
'input',
tokenUnit,
showRechargePrice,
priceRate,
usdExchangeRate
)}
</span>
/{tokenUnitLabel}
</span>
<span className='text-muted-foreground whitespace-nowrap'>
{t('Output')}{' '}
<span className='text-foreground font-mono font-semibold'>
{formatPrice(
props.model,
'output',
tokenUnit,
showRechargePrice,
priceRate,
usdExchangeRate
)}
</span>
/{tokenUnitLabel}
</span>
{hasCachedPrice && (
<span className='text-muted-foreground/60 whitespace-nowrap'>
{t('Cached')}{' '}
<span className='font-mono'>
{formatPrice(
props.model,
'cache',
tokenUnit,
showRechargePrice,
priceRate,
usdExchangeRate
)}
</span>
</span>
)}
</>
) : (
<span className='text-muted-foreground whitespace-nowrap'>
<span className='text-foreground font-mono font-semibold'>
{formatRequestPrice(
props.model,
showRechargePrice,
priceRate,
usdExchangeRate
)}
</span>{' '}
/ {t('request')}
</span>
)}
{priceSummary}
</div>
</div>
</div>
@@ -16,7 +16,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import { type ColumnDef } from '@tanstack/react-table'
import type { ColumnDef } from '@tanstack/react-table'
import { useTranslation } from 'react-i18next'
import {
@@ -51,6 +51,7 @@ export interface PricingColumnsOptions {
priceRate?: number
usdExchangeRate?: number
showRechargePrice?: boolean
selectedGroup?: string
}
export function usePricingColumns(
@@ -62,6 +63,7 @@ export function usePricingColumns(
priceRate = 1,
usdExchangeRate = 1,
showRechargePrice = false,
selectedGroup,
} = options
const tokenUnitLabel = tokenUnit === 'K' ? '1K' : '1M'
@@ -124,7 +126,10 @@ export function usePricingColumns(
showRechargePrice,
priceRate,
usdExchangeRate,
groupRatioMultiplier: getDynamicDisplayGroupRatio(model),
groupRatioMultiplier: getDynamicDisplayGroupRatio(
model,
selectedGroup
),
})
if (dynamicSummary) {
@@ -186,7 +191,8 @@ export function usePricingColumns(
tokenUnit,
showRechargePrice,
priceRate,
usdExchangeRate
usdExchangeRate,
selectedGroup
)
)
const outputPrice = stripTrailingZeros(
@@ -196,7 +202,8 @@ export function usePricingColumns(
tokenUnit,
showRechargePrice,
priceRate,
usdExchangeRate
usdExchangeRate,
selectedGroup
)
)
@@ -219,7 +226,8 @@ export function usePricingColumns(
model,
showRechargePrice,
priceRate,
usdExchangeRate
usdExchangeRate,
selectedGroup
)
)
@@ -247,7 +255,10 @@ export function usePricingColumns(
showRechargePrice,
priceRate,
usdExchangeRate,
groupRatioMultiplier: getDynamicDisplayGroupRatio(model),
groupRatioMultiplier: getDynamicDisplayGroupRatio(
model,
selectedGroup
),
})
if (dynamicSummary) {
@@ -291,7 +302,8 @@ export function usePricingColumns(
tokenUnit,
showRechargePrice,
priceRate,
usdExchangeRate
usdExchangeRate,
selectedGroup
)
)
@@ -16,7 +16,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import { type Row, type PaginationState } from '@tanstack/react-table'
import type { Row, PaginationState } from '@tanstack/react-table'
import { useState, useCallback } from 'react'
import { useTranslation } from 'react-i18next'
@@ -38,6 +38,7 @@ export interface PricingTableProps {
usdExchangeRate?: number
tokenUnit?: TokenUnit
showRechargePrice?: boolean
selectedGroup?: string
onModelClick?: (modelName: string) => void
}
@@ -50,6 +51,7 @@ export function PricingTable(props: PricingTableProps) {
usdExchangeRate = 1,
tokenUnit = DEFAULT_TOKEN_UNIT,
showRechargePrice = false,
selectedGroup,
onModelClick,
} = props
@@ -63,6 +65,7 @@ export function PricingTable(props: PricingTableProps) {
priceRate,
usdExchangeRate,
showRechargePrice,
selectedGroup,
})
const { table } = useDataTable({