format.ts 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. import dayjs from '@/lib/dayjs'
  2. import {
  3. formatCurrencyFromUSD,
  4. formatQuotaWithCurrency,
  5. getCurrencyDisplay,
  6. } from './currency'
  7. // ============================================================================
  8. // Number Formatting
  9. // ============================================================================
  10. export function formatNumber(value: number | null | undefined): string {
  11. if (value == null || Number.isNaN(value as number)) return '-'
  12. return Intl.NumberFormat(undefined, { maximumFractionDigits: 2 }).format(
  13. value as number
  14. )
  15. }
  16. export function formatCompactNumber(value: number | null | undefined): string {
  17. if (value == null || Number.isNaN(value as number)) return '-'
  18. return Intl.NumberFormat(undefined, {
  19. notation: 'compact',
  20. maximumFractionDigits: 1,
  21. }).format(value as number)
  22. }
  23. export function formatPercent(value: number | null | undefined): string {
  24. if (value == null || Number.isNaN(value as number)) return '-'
  25. return Intl.NumberFormat(undefined, {
  26. style: 'percent',
  27. maximumFractionDigits: 2,
  28. }).format((value as number) / 100)
  29. }
  30. export function formatCurrencyUSD(value: number | null | undefined): string {
  31. return formatCurrencyFromUSD(value == null ? null : (value as number))
  32. }
  33. // ============================================================================
  34. // Quota Formatting (500,000 units = $1)
  35. // ============================================================================
  36. /**
  37. * Format quota into the configured display amount.
  38. * Quota is stored in units where `quotaPerUnit` equals 1 USD.
  39. */
  40. export function formatQuota(quota: number): string {
  41. return formatQuotaWithCurrency(quota, {
  42. digitsLarge: 2,
  43. digitsSmall: 4,
  44. abbreviate: true,
  45. })
  46. }
  47. /**
  48. * Parse quota from the current display input back to quota units.
  49. */
  50. export function parseQuotaFromDollars(amount: number): number {
  51. if (!Number.isFinite(amount)) return 0
  52. const { config, meta } = getCurrencyDisplay()
  53. // Tokens-only or raw quota mode
  54. if (meta.kind === 'tokens') {
  55. return Math.round(amount)
  56. }
  57. const exchangeRate =
  58. meta.kind === 'currency' || meta.kind === 'custom' ? meta.exchangeRate : 1
  59. const usdAmount = exchangeRate > 0 ? amount / exchangeRate : amount
  60. return Math.round(usdAmount * config.quotaPerUnit)
  61. }
  62. /**
  63. * Convert quota units to the configured display amount.
  64. * Reverse of parseQuotaFromDollars.
  65. */
  66. export function quotaUnitsToDollars(units: number): number {
  67. const { config, meta } = getCurrencyDisplay()
  68. if (meta.kind === 'tokens') {
  69. return units
  70. }
  71. const usdAmount = units / config.quotaPerUnit
  72. const exchangeRate =
  73. meta.kind === 'currency' || meta.kind === 'custom' ? meta.exchangeRate : 1
  74. return usdAmount * exchangeRate
  75. }
  76. // ============================================================================
  77. // Timestamp Formatting
  78. // ============================================================================
  79. /**
  80. * Format Unix timestamp (seconds) to YYYY-MM-DD HH:mm:ss
  81. */
  82. export function formatTimestamp(timestamp: number): string {
  83. if (timestamp === -1) {
  84. return 'Never'
  85. }
  86. return formatTimestampToDate(timestamp)
  87. }
  88. /**
  89. * Format timestamp to YYYY-MM-DD HH:mm:ss
  90. * @param timestamp - Timestamp in seconds or milliseconds
  91. * @param unit - Unit of the timestamp ('seconds' or 'milliseconds')
  92. */
  93. export function formatTimestampToDate(
  94. timestamp?: number,
  95. unit: 'seconds' | 'milliseconds' = 'seconds'
  96. ): string {
  97. if (!timestamp || timestamp === -1 || timestamp === 0) {
  98. return '-'
  99. }
  100. const ms = unit === 'seconds' ? timestamp * 1000 : timestamp
  101. return dayjs(ms).format('YYYY-MM-DD HH:mm:ss')
  102. }
  103. /** Format a Date object to YYYY-MM-DD HH:mm:ss */
  104. export function formatDateTimeStr(date: Date): string {
  105. return dayjs(date).format('YYYY-MM-DD HH:mm:ss')
  106. }
  107. /** Format a Date object to YYYY-MM-DD */
  108. export function formatDateStr(date: Date): string {
  109. return dayjs(date).format('YYYY-MM-DD')
  110. }
  111. /** Format a Date object to HH:mm:ss */
  112. export function formatTimeStr(date: Date): string {
  113. return dayjs(date).format('HH:mm:ss')
  114. }
  115. /**
  116. * Format quota for usage logs with higher precision
  117. * Uses 6 decimal places to show very small costs accurately
  118. */
  119. export function formatLogQuota(quota: number): string {
  120. return formatQuotaWithCurrency(quota, {
  121. digitsLarge: 4,
  122. digitsSmall: 6,
  123. abbreviate: false,
  124. })
  125. }
  126. /**
  127. * Format tokens count with K/M suffixes
  128. */
  129. export function formatTokens(tokens: number): string {
  130. if (tokens === 0) return '-'
  131. if (tokens < 1000) return tokens.toString()
  132. if (tokens < 1000000) return `${(tokens / 1000).toFixed(1)}K`
  133. return `${(tokens / 1000000).toFixed(2)}M`
  134. }
  135. /**
  136. * Format use time in seconds with appropriate unit
  137. */
  138. export function formatUseTime(seconds: number): string {
  139. if (seconds < 60) return `${seconds.toFixed(1)}s`
  140. const minutes = Math.floor(seconds / 60)
  141. const remainingSeconds = seconds % 60
  142. return `${minutes}m ${remainingSeconds.toFixed(0)}s`
  143. }
  144. /**
  145. * Format timestamp to date input value (YYYY-MM-DDTHH:mm)
  146. */
  147. export function formatTimestampForInput(timestamp: number): string {
  148. if (timestamp === -1) {
  149. return ''
  150. }
  151. return dayjs(timestamp * 1000).format('YYYY-MM-DDTHH:mm')
  152. }
  153. /**
  154. * Parse datetime-local input to Unix timestamp
  155. */
  156. export function parseTimestampFromInput(value: string): number {
  157. if (!value) {
  158. return -1
  159. }
  160. const date = new Date(value)
  161. return Math.floor(date.getTime() / 1000)
  162. }
  163. // ============================================================================
  164. // Color Generation
  165. // ============================================================================
  166. /**
  167. * Generate a consistent color from a string
  168. * Uses HSL for better color distribution
  169. */
  170. export function stringToColor(str: string): string {
  171. if (!str) return 'gray'
  172. // Generate hash from string
  173. let hash = 0
  174. for (let i = 0; i < str.length; i++) {
  175. hash = str.charCodeAt(i) + ((hash << 5) - hash)
  176. hash = hash & hash // Convert to 32-bit integer
  177. }
  178. // Use hash to generate hue (0-360)
  179. const hue = Math.abs(hash % 360)
  180. // Use saturation and lightness that work well for tags
  181. const saturation = 65 + (Math.abs(hash) % 10) // 65-75%
  182. const lightness = 55 + (Math.abs(hash >> 8) % 10) // 55-65%
  183. return `hsl(${hue}, ${saturation}%, ${lightness}%)`
  184. }