/*
Copyright (C) 2023-2026 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
For commercial licensing, please contact support@quantumnous.com
*/
import { formatCurrencyFromUSD } from '@/lib/currency'
import { QUOTA_TYPE_VALUES, TOKEN_UNIT_DIVISORS } from '../constants'
import type { PricingModel, TokenUnit, PriceType } from '../types'
import { getConfiguredGroupRatio, getDisplayGroupRatio } from './model-helpers'
// ----------------------------------------------------------------------------
// Price Calculation Utilities
// ----------------------------------------------------------------------------
/**
* Strip trailing zeros from formatted price string while preserving currency symbols
*/
export function stripTrailingZeros(formatted: string): string {
// Match currency symbol at start, number, and potential 'k' suffix
const match = formatted.match(/^([^\d-]*)([-\d,]+\.?\d*)(k?)$/)
if (!match) return formatted
const [, symbol, number, suffix] = match
// Remove commas for processing
const cleanNumber = number.replaceAll(',', '')
// Convert to number and back to remove trailing zeros
const parsed = Number.parseFloat(cleanNumber)
if (Number.isNaN(parsed)) return formatted
// Convert to string, which automatically removes trailing zeros
let result = parsed.toString()
// If the result is in scientific notation, format it properly
if (result.includes('e')) {
result = parsed.toFixed(20).replace(/\.?0+$/, '')
}
return `${symbol}${result}${suffix}`
}
/**
* Calculate token price in USD.
*
* Returns NaN when the required ratio field is missing/null so callers can
* skip rendering that price type.
*/
function calculateTokenPrice(
model: PricingModel,
type: PriceType,
ratio: number
): number {
const base = model.model_ratio * 2 * ratio
switch (type) {
case 'input':
return base
case 'output':
return base * model.completion_ratio
case 'cache':
return hasRatio(model.cache_ratio)
? base * Number(model.cache_ratio)
: Number.NaN
case 'create_cache':
return hasRatio(model.create_cache_ratio)
? base * Number(model.create_cache_ratio)
: Number.NaN
case 'image':
return hasRatio(model.image_ratio)
? base * Number(model.image_ratio)
: Number.NaN
case 'audio_input':
return hasRatio(model.audio_ratio)
? base * Number(model.audio_ratio)
: Number.NaN
case 'audio_output':
return hasRatio(model.audio_ratio) &&
hasRatio(model.audio_completion_ratio)
? base *
Number(model.audio_ratio) *
Number(model.audio_completion_ratio)
: Number.NaN
}
}
function hasRatio(value: number | null | undefined): boolean {
return value !== undefined && value !== null && Number.isFinite(Number(value))
}
/**
* Apply recharge rate to price
*
* priceRate represents how much users need to recharge (in the display currency)
* to get 1 USD credit. usdExchangeRate is the real exchange rate.
*
* The returned value will be formatted by formatCurrencyFromUSD, which will
* multiply by the display currency's exchange rate.
*
* Examples:
*
* 1. Display currency = USD:
* - Model: 1 USD
* - priceRate = 0.5 (recharge $0.5 to get $1 credit)
* - usdExchangeRate = 1
* - Return: 1 × 0.5 / 1 = 0.5
* - formatCurrencyFromUSD(0.5) → $0.5 ✓
*
* 2. Display currency = CNY:
* - Model: 1 USD
* - priceRate = 4 (recharge ¥4 to get $1 credit)
* - usdExchangeRate = 7 (real rate: 1 USD = ¥7)
* - Return: 1 × 4 / 7 = 0.571
* - formatCurrencyFromUSD(0.571) → 0.571 × 7 = ¥4 ✓
* - Normal price: ¥7, Recharge price: ¥4 (cheaper!)
*/
function applyRechargeRate(
price: number,
showWithRecharge: boolean,
priceRate: number,
usdExchangeRate: number
): number {
if (!showWithRecharge) return price
return (price * priceRate) / usdExchangeRate
}
/**
* Format token-based price for display
*/
export function formatPrice(
model: PricingModel,
type: PriceType,
tokenUnit: TokenUnit,
showWithRecharge = false,
priceRate = 1,
usdExchangeRate = 1,
selectedGroup?: string
): string {
if (model.quota_type === QUOTA_TYPE_VALUES.REQUEST) {
return '-'
}
const displayGroupRatio = getDisplayGroupRatio(model, selectedGroup)
let priceInUSD = calculateTokenPrice(model, type, displayGroupRatio)
priceInUSD = applyRechargeRate(
priceInUSD,
showWithRecharge,
priceRate,
usdExchangeRate
)
const price = priceInUSD / TOKEN_UNIT_DIVISORS[tokenUnit]
return formatCurrencyFromUSD(price, {
digitsLarge: 4,
digitsSmall: 6,
abbreviate: false,
})
}
/**
* Format price for a specific group (token-based)
*/
export function formatGroupPrice(
model: PricingModel,
group: string,
type: PriceType,
tokenUnit: TokenUnit,
showWithRecharge = false,
priceRate = 1,
usdExchangeRate = 1,
groupRatio: Record
): string {
if (model.quota_type === QUOTA_TYPE_VALUES.REQUEST) {
return '-'
}
const ratio = getConfiguredGroupRatio(groupRatio, group)
let priceInUSD = calculateTokenPrice(model, type, ratio)
priceInUSD = applyRechargeRate(
priceInUSD,
showWithRecharge,
priceRate,
usdExchangeRate
)
const price = priceInUSD / TOKEN_UNIT_DIVISORS[tokenUnit]
return formatCurrencyFromUSD(price, {
digitsLarge: 4,
digitsSmall: 6,
abbreviate: false,
})
}
/**
* Format fixed price for pay-per-request models (with specific group)
*/
export function formatFixedPrice(
model: PricingModel,
group: string,
showWithRecharge = false,
priceRate = 1,
usdExchangeRate = 1,
groupRatio: Record
): string {
if (model.quota_type !== QUOTA_TYPE_VALUES.REQUEST) {
return '-'
}
const ratio = getConfiguredGroupRatio(groupRatio, group)
let priceInUSD = (model.model_price || 0) * ratio
priceInUSD = applyRechargeRate(
priceInUSD,
showWithRecharge,
priceRate,
usdExchangeRate
)
return formatCurrencyFromUSD(priceInUSD, {
digitsLarge: 4,
digitsSmall: 4,
abbreviate: false,
})
}
/**
* Format fixed price for pay-per-request models (minimum price from all groups)
*/
export function formatRequestPrice(
model: PricingModel,
showWithRecharge = false,
priceRate = 1,
usdExchangeRate = 1,
selectedGroup?: string
): string {
if (model.quota_type !== QUOTA_TYPE_VALUES.REQUEST) {
return '-'
}
const displayGroupRatio = getDisplayGroupRatio(model, selectedGroup)
let priceInUSD = (model.model_price || 0) * displayGroupRatio
priceInUSD = applyRechargeRate(
priceInUSD,
showWithRecharge,
priceRate,
usdExchangeRate
)
return formatCurrencyFromUSD(priceInUSD, {
digitsLarge: 4,
digitsSmall: 4,
abbreviate: false,
})
}