| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218 |
- import dayjs from '@/lib/dayjs'
- import {
- formatCurrencyFromUSD,
- formatQuotaWithCurrency,
- getCurrencyDisplay,
- } from './currency'
- // ============================================================================
- // Number Formatting
- // ============================================================================
- export function formatNumber(value: number | null | undefined): string {
- if (value == null || Number.isNaN(value as number)) return '-'
- return Intl.NumberFormat(undefined, { maximumFractionDigits: 2 }).format(
- value as number
- )
- }
- export function formatCompactNumber(value: number | null | undefined): string {
- if (value == null || Number.isNaN(value as number)) return '-'
- return Intl.NumberFormat(undefined, {
- notation: 'compact',
- maximumFractionDigits: 1,
- }).format(value as number)
- }
- export function formatPercent(value: number | null | undefined): string {
- if (value == null || Number.isNaN(value as number)) return '-'
- return Intl.NumberFormat(undefined, {
- style: 'percent',
- maximumFractionDigits: 2,
- }).format((value as number) / 100)
- }
- export function formatCurrencyUSD(value: number | null | undefined): string {
- return formatCurrencyFromUSD(value == null ? null : (value as number))
- }
- // ============================================================================
- // Quota Formatting (500,000 units = $1)
- // ============================================================================
- /**
- * Format quota into the configured display amount.
- * Quota is stored in units where `quotaPerUnit` equals 1 USD.
- */
- export function formatQuota(quota: number): string {
- return formatQuotaWithCurrency(quota, {
- digitsLarge: 2,
- digitsSmall: 4,
- abbreviate: true,
- })
- }
- /**
- * Parse quota from the current display input back to quota units.
- */
- export function parseQuotaFromDollars(amount: number): number {
- if (!Number.isFinite(amount)) return 0
- const { config, meta } = getCurrencyDisplay()
- // Tokens-only or raw quota mode
- if (meta.kind === 'tokens') {
- return Math.round(amount)
- }
- const exchangeRate =
- meta.kind === 'currency' || meta.kind === 'custom' ? meta.exchangeRate : 1
- const usdAmount = exchangeRate > 0 ? amount / exchangeRate : amount
- return Math.round(usdAmount * config.quotaPerUnit)
- }
- /**
- * Convert quota units to the configured display amount.
- * Reverse of parseQuotaFromDollars.
- */
- export function quotaUnitsToDollars(units: number): number {
- const { config, meta } = getCurrencyDisplay()
- if (meta.kind === 'tokens') {
- return units
- }
- const usdAmount = units / config.quotaPerUnit
- const exchangeRate =
- meta.kind === 'currency' || meta.kind === 'custom' ? meta.exchangeRate : 1
- return usdAmount * exchangeRate
- }
- // ============================================================================
- // Timestamp Formatting
- // ============================================================================
- /**
- * Format Unix timestamp (seconds) to YYYY-MM-DD HH:mm:ss
- */
- export function formatTimestamp(timestamp: number): string {
- if (timestamp === -1) {
- return 'Never'
- }
- return formatTimestampToDate(timestamp)
- }
- /**
- * Format timestamp to YYYY-MM-DD HH:mm:ss
- * @param timestamp - Timestamp in seconds or milliseconds
- * @param unit - Unit of the timestamp ('seconds' or 'milliseconds')
- */
- export function formatTimestampToDate(
- timestamp?: number,
- unit: 'seconds' | 'milliseconds' = 'seconds'
- ): string {
- if (!timestamp || timestamp === -1 || timestamp === 0) {
- return '-'
- }
- const ms = unit === 'seconds' ? timestamp * 1000 : timestamp
- return dayjs(ms).format('YYYY-MM-DD HH:mm:ss')
- }
- /** Format a Date object to YYYY-MM-DD HH:mm:ss */
- export function formatDateTimeStr(date: Date): string {
- return dayjs(date).format('YYYY-MM-DD HH:mm:ss')
- }
- /** Format a Date object to YYYY-MM-DD */
- export function formatDateStr(date: Date): string {
- return dayjs(date).format('YYYY-MM-DD')
- }
- /** Format a Date object to HH:mm:ss */
- export function formatTimeStr(date: Date): string {
- return dayjs(date).format('HH:mm:ss')
- }
- /**
- * Format quota for usage logs with higher precision
- * Uses 6 decimal places to show very small costs accurately
- */
- export function formatLogQuota(quota: number): string {
- return formatQuotaWithCurrency(quota, {
- digitsLarge: 4,
- digitsSmall: 6,
- abbreviate: false,
- })
- }
- /**
- * Format tokens count with K/M suffixes
- */
- export function formatTokens(tokens: number): string {
- if (tokens === 0) return '-'
- if (tokens < 1000) return tokens.toString()
- if (tokens < 1000000) return `${(tokens / 1000).toFixed(1)}K`
- return `${(tokens / 1000000).toFixed(2)}M`
- }
- /**
- * Format use time in seconds with appropriate unit
- */
- export function formatUseTime(seconds: number): string {
- if (seconds < 60) return `${seconds.toFixed(1)}s`
- const minutes = Math.floor(seconds / 60)
- const remainingSeconds = seconds % 60
- return `${minutes}m ${remainingSeconds.toFixed(0)}s`
- }
- /**
- * Format timestamp to date input value (YYYY-MM-DDTHH:mm)
- */
- export function formatTimestampForInput(timestamp: number): string {
- if (timestamp === -1) {
- return ''
- }
- return dayjs(timestamp * 1000).format('YYYY-MM-DDTHH:mm')
- }
- /**
- * Parse datetime-local input to Unix timestamp
- */
- export function parseTimestampFromInput(value: string): number {
- if (!value) {
- return -1
- }
- const date = new Date(value)
- return Math.floor(date.getTime() / 1000)
- }
- // ============================================================================
- // Color Generation
- // ============================================================================
- /**
- * Generate a consistent color from a string
- * Uses HSL for better color distribution
- */
- export function stringToColor(str: string): string {
- if (!str) return 'gray'
- // Generate hash from string
- let hash = 0
- for (let i = 0; i < str.length; i++) {
- hash = str.charCodeAt(i) + ((hash << 5) - hash)
- hash = hash & hash // Convert to 32-bit integer
- }
- // Use hash to generate hue (0-360)
- const hue = Math.abs(hash % 360)
- // Use saturation and lightness that work well for tags
- const saturation = 65 + (Math.abs(hash) % 10) // 65-75%
- const lightness = 55 + (Math.abs(hash >> 8) % 10) // 55-65%
- return `hsl(${hue}, ${saturation}%, ${lightness}%)`
- }
|