feat(pricing): enhance dynamic pricing calculations with group selection support
This commit is contained in:
@@ -36,6 +36,7 @@ export interface ModelCardGridProps {
|
|||||||
usdExchangeRate?: number
|
usdExchangeRate?: number
|
||||||
tokenUnit?: TokenUnit
|
tokenUnit?: TokenUnit
|
||||||
showRechargePrice?: boolean
|
showRechargePrice?: boolean
|
||||||
|
selectedGroup?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ModelCardGrid(props: ModelCardGridProps) {
|
export function ModelCardGrid(props: ModelCardGridProps) {
|
||||||
@@ -81,6 +82,7 @@ export function ModelCardGrid(props: ModelCardGridProps) {
|
|||||||
priceRate={props.priceRate}
|
priceRate={props.priceRate}
|
||||||
usdExchangeRate={props.usdExchangeRate}
|
usdExchangeRate={props.usdExchangeRate}
|
||||||
showRechargePrice={props.showRechargePrice}
|
showRechargePrice={props.showRechargePrice}
|
||||||
|
selectedGroup={props.selectedGroup}
|
||||||
perf={perfMap.get(model.model_name || '')}
|
perf={perfMap.get(model.model_name || '')}
|
||||||
onClick={() => props.onModelClick(model.model_name || '')}
|
onClick={() => props.onModelClick(model.model_name || '')}
|
||||||
/>
|
/>
|
||||||
|
|||||||
+112
-91
@@ -17,7 +17,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||||||
For commercial licensing, please contact support@quantumnous.com
|
For commercial licensing, please contact support@quantumnous.com
|
||||||
*/
|
*/
|
||||||
import { ChevronRight, Copy } from 'lucide-react'
|
import { ChevronRight, Copy } from 'lucide-react'
|
||||||
import { memo } from 'react'
|
import { memo, type ReactNode } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import { StatusBadge } from '@/components/status-badge'
|
import { StatusBadge } from '@/components/status-badge'
|
||||||
@@ -43,6 +43,7 @@ export interface ModelCardProps {
|
|||||||
usdExchangeRate?: number
|
usdExchangeRate?: number
|
||||||
tokenUnit?: TokenUnit
|
tokenUnit?: TokenUnit
|
||||||
showRechargePrice?: boolean
|
showRechargePrice?: boolean
|
||||||
|
selectedGroup?: string
|
||||||
perf?: ModelPerfBadgeData
|
perf?: ModelPerfBadgeData
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,7 +72,10 @@ export const ModelCard = memo(function ModelCard(props: ModelCardProps) {
|
|||||||
showRechargePrice,
|
showRechargePrice,
|
||||||
priceRate,
|
priceRate,
|
||||||
usdExchangeRate,
|
usdExchangeRate,
|
||||||
groupRatioMultiplier: getDynamicDisplayGroupRatio(props.model),
|
groupRatioMultiplier: getDynamicDisplayGroupRatio(
|
||||||
|
props.model,
|
||||||
|
props.selectedGroup
|
||||||
|
),
|
||||||
})
|
})
|
||||||
: null
|
: null
|
||||||
|
|
||||||
@@ -87,6 +91,111 @@ export const ModelCard = memo(function ModelCard(props: ModelCardProps) {
|
|||||||
copyToClipboard(props.model.model_name || '')
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -109,95 +218,7 @@ export const ModelCard = memo(function ModelCard(props: ModelCardProps) {
|
|||||||
{props.model.model_name}
|
{props.model.model_name}
|
||||||
</h3>
|
</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'>
|
<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 ? (
|
{priceSummary}
|
||||||
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>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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
|
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 { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -51,6 +51,7 @@ export interface PricingColumnsOptions {
|
|||||||
priceRate?: number
|
priceRate?: number
|
||||||
usdExchangeRate?: number
|
usdExchangeRate?: number
|
||||||
showRechargePrice?: boolean
|
showRechargePrice?: boolean
|
||||||
|
selectedGroup?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export function usePricingColumns(
|
export function usePricingColumns(
|
||||||
@@ -62,6 +63,7 @@ export function usePricingColumns(
|
|||||||
priceRate = 1,
|
priceRate = 1,
|
||||||
usdExchangeRate = 1,
|
usdExchangeRate = 1,
|
||||||
showRechargePrice = false,
|
showRechargePrice = false,
|
||||||
|
selectedGroup,
|
||||||
} = options
|
} = options
|
||||||
|
|
||||||
const tokenUnitLabel = tokenUnit === 'K' ? '1K' : '1M'
|
const tokenUnitLabel = tokenUnit === 'K' ? '1K' : '1M'
|
||||||
@@ -124,7 +126,10 @@ export function usePricingColumns(
|
|||||||
showRechargePrice,
|
showRechargePrice,
|
||||||
priceRate,
|
priceRate,
|
||||||
usdExchangeRate,
|
usdExchangeRate,
|
||||||
groupRatioMultiplier: getDynamicDisplayGroupRatio(model),
|
groupRatioMultiplier: getDynamicDisplayGroupRatio(
|
||||||
|
model,
|
||||||
|
selectedGroup
|
||||||
|
),
|
||||||
})
|
})
|
||||||
|
|
||||||
if (dynamicSummary) {
|
if (dynamicSummary) {
|
||||||
@@ -186,7 +191,8 @@ export function usePricingColumns(
|
|||||||
tokenUnit,
|
tokenUnit,
|
||||||
showRechargePrice,
|
showRechargePrice,
|
||||||
priceRate,
|
priceRate,
|
||||||
usdExchangeRate
|
usdExchangeRate,
|
||||||
|
selectedGroup
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
const outputPrice = stripTrailingZeros(
|
const outputPrice = stripTrailingZeros(
|
||||||
@@ -196,7 +202,8 @@ export function usePricingColumns(
|
|||||||
tokenUnit,
|
tokenUnit,
|
||||||
showRechargePrice,
|
showRechargePrice,
|
||||||
priceRate,
|
priceRate,
|
||||||
usdExchangeRate
|
usdExchangeRate,
|
||||||
|
selectedGroup
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -219,7 +226,8 @@ export function usePricingColumns(
|
|||||||
model,
|
model,
|
||||||
showRechargePrice,
|
showRechargePrice,
|
||||||
priceRate,
|
priceRate,
|
||||||
usdExchangeRate
|
usdExchangeRate,
|
||||||
|
selectedGroup
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -247,7 +255,10 @@ export function usePricingColumns(
|
|||||||
showRechargePrice,
|
showRechargePrice,
|
||||||
priceRate,
|
priceRate,
|
||||||
usdExchangeRate,
|
usdExchangeRate,
|
||||||
groupRatioMultiplier: getDynamicDisplayGroupRatio(model),
|
groupRatioMultiplier: getDynamicDisplayGroupRatio(
|
||||||
|
model,
|
||||||
|
selectedGroup
|
||||||
|
),
|
||||||
})
|
})
|
||||||
|
|
||||||
if (dynamicSummary) {
|
if (dynamicSummary) {
|
||||||
@@ -291,7 +302,8 @@ export function usePricingColumns(
|
|||||||
tokenUnit,
|
tokenUnit,
|
||||||
showRechargePrice,
|
showRechargePrice,
|
||||||
priceRate,
|
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
|
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 { useState, useCallback } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
@@ -38,6 +38,7 @@ export interface PricingTableProps {
|
|||||||
usdExchangeRate?: number
|
usdExchangeRate?: number
|
||||||
tokenUnit?: TokenUnit
|
tokenUnit?: TokenUnit
|
||||||
showRechargePrice?: boolean
|
showRechargePrice?: boolean
|
||||||
|
selectedGroup?: string
|
||||||
onModelClick?: (modelName: string) => void
|
onModelClick?: (modelName: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,6 +51,7 @@ export function PricingTable(props: PricingTableProps) {
|
|||||||
usdExchangeRate = 1,
|
usdExchangeRate = 1,
|
||||||
tokenUnit = DEFAULT_TOKEN_UNIT,
|
tokenUnit = DEFAULT_TOKEN_UNIT,
|
||||||
showRechargePrice = false,
|
showRechargePrice = false,
|
||||||
|
selectedGroup,
|
||||||
onModelClick,
|
onModelClick,
|
||||||
} = props
|
} = props
|
||||||
|
|
||||||
@@ -63,6 +65,7 @@ export function PricingTable(props: PricingTableProps) {
|
|||||||
priceRate,
|
priceRate,
|
||||||
usdExchangeRate,
|
usdExchangeRate,
|
||||||
showRechargePrice,
|
showRechargePrice,
|
||||||
|
selectedGroup,
|
||||||
})
|
})
|
||||||
|
|
||||||
const { table } = useDataTable({
|
const { table } = useDataTable({
|
||||||
|
|||||||
+2
@@ -130,6 +130,7 @@ export function Pricing() {
|
|||||||
usdExchangeRate={usdExchangeRate}
|
usdExchangeRate={usdExchangeRate}
|
||||||
tokenUnit={tokenUnit}
|
tokenUnit={tokenUnit}
|
||||||
showRechargePrice={showRechargePrice}
|
showRechargePrice={showRechargePrice}
|
||||||
|
selectedGroup={groupFilter}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -141,6 +142,7 @@ export function Pricing() {
|
|||||||
usdExchangeRate={usdExchangeRate}
|
usdExchangeRate={usdExchangeRate}
|
||||||
tokenUnit={tokenUnit}
|
tokenUnit={tokenUnit}
|
||||||
showRechargePrice={showRechargePrice}
|
showRechargePrice={showRechargePrice}
|
||||||
|
selectedGroup={groupFilter}
|
||||||
onModelClick={handleModelClick}
|
onModelClick={handleModelClick}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
+6
-14
@@ -28,6 +28,7 @@ import {
|
|||||||
type BillingVar,
|
type BillingVar,
|
||||||
type ParsedTier,
|
type ParsedTier,
|
||||||
} from './billing-expr'
|
} from './billing-expr'
|
||||||
|
import { getDisplayGroupRatio } from './model-helpers'
|
||||||
|
|
||||||
type DynamicPriceOptions = {
|
type DynamicPriceOptions = {
|
||||||
tokenUnit: TokenUnit
|
tokenUnit: TokenUnit
|
||||||
@@ -65,20 +66,11 @@ export function isDynamicPricingModel(model: PricingModel): boolean {
|
|||||||
return model.billing_mode === 'tiered_expr' && Boolean(model.billing_expr)
|
return model.billing_mode === 'tiered_expr' && Boolean(model.billing_expr)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDynamicDisplayGroupRatio(model: PricingModel): number {
|
export function getDynamicDisplayGroupRatio(
|
||||||
const groups = Array.isArray(model.enable_groups) ? model.enable_groups : []
|
model: PricingModel,
|
||||||
const ratios = model.group_ratio || {}
|
selectedGroup?: string
|
||||||
if (groups.length === 0) return 1
|
): number {
|
||||||
|
return getDisplayGroupRatio(model, selectedGroup)
|
||||||
let minRatio = Number.POSITIVE_INFINITY
|
|
||||||
for (const group of groups) {
|
|
||||||
const ratio = ratios[group]
|
|
||||||
if (ratio !== undefined && ratio < minRatio) {
|
|
||||||
minRatio = ratio
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return minRatio === Number.POSITIVE_INFINITY ? 1 : minRatio
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyRechargeRate(
|
function applyRechargeRate(
|
||||||
|
|||||||
+57
-2
@@ -16,7 +16,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||||||
|
|
||||||
For commercial licensing, please contact support@quantumnous.com
|
For commercial licensing, please contact support@quantumnous.com
|
||||||
*/
|
*/
|
||||||
import { EXCLUDED_GROUPS, QUOTA_TYPE_VALUES } from '../constants'
|
import { EXCLUDED_GROUPS, FILTER_ALL, QUOTA_TYPE_VALUES } from '../constants'
|
||||||
import type { PricingModel } from '../types'
|
import type { PricingModel } from '../types'
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
@@ -39,11 +39,66 @@ export function getAvailableGroups(
|
|||||||
.filter((g) => modelEnableGroups.includes(g))
|
.filter((g) => modelEnableGroups.includes(g))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a configured group ratio while preserving valid zero ratios.
|
||||||
|
*/
|
||||||
|
export function getConfiguredGroupRatio(
|
||||||
|
groupRatio: Record<string, number>,
|
||||||
|
group: string
|
||||||
|
): number {
|
||||||
|
const ratio = groupRatio[group]
|
||||||
|
return typeof ratio === 'number' && Number.isFinite(ratio) ? ratio : 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the group ratio used by model square summary prices.
|
||||||
|
*
|
||||||
|
* When no specific group is selected, the model square shows the best price
|
||||||
|
* available to the viewer. When a group filter is active, it mirrors classic
|
||||||
|
* and shows that group's price.
|
||||||
|
*/
|
||||||
|
export function getDisplayGroupRatio(
|
||||||
|
model: PricingModel,
|
||||||
|
selectedGroup?: string
|
||||||
|
): number {
|
||||||
|
const modelEnableGroups = Array.isArray(model.enable_groups)
|
||||||
|
? model.enable_groups
|
||||||
|
: []
|
||||||
|
const groupRatio = model.group_ratio || {}
|
||||||
|
|
||||||
|
if (
|
||||||
|
selectedGroup &&
|
||||||
|
selectedGroup !== FILTER_ALL &&
|
||||||
|
modelEnableGroups.includes(selectedGroup)
|
||||||
|
) {
|
||||||
|
return getConfiguredGroupRatio(groupRatio, selectedGroup)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modelEnableGroups.length === 0) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
let minRatio = Number.POSITIVE_INFINITY
|
||||||
|
|
||||||
|
for (const group of modelEnableGroups) {
|
||||||
|
const ratio = groupRatio[group]
|
||||||
|
if (
|
||||||
|
typeof ratio === 'number' &&
|
||||||
|
Number.isFinite(ratio) &&
|
||||||
|
ratio < minRatio
|
||||||
|
) {
|
||||||
|
minRatio = ratio
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return minRatio === Number.POSITIVE_INFINITY ? 1 : minRatio
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replace model placeholder in endpoint path
|
* Replace model placeholder in endpoint path
|
||||||
*/
|
*/
|
||||||
export function replaceModelInPath(path: string, modelName: string): string {
|
export function replaceModelInPath(path: string, modelName: string): string {
|
||||||
return path.replace(/\{model\}/g, modelName)
|
return path.replaceAll('{model}', modelName)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+19
-45
@@ -20,6 +20,7 @@ import { formatCurrencyFromUSD } from '@/lib/currency'
|
|||||||
|
|
||||||
import { QUOTA_TYPE_VALUES, TOKEN_UNIT_DIVISORS } from '../constants'
|
import { QUOTA_TYPE_VALUES, TOKEN_UNIT_DIVISORS } from '../constants'
|
||||||
import type { PricingModel, TokenUnit, PriceType } from '../types'
|
import type { PricingModel, TokenUnit, PriceType } from '../types'
|
||||||
|
import { getConfiguredGroupRatio, getDisplayGroupRatio } from './model-helpers'
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// Price Calculation Utilities
|
// Price Calculation Utilities
|
||||||
@@ -36,11 +37,11 @@ export function stripTrailingZeros(formatted: string): string {
|
|||||||
const [, symbol, number, suffix] = match
|
const [, symbol, number, suffix] = match
|
||||||
|
|
||||||
// Remove commas for processing
|
// Remove commas for processing
|
||||||
const cleanNumber = number.replace(/,/g, '')
|
const cleanNumber = number.replaceAll(',', '')
|
||||||
|
|
||||||
// Convert to number and back to remove trailing zeros
|
// Convert to number and back to remove trailing zeros
|
||||||
const parsed = parseFloat(cleanNumber)
|
const parsed = Number.parseFloat(cleanNumber)
|
||||||
if (isNaN(parsed)) return formatted
|
if (Number.isNaN(parsed)) return formatted
|
||||||
|
|
||||||
// Convert to string, which automatically removes trailing zeros
|
// Convert to string, which automatically removes trailing zeros
|
||||||
let result = parsed.toString()
|
let result = parsed.toString()
|
||||||
@@ -53,27 +54,6 @@ export function stripTrailingZeros(formatted: string): string {
|
|||||||
return `${symbol}${result}${suffix}`
|
return `${symbol}${result}${suffix}`
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Find minimum group ratio from enabled groups
|
|
||||||
*/
|
|
||||||
function getMinGroupRatio(
|
|
||||||
enableGroups: string[],
|
|
||||||
groupRatio: Record<string, number>
|
|
||||||
): number {
|
|
||||||
if (enableGroups.length === 0) return 1
|
|
||||||
|
|
||||||
let minRatio = Number.POSITIVE_INFINITY
|
|
||||||
|
|
||||||
for (const group of enableGroups) {
|
|
||||||
const ratio = groupRatio[group]
|
|
||||||
if (ratio !== undefined && ratio < minRatio) {
|
|
||||||
minRatio = ratio
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return minRatio === Number.POSITIVE_INFINITY ? 1 : minRatio
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate token price in USD.
|
* Calculate token price in USD.
|
||||||
*
|
*
|
||||||
@@ -95,26 +75,26 @@ function calculateTokenPrice(
|
|||||||
case 'cache':
|
case 'cache':
|
||||||
return hasRatio(model.cache_ratio)
|
return hasRatio(model.cache_ratio)
|
||||||
? base * Number(model.cache_ratio)
|
? base * Number(model.cache_ratio)
|
||||||
: NaN
|
: Number.NaN
|
||||||
case 'create_cache':
|
case 'create_cache':
|
||||||
return hasRatio(model.create_cache_ratio)
|
return hasRatio(model.create_cache_ratio)
|
||||||
? base * Number(model.create_cache_ratio)
|
? base * Number(model.create_cache_ratio)
|
||||||
: NaN
|
: Number.NaN
|
||||||
case 'image':
|
case 'image':
|
||||||
return hasRatio(model.image_ratio)
|
return hasRatio(model.image_ratio)
|
||||||
? base * Number(model.image_ratio)
|
? base * Number(model.image_ratio)
|
||||||
: NaN
|
: Number.NaN
|
||||||
case 'audio_input':
|
case 'audio_input':
|
||||||
return hasRatio(model.audio_ratio)
|
return hasRatio(model.audio_ratio)
|
||||||
? base * Number(model.audio_ratio)
|
? base * Number(model.audio_ratio)
|
||||||
: NaN
|
: Number.NaN
|
||||||
case 'audio_output':
|
case 'audio_output':
|
||||||
return hasRatio(model.audio_ratio) &&
|
return hasRatio(model.audio_ratio) &&
|
||||||
hasRatio(model.audio_completion_ratio)
|
hasRatio(model.audio_completion_ratio)
|
||||||
? base *
|
? base *
|
||||||
Number(model.audio_ratio) *
|
Number(model.audio_ratio) *
|
||||||
Number(model.audio_completion_ratio)
|
Number(model.audio_completion_ratio)
|
||||||
: NaN
|
: Number.NaN
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,19 +147,16 @@ export function formatPrice(
|
|||||||
tokenUnit: TokenUnit,
|
tokenUnit: TokenUnit,
|
||||||
showWithRecharge = false,
|
showWithRecharge = false,
|
||||||
priceRate = 1,
|
priceRate = 1,
|
||||||
usdExchangeRate = 1
|
usdExchangeRate = 1,
|
||||||
|
selectedGroup?: string
|
||||||
): string {
|
): string {
|
||||||
if (model.quota_type === QUOTA_TYPE_VALUES.REQUEST) {
|
if (model.quota_type === QUOTA_TYPE_VALUES.REQUEST) {
|
||||||
return '-'
|
return '-'
|
||||||
}
|
}
|
||||||
|
|
||||||
const enableGroups = Array.isArray(model.enable_groups)
|
const displayGroupRatio = getDisplayGroupRatio(model, selectedGroup)
|
||||||
? model.enable_groups
|
|
||||||
: []
|
|
||||||
const groupRatio = model.group_ratio || {}
|
|
||||||
const minRatio = getMinGroupRatio(enableGroups, groupRatio)
|
|
||||||
|
|
||||||
let priceInUSD = calculateTokenPrice(model, type, minRatio)
|
let priceInUSD = calculateTokenPrice(model, type, displayGroupRatio)
|
||||||
priceInUSD = applyRechargeRate(
|
priceInUSD = applyRechargeRate(
|
||||||
priceInUSD,
|
priceInUSD,
|
||||||
showWithRecharge,
|
showWithRecharge,
|
||||||
@@ -212,7 +189,7 @@ export function formatGroupPrice(
|
|||||||
return '-'
|
return '-'
|
||||||
}
|
}
|
||||||
|
|
||||||
const ratio = groupRatio[group] || 1
|
const ratio = getConfiguredGroupRatio(groupRatio, group)
|
||||||
let priceInUSD = calculateTokenPrice(model, type, ratio)
|
let priceInUSD = calculateTokenPrice(model, type, ratio)
|
||||||
|
|
||||||
priceInUSD = applyRechargeRate(
|
priceInUSD = applyRechargeRate(
|
||||||
@@ -245,7 +222,7 @@ export function formatFixedPrice(
|
|||||||
return '-'
|
return '-'
|
||||||
}
|
}
|
||||||
|
|
||||||
const ratio = groupRatio[group] || 1
|
const ratio = getConfiguredGroupRatio(groupRatio, group)
|
||||||
let priceInUSD = (model.model_price || 0) * ratio
|
let priceInUSD = (model.model_price || 0) * ratio
|
||||||
|
|
||||||
priceInUSD = applyRechargeRate(
|
priceInUSD = applyRechargeRate(
|
||||||
@@ -269,19 +246,16 @@ export function formatRequestPrice(
|
|||||||
model: PricingModel,
|
model: PricingModel,
|
||||||
showWithRecharge = false,
|
showWithRecharge = false,
|
||||||
priceRate = 1,
|
priceRate = 1,
|
||||||
usdExchangeRate = 1
|
usdExchangeRate = 1,
|
||||||
|
selectedGroup?: string
|
||||||
): string {
|
): string {
|
||||||
if (model.quota_type !== QUOTA_TYPE_VALUES.REQUEST) {
|
if (model.quota_type !== QUOTA_TYPE_VALUES.REQUEST) {
|
||||||
return '-'
|
return '-'
|
||||||
}
|
}
|
||||||
|
|
||||||
const enableGroups = Array.isArray(model.enable_groups)
|
const displayGroupRatio = getDisplayGroupRatio(model, selectedGroup)
|
||||||
? model.enable_groups
|
|
||||||
: []
|
|
||||||
const groupRatio = model.group_ratio || {}
|
|
||||||
const minRatio = getMinGroupRatio(enableGroups, groupRatio)
|
|
||||||
|
|
||||||
let priceInUSD = (model.model_price || 0) * minRatio
|
let priceInUSD = (model.model_price || 0) * displayGroupRatio
|
||||||
|
|
||||||
priceInUSD = applyRechargeRate(
|
priceInUSD = applyRechargeRate(
|
||||||
priceInUSD,
|
priceInUSD,
|
||||||
|
|||||||
Reference in New Issue
Block a user