mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-08-02 04:45:21 +00:00
- formatUsd applies the payload currency {code,symbol,rate} once at
display; formatConverted (symbol only) for CLI-preconverted plan
values so nothing converts twice
- provider picker built from providerDetails: label shown, internal id
sent as --provider (fixes filters for providers whose display name
differs, e.g. Grok Build); falls back to map keys on older CLIs
- getYield threads the active provider through preload/main/sections
App suite 170/170, root 1615/1615.
82 lines
3.3 KiB
TypeScript
82 lines
3.3 KiB
TypeScript
type ActiveCurrency = { code: string; symbol: string; rate: number }
|
|
|
|
// Single source of truth for display currency. App.tsx sets it from the overview
|
|
// payload; every formatUsd/formatConverted call site then converts for free.
|
|
// Defaults to USD so the first render (before the payload arrives) is correct.
|
|
let activeCurrency: ActiveCurrency = { code: 'USD', symbol: '$', rate: 1 }
|
|
|
|
export function setActiveCurrency(currency: ActiveCurrency): void {
|
|
activeCurrency = currency
|
|
}
|
|
|
|
/** Raw-USD input: multiplies by the active FX rate, then prefixes the symbol. */
|
|
export function formatUsd(n: number): string {
|
|
return formatConverted(n * activeCurrency.rate)
|
|
}
|
|
|
|
/**
|
|
* Already-converted input (CLI-side convertCost values, e.g. plan budgets): only
|
|
* prefixes the active symbol and formats the magnitude — never re-applies the rate.
|
|
*/
|
|
export function formatConverted(n: number): string {
|
|
return `${activeCurrency.symbol}${n.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`
|
|
}
|
|
|
|
/** Shorten filesystem and CLI-mangled project paths to their useful trailing segments. */
|
|
export function shortenProjectPath(value: string, maxSegments = 3): string {
|
|
const trimmed = value.trim()
|
|
const pathDelimited = /[\\/]/.test(trimmed)
|
|
const parts = trimmed.split(pathDelimited ? /[\\/]+/ : /-+/).filter(Boolean)
|
|
let displayParts = parts.slice(-Math.max(1, maxSegments))
|
|
|
|
// A tail rooted directly under a home directory starts with the user name,
|
|
// which adds noise rather than useful project context.
|
|
const precedingPart = parts.at(-(displayParts.length + 1))
|
|
if (displayParts.length > 1 && /^(users?|home)$/i.test(precedingPart ?? '')) {
|
|
displayParts = displayParts.slice(1)
|
|
}
|
|
|
|
if (/^(projects|src|config)$/i.test(displayParts[0] ?? '')) {
|
|
displayParts[0] = displayParts[0].toLowerCase()
|
|
}
|
|
|
|
return displayParts.join('/') || trimmed
|
|
}
|
|
|
|
/** Compact token/count formatting: 1_842 → "1.8K", 184_000 → "184K", 1_200_000 → "1.2M". */
|
|
export function formatCompact(n: number): string {
|
|
if (!Number.isFinite(n)) return '—'
|
|
if (n === 0) return '0'
|
|
const abs = Math.abs(n)
|
|
if (abs < 1_000) return String(Math.round(n))
|
|
if (abs < 1_000_000) return `${trim(n / 1_000)}K`
|
|
if (abs < 1_000_000_000) return `${trim(n / 1_000_000)}M`
|
|
return `${trim(n / 1_000_000_000)}B`
|
|
}
|
|
|
|
// One decimal, but drop a trailing ".0" (184.0K → "184K", 1.2K stays "1.2K").
|
|
function trim(v: number): string {
|
|
const s = v.toFixed(1)
|
|
return s.endsWith('.0') ? s.slice(0, -2) : s
|
|
}
|
|
|
|
/** "Jul 10" — short month + day, no year. */
|
|
export function formatDayShort(iso: string): string {
|
|
const d = new Date(iso)
|
|
return Number.isNaN(d.getTime()) ? '—' : d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })
|
|
}
|
|
|
|
/** "Jul 10, 2026" — full date. */
|
|
export function formatDayLong(iso: string): string {
|
|
const d = new Date(iso)
|
|
return Number.isNaN(d.getTime()) ? '—' : d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })
|
|
}
|
|
|
|
/** "2h 14m" / "47m" / "38s" from a duration in ms. */
|
|
export function formatDuration(ms: number): string {
|
|
if (!Number.isFinite(ms) || ms <= 0) return '—'
|
|
const totalMin = Math.round(ms / 60_000)
|
|
if (totalMin < 1) return `${Math.round(ms / 1000)}s`
|
|
if (totalMin < 60) return `${totalMin}m`
|
|
return `${Math.floor(totalMin / 60)}h ${totalMin % 60}m`
|
|
}
|