mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
Add automatic B/T compact unit formatting
This commit is contained in:
@@ -160,21 +160,26 @@ fn dashboard_format_token_compact(value: u64) -> String {
|
||||
return dashboard_format_integer(value);
|
||||
}
|
||||
|
||||
if value < 1_000_000 {
|
||||
let thousands = value as f64 / 1_000.0;
|
||||
if thousands >= 100.0 {
|
||||
return format!("{}K", thousands.round() as u64);
|
||||
const UNITS: &[(u64, &str)] = &[
|
||||
(1_000_000_000_000, "T"),
|
||||
(1_000_000_000, "B"),
|
||||
(1_000_000, "M"),
|
||||
(1_000, "K"),
|
||||
];
|
||||
|
||||
for (divisor, suffix) in UNITS {
|
||||
if value < *divisor {
|
||||
continue;
|
||||
}
|
||||
let decimals = if thousands >= 10.0 { 1 } else { 2 };
|
||||
return format!("{}K", dashboard_trimmed_decimal(thousands, decimals));
|
||||
let scaled = value as f64 / *divisor as f64;
|
||||
if scaled >= 100.0 {
|
||||
return format!("{}{}", scaled.round() as u64, suffix);
|
||||
}
|
||||
let decimals = if scaled >= 10.0 { 1 } else { 2 };
|
||||
return format!("{}{}", dashboard_trimmed_decimal(scaled, decimals), suffix);
|
||||
}
|
||||
|
||||
let millions = value as f64 / 1_000_000.0;
|
||||
if millions >= 100.0 {
|
||||
return format!("{}M", millions.round() as u64);
|
||||
}
|
||||
let decimals = if millions >= 10.0 { 1 } else { 2 };
|
||||
format!("{}M", dashboard_trimmed_decimal(millions, decimals))
|
||||
dashboard_format_integer(value)
|
||||
}
|
||||
|
||||
fn dashboard_format_usd(value: f64) -> String {
|
||||
@@ -1500,3 +1505,17 @@ fn dashboard_format_time_hhmm(unix_secs: u64) -> Option<String> {
|
||||
let datetime = chrono::DateTime::<chrono::Utc>::from_timestamp(timestamp, 0)?;
|
||||
Some(datetime.format("%H:%M").to_string())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::dashboard_format_token_compact;
|
||||
|
||||
#[test]
|
||||
fn dashboard_format_token_compact_promotes_above_millions() {
|
||||
assert_eq!(dashboard_format_token_compact(999), "999");
|
||||
assert_eq!(dashboard_format_token_compact(1_250), "1.25K");
|
||||
assert_eq!(dashboard_format_token_compact(12_500_000), "12.5M");
|
||||
assert_eq!(dashboard_format_token_compact(1_250_000_000), "1.25B");
|
||||
assert_eq!(dashboard_format_token_compact(12_500_000_000_000), "12.5T");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -559,6 +559,7 @@ import RoutingTab from './RoutingTab.vue'
|
||||
import ModelMappingsTab from './ModelMappingsTab.vue'
|
||||
import { sortResolutionEntries } from '@/utils/form'
|
||||
import { parseApiError } from '@/utils/errorParser'
|
||||
import { formatCompactNumber, formatTokens } from '@/utils/format'
|
||||
import { getGlobalModelRoutingPreview } from '@/api/global-models'
|
||||
|
||||
// 使用外部类型定义
|
||||
@@ -734,13 +735,7 @@ function formatPixelLimit(value: number | null): string {
|
||||
}
|
||||
|
||||
function formatPixels(value: number): string {
|
||||
if (value >= 1_000_000) {
|
||||
return `${(value / 1_000_000).toFixed(value % 1_000_000 === 0 ? 0 : 2)}M px`
|
||||
}
|
||||
if (value >= 1_000) {
|
||||
return `${(value / 1_000).toFixed(0)}K px`
|
||||
}
|
||||
return `${value} px`
|
||||
return `${formatCompactNumber(value)} px`
|
||||
}
|
||||
|
||||
const detailTab = ref('basic')
|
||||
@@ -790,12 +785,7 @@ function getTierCount(tieredPricing: TieredPricingConfig | undefined | null): nu
|
||||
// 格式化阶梯上限(tokens 数量简化显示)
|
||||
function formatTierLimit(limit: number | null | undefined): string {
|
||||
if (limit == null) return ''
|
||||
if (limit >= 1000000) {
|
||||
return `${(limit / 1000000).toFixed(1)}M`
|
||||
} else if (limit >= 1000) {
|
||||
return `${(limit / 1000).toFixed(0)}K`
|
||||
}
|
||||
return limit.toString()
|
||||
return formatTokens(limit)
|
||||
}
|
||||
|
||||
// 获取 1h 缓存价格
|
||||
|
||||
@@ -286,6 +286,7 @@
|
||||
import { ref, computed, watch, reactive } from 'vue'
|
||||
import { Plus, X } from 'lucide-vue-next'
|
||||
import { Button, Input, Label } from '@/components/ui'
|
||||
import { formatTokens } from '@/utils/format'
|
||||
import type { TieredPricingConfig, PricingTier, ImageOutputPriceRange } from '@/api/endpoints/types'
|
||||
|
||||
type ImageOutputQuality = 'low' | 'medium' | 'high'
|
||||
@@ -434,17 +435,6 @@ function getAvailableThresholds(index: number) {
|
||||
return options
|
||||
}
|
||||
|
||||
// 格式化 token 数量
|
||||
function formatTokens(tokens: number): string {
|
||||
if (tokens >= 1000000) {
|
||||
return `${(tokens / 1000000).toFixed(tokens % 1000000 === 0 ? 0 : 1)}M`
|
||||
}
|
||||
if (tokens >= 1000) {
|
||||
return `${(tokens / 1000).toFixed(0)}K`
|
||||
}
|
||||
return tokens.toString()
|
||||
}
|
||||
|
||||
// 缓存价格自动计算
|
||||
function getAutoCacheCreation(index: number): number {
|
||||
const inputPrice = localTiers.value[index]?.input_price_per_1m || 0
|
||||
|
||||
@@ -112,4 +112,16 @@ describe('poolStatsDisplay', () => {
|
||||
total_cost_usd: '$12.35',
|
||||
})
|
||||
})
|
||||
|
||||
it('promotes large token totals above M', () => {
|
||||
const display = buildPoolStatsDisplay(
|
||||
createCodexKey({ total_tokens: 1_500_000_000 }),
|
||||
'openai',
|
||||
'account_total',
|
||||
)
|
||||
|
||||
expect(display.kind).toBe('account_total')
|
||||
if (display.kind !== 'account_total') throw new Error('expected account total display')
|
||||
expect(metricValues(display.metrics).total_tokens).toBe('1.5B')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { QuotaWindowUsageSnapshot } from '@/api/endpoints/types/statusSnapshot'
|
||||
import type { PoolManagementStatsMode } from '@/features/pool/utils/poolManagementState'
|
||||
import { formatCompactNumber } from '@/utils/format'
|
||||
|
||||
export type PoolStatsMetricKey = 'request_count' | 'total_tokens' | 'total_cost_usd'
|
||||
export type PoolStatsDisplayKind = 'account_total' | 'codex_cycle'
|
||||
@@ -63,9 +64,7 @@ export function formatPoolStatInteger(value: number | null | undefined): string
|
||||
export function formatPoolTokenCount(value: number | null | undefined): string {
|
||||
const n = Number(value ?? 0)
|
||||
if (!Number.isFinite(n) || n <= 0) return '0'
|
||||
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`
|
||||
if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`
|
||||
return String(Math.round(n))
|
||||
return formatCompactNumber(Math.round(n), { fractionDigits: 1 })
|
||||
}
|
||||
|
||||
export function formatPoolStatUsd(value: number | string | null | undefined): string {
|
||||
|
||||
@@ -205,6 +205,7 @@ import { RefreshCw, RotateCcw } from 'lucide-vue-next'
|
||||
import { getPoolStatus, clearPoolCooldown, resetPoolCost } from '@/api/endpoints/pool'
|
||||
import type { PoolStatusResponse } from '@/api/endpoints/pool'
|
||||
import { parseApiError } from '@/utils/errorParser'
|
||||
import { formatTokens } from '@/utils/format'
|
||||
import { useToast } from '@/composables/useToast'
|
||||
|
||||
import Card from '@/components/ui/card.vue'
|
||||
@@ -287,12 +288,6 @@ function formatTTL(seconds: number): string {
|
||||
return m > 0 ? `${m}m ${s}s` : `${s}s`
|
||||
}
|
||||
|
||||
function formatTokens(tokens: number): string {
|
||||
if (tokens >= 1_000_000) return `${(tokens / 1_000_000).toFixed(1)}M`
|
||||
if (tokens >= 1_000) return `${(tokens / 1_000).toFixed(1)}K`
|
||||
return String(tokens)
|
||||
}
|
||||
|
||||
function formatEmaHeat(value: number): string {
|
||||
if (!Number.isFinite(value) || value <= 0) return '0.0'
|
||||
return value.toFixed(1)
|
||||
|
||||
@@ -1440,6 +1440,7 @@ import {
|
||||
} from '../utils/quotaAutoRefreshCooldown'
|
||||
import { getOAuthOrgBadge } from '@/utils/oauthIdentity'
|
||||
import { getOAuthRefreshFeedback } from '@/utils/oauthRefreshFeedback'
|
||||
import { formatCompactNumber } from '@/utils/format'
|
||||
import {
|
||||
canEditOAuthCredential,
|
||||
canExportOAuthCredential,
|
||||
@@ -2633,13 +2634,10 @@ const formatKiroUpdatedAt = formatUpdatedAt
|
||||
// 格式化 Kiro 使用量(带单位)
|
||||
function formatKiroUsage(value: number | undefined): string {
|
||||
if (value === undefined || value === null) return '-'
|
||||
if (value >= 1000000) {
|
||||
return `${(value / 1000000).toFixed(1)}M`
|
||||
}
|
||||
if (value >= 1000) {
|
||||
return `${(value / 1000).toFixed(1)}K`
|
||||
}
|
||||
return value.toFixed(1)
|
||||
const normalized = Number(value)
|
||||
if (!Number.isFinite(normalized)) return '-'
|
||||
if (normalized >= 1000) return formatCompactNumber(normalized, { fractionDigits: 1 })
|
||||
return normalized.toFixed(1)
|
||||
}
|
||||
|
||||
// 格式化 Kiro 重置时间
|
||||
|
||||
@@ -542,6 +542,7 @@ import { ChevronLeft, ChevronRight, ExternalLink } from 'lucide-vue-next'
|
||||
import { requestTraceApi, type RequestTrace, type CandidateRecord, type ImageProgress } from '@/api/requestTrace'
|
||||
import { log } from '@/utils/logger'
|
||||
import { parseApiError } from '@/utils/errorParser'
|
||||
import { formatTokens } from '@/utils/format'
|
||||
import { formatApiFormat } from '@/api/endpoints/types/api-format'
|
||||
import { useDarkMode } from '@/composables/useDarkMode'
|
||||
import { resolveTimelineFinalStatus } from '../utils/status'
|
||||
@@ -629,7 +630,7 @@ const usageData = computed(() => props.usageData)
|
||||
|
||||
// 格式化数字
|
||||
const formatNumber = (num: number): string => {
|
||||
return num.toLocaleString('zh-CN')
|
||||
return formatTokens(num)
|
||||
}
|
||||
|
||||
// 获取最终状态标签
|
||||
|
||||
@@ -794,7 +794,7 @@ import { AlertTriangle, Check, Columns2, RefreshCw, X, Monitor, Server, MessageS
|
||||
import { dashboardApi, type RequestDetail, type RequestErrorDomain } from '@/api/dashboard'
|
||||
import type { ImageProgress, RequestTrace } from '@/api/requestTrace'
|
||||
import { formatApiFormat } from '@/api/endpoints/types/api-format'
|
||||
import { formatShortRequestId } from '@/utils/format'
|
||||
import { formatCompactNumber, formatShortRequestId, formatTokens } from '@/utils/format'
|
||||
import { log } from '@/utils/logger'
|
||||
import { getEffectiveInputTokens } from '../token-normalization'
|
||||
import {
|
||||
@@ -2274,12 +2274,7 @@ function getTaskTypeLabel(taskType: string): string {
|
||||
}
|
||||
|
||||
function formatNumber(num: number): string {
|
||||
if (num >= 1_000_000) {
|
||||
return `${(num / 1_000_000).toFixed(1) }M`
|
||||
} else if (num >= 1_000) {
|
||||
return `${(num / 1_000).toFixed(1) }K`
|
||||
}
|
||||
return num.toLocaleString()
|
||||
return formatTokens(num)
|
||||
}
|
||||
|
||||
function parseImageSizePixels(size: string | null): number | null {
|
||||
@@ -2301,13 +2296,7 @@ function formatImagePriceBucket(bucket: string): string {
|
||||
}
|
||||
|
||||
function formatPixels(value: number): string {
|
||||
if (value >= 1_000_000) {
|
||||
return `${(value / 1_000_000).toFixed(value % 1_000_000 === 0 ? 0 : 2)}M px`
|
||||
}
|
||||
if (value >= 1_000) {
|
||||
return `${(value / 1_000).toFixed(0)}K px`
|
||||
}
|
||||
return `${value} px`
|
||||
return `${formatCompactNumber(value)} px`
|
||||
}
|
||||
|
||||
// 格式化响应时间,自动选择合适的单位
|
||||
|
||||
19
frontend/src/utils/__tests__/format.spec.ts
Normal file
19
frontend/src/utils/__tests__/format.spec.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { formatCompactNumber, formatTokens, formatUsageCount } from '../format'
|
||||
|
||||
describe('format utils', () => {
|
||||
it('formats compact numbers beyond millions', () => {
|
||||
expect(formatCompactNumber(999)).toBe('999')
|
||||
expect(formatCompactNumber(1_250)).toBe('1.25K')
|
||||
expect(formatCompactNumber(12_500_000)).toBe('12.5M')
|
||||
expect(formatCompactNumber(1_250_000_000)).toBe('1.25B')
|
||||
expect(formatCompactNumber(12_500_000_000_000)).toBe('12.5T')
|
||||
})
|
||||
|
||||
it('uses the same B/T compact units for tokens and usage counts', () => {
|
||||
expect(formatTokens(1_000_000_000)).toBe('1B')
|
||||
expect(formatTokens(1_500_000_000_000)).toBe('1.5T')
|
||||
expect(formatUsageCount(1_000_000_000)).toBe('1B')
|
||||
})
|
||||
})
|
||||
@@ -1,35 +1,74 @@
|
||||
const COMPACT_NUMBER_UNITS = [
|
||||
{ value: 1_000_000_000_000, suffix: 'T' },
|
||||
{ value: 1_000_000_000, suffix: 'B' },
|
||||
{ value: 1_000_000, suffix: 'M' },
|
||||
{ value: 1_000, suffix: 'K' },
|
||||
] as const
|
||||
|
||||
interface CompactNumberOptions {
|
||||
fractionDigits?: number
|
||||
nullLabel?: string
|
||||
}
|
||||
|
||||
function trimTrailingDecimalZeros(value: string): string {
|
||||
return value.replace(/\.0+$/, '').replace(/(\.\d*?)0+$/, '$1')
|
||||
}
|
||||
|
||||
function compactFractionDigits(scaled: number, fixedFractionDigits?: number): number {
|
||||
if (fixedFractionDigits !== undefined) return fixedFractionDigits
|
||||
if (scaled >= 100) return 0
|
||||
if (scaled >= 10) return 1
|
||||
return 2
|
||||
}
|
||||
|
||||
function formatCompactScaledValue(
|
||||
absValue: number,
|
||||
unitIndex: number,
|
||||
fixedFractionDigits?: number,
|
||||
): string {
|
||||
const unit = COMPACT_NUMBER_UNITS[unitIndex]
|
||||
const scaled = absValue / unit.value
|
||||
const fractionDigits = compactFractionDigits(scaled, fixedFractionDigits)
|
||||
const rounded = Number(scaled.toFixed(fractionDigits))
|
||||
|
||||
if (rounded >= 1000 && unitIndex > 0) {
|
||||
return formatCompactScaledValue(absValue, unitIndex - 1, fixedFractionDigits)
|
||||
}
|
||||
|
||||
return `${trimTrailingDecimalZeros(scaled.toFixed(fractionDigits))}${unit.suffix}`
|
||||
}
|
||||
|
||||
export function formatCompactNumber(
|
||||
num: number | undefined | null,
|
||||
options: CompactNumberOptions = {},
|
||||
): string {
|
||||
if (num === undefined || num === null) {
|
||||
return options.nullLabel ?? '0'
|
||||
}
|
||||
|
||||
const value = Number(num)
|
||||
if (!Number.isFinite(value)) {
|
||||
return options.nullLabel ?? '0'
|
||||
}
|
||||
|
||||
const sign = value < 0 ? '-' : ''
|
||||
const absValue = Math.abs(value)
|
||||
|
||||
if (absValue < 1_000) {
|
||||
return `${sign}${Number.isInteger(absValue) ? absValue.toString() : trimTrailingDecimalZeros(absValue.toFixed(1))}`
|
||||
}
|
||||
|
||||
const unitIndex = COMPACT_NUMBER_UNITS.findIndex(unit => absValue >= unit.value)
|
||||
if (unitIndex === -1) {
|
||||
return `${sign}${Math.round(absValue)}`
|
||||
}
|
||||
|
||||
return `${sign}${formatCompactScaledValue(absValue, unitIndex, options.fractionDigits)}`
|
||||
}
|
||||
|
||||
// Token formatting - intelligent display based on value size
|
||||
export function formatTokens(num: number | undefined | null): string {
|
||||
if (num === undefined || num === null || num === 0) {
|
||||
return '0'
|
||||
}
|
||||
|
||||
// For very small values (< 1000), show as is without unit
|
||||
if (num < 1000) {
|
||||
return num.toString()
|
||||
}
|
||||
|
||||
// For values 1K-999K, show in thousands
|
||||
if (num < 1000000) {
|
||||
const thousands = num / 1000
|
||||
if (thousands >= 100) {
|
||||
return `${Math.round(thousands) }K`
|
||||
} else if (thousands >= 10) {
|
||||
return `${thousands.toFixed(1) }K`
|
||||
} else {
|
||||
return `${thousands.toFixed(2) }K`
|
||||
}
|
||||
}
|
||||
|
||||
// For values >= 1M, show in millions
|
||||
const millions = num / 1000000
|
||||
if (millions >= 100) {
|
||||
return `${Math.round(millions) }M`
|
||||
} else if (millions >= 10) {
|
||||
return `${millions.toFixed(1) }M`
|
||||
} else {
|
||||
return `${millions.toFixed(2) }M`
|
||||
}
|
||||
return formatCompactNumber(num)
|
||||
}
|
||||
|
||||
// Currency formatting with high precision for small values
|
||||
@@ -135,12 +174,7 @@ export function formatCost(cost: number | null | undefined): string {
|
||||
|
||||
// Usage count formatting (compact display for large numbers)
|
||||
export function formatUsageCount(count: number): string {
|
||||
if (count >= 1000000) {
|
||||
return `${(count / 1000000).toFixed(1)}M`
|
||||
} else if (count >= 1000) {
|
||||
return `${(count / 1000).toFixed(1)}K`
|
||||
}
|
||||
return count.toString()
|
||||
return formatCompactNumber(count, { fractionDigits: 1 })
|
||||
}
|
||||
|
||||
// Format remaining time from unix timestamp
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { formatCompactNumber } from '@/utils/format'
|
||||
|
||||
export function walletStatusLabel(status: string | null | undefined): string {
|
||||
const labels: Record<string, string> = {
|
||||
active: '正常',
|
||||
@@ -41,13 +43,8 @@ export function dailyUsageCategoryLabel(isToday = false): string {
|
||||
|
||||
export function formatTokenCount(value: number | null | undefined): string {
|
||||
const amount = Number(value ?? 0)
|
||||
if (amount >= 1_000_000) {
|
||||
return `${(amount / 1_000_000).toFixed(amount >= 10_000_000 ? 0 : 1)}M`
|
||||
}
|
||||
if (amount >= 1_000) {
|
||||
return `${(amount / 1_000).toFixed(amount >= 10_000 ? 0 : 1)}K`
|
||||
}
|
||||
return `${Math.round(amount)}`
|
||||
if (!Number.isFinite(amount) || amount <= 0) return '0'
|
||||
return formatCompactNumber(Math.round(amount), { fractionDigits: 1 })
|
||||
}
|
||||
|
||||
export function walletTransactionReasonLabel(reasonCode: string | null | undefined): string {
|
||||
|
||||
@@ -999,6 +999,7 @@ import {
|
||||
|
||||
import { Search, Trash2, Plus, SquarePen, Activity, Loader2, Settings, History, ChevronDown, ChevronRight, Terminal, Copy, CheckCircle } from 'lucide-vue-next'
|
||||
import { parseApiError } from '@/utils/errorParser'
|
||||
import { formatCompactNumber } from '@/utils/format'
|
||||
import { formatRegion } from '@/utils/region'
|
||||
import HardwareTooltip from './components/HardwareTooltip.vue'
|
||||
import ProxyNodeDataPanel from './components/ProxyNodeDataPanel.vue'
|
||||
@@ -1567,9 +1568,7 @@ function statusTitle(node: ProxyNode) {
|
||||
}
|
||||
|
||||
function formatNumber(n: number) {
|
||||
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`
|
||||
if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`
|
||||
return String(n)
|
||||
return formatCompactNumber(n, { fractionDigits: 1 })
|
||||
}
|
||||
|
||||
function formatTime(iso: string | null) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { ProxyNode } from '@/api/proxy-nodes'
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { Cpu } from 'lucide-vue-next'
|
||||
import { computed } from 'vue'
|
||||
import { formatCompactNumber } from '@/utils/format'
|
||||
|
||||
const props = defineProps<{ node: ProxyNode }>()
|
||||
|
||||
@@ -104,9 +105,7 @@ function formatMemory(mb: number | null) {
|
||||
}
|
||||
|
||||
function formatNumber(n: number) {
|
||||
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`
|
||||
if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`
|
||||
return String(n)
|
||||
return formatCompactNumber(n, { fractionDigits: 1 })
|
||||
}
|
||||
|
||||
function normalizeHardwareInfo(info: unknown): Record<string, unknown> | null {
|
||||
|
||||
@@ -234,6 +234,7 @@
|
||||
import { computed } from 'vue'
|
||||
import { AlertTriangle, Loader2, RefreshCw } from 'lucide-vue-next'
|
||||
import { Badge, Button } from '@/components/ui'
|
||||
import { formatCompactNumber } from '@/utils/format'
|
||||
import type {
|
||||
ProxyNode,
|
||||
ProxyNodeEvent,
|
||||
@@ -456,9 +457,7 @@ function formatTunnelNumber(key: string) {
|
||||
|
||||
function formatNumber(value: number) {
|
||||
if (!Number.isFinite(value)) return '-'
|
||||
if (Math.abs(value) >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}M`
|
||||
if (Math.abs(value) >= 1_000) return `${(value / 1_000).toFixed(1)}K`
|
||||
return String(Math.round(value))
|
||||
return formatCompactNumber(Math.round(value), { fractionDigits: 1 })
|
||||
}
|
||||
|
||||
function formatOptionalNumber(value: number | null) {
|
||||
|
||||
@@ -335,6 +335,7 @@ import TableBody from '@/components/ui/table-body.vue'
|
||||
import TableRow from '@/components/ui/table-row.vue'
|
||||
import TableHead from '@/components/ui/table-head.vue'
|
||||
import TableCell from '@/components/ui/table-cell.vue'
|
||||
import { formatTokens } from '@/utils/format'
|
||||
|
||||
import type { PublicGlobalModel } from '@/api/public-models'
|
||||
import type { TieredPricingConfig, PricingTier } from '@/api/endpoints/types'
|
||||
@@ -373,12 +374,7 @@ function getTierCount(tieredPricing: TieredPricingConfig | undefined | null): nu
|
||||
|
||||
function formatTierLimit(limit: number | null | undefined): string {
|
||||
if (limit == null) return ''
|
||||
if (limit >= 1000000) {
|
||||
return `${(limit / 1000000).toFixed(1)}M`
|
||||
} else if (limit >= 1000) {
|
||||
return `${(limit / 1000).toFixed(0)}K`
|
||||
}
|
||||
return limit.toString()
|
||||
return formatTokens(limit)
|
||||
}
|
||||
|
||||
function get1hCachePrice(tier: PricingTier): string {
|
||||
|
||||
Reference in New Issue
Block a user