/** * Format a number of seconds into a human-readable duration string. * Examples: "23s", "7m", "2h 15m", "3d" */ export function formatElapsedSecs(secs: number): string { if (secs < 60) return `${Math.round(secs)}s`; const minutes = Math.floor(secs / 60); if (minutes < 60) { const remainSecs = Math.round(secs % 60); return remainSecs > 0 ? `${minutes}m ${remainSecs}s` : `${minutes}m`; } const hours = Math.floor(minutes / 60); if (hours < 24) { const remainMin = minutes % 60; return remainMin > 0 ? `${hours}h ${remainMin}m` : `${hours}h`; } const days = Math.floor(hours / 24); const remainHrs = hours % 24; return remainHrs > 0 ? `${days}d ${remainHrs}h` : `${days}d`; } /** * Format a model context window size (e.g., "128k", "1m", "400"). * Rounds to the nearest thousand before bucketing into k / m. */ export function formatContextWindow(tokens: number): string { const rounded = Math.round(tokens / 1000) * 1000; if (rounded >= 1_000_000) return `${rounded / 1_000_000}m`; if (rounded >= 1_000) return `${rounded / 1_000}k`; return String(tokens); } /** * Format an estimated output throughput in tokens per second (e.g., "85 tok/s"). */ export function formatTokensPerSecond(tps: number | null | undefined): string { if (tps == null) return "—"; return `${Math.trunc(tps)} tok/s`; } /** * Format a byte count for display (e.g., "1.23 MB", "247.32 KB", "742 B"). */ export function formatBytes(bytes: number): string { if (bytes >= 1e9) return `${(bytes / 1e9).toFixed(2)} GB`; if (bytes >= 1e6) return `${(bytes / 1e6).toFixed(2)} MB`; if (bytes >= 1e3) return `${(bytes / 1e3).toFixed(2)} KB`; return `${bytes} B`; } /** * Short relative time string (e.g. "just now", "15s ago", "4m ago", "2h ago", "3d ago"). * Future timestamps clamp to "just now". */ export function formatRelativeTime(iso: string, now: number = Date.now()): string { const then = Date.parse(iso); if (Number.isNaN(then)) return ""; const secs = Math.max(0, Math.floor((now - then) / 1000)); if (secs < 5) return "just now"; if (secs < 60) return `${secs}s ago`; const minutes = Math.floor(secs / 60); if (minutes < 60) return `${minutes}m ago`; const hours = Math.floor(minutes / 60); if (hours < 24) return `${hours}h ago`; const days = Math.floor(hours / 24); return `${days}d ago`; } /** * Format an ISO 8601 timestamp as an absolute, human-readable datetime * (e.g., "04/24/2026, 1:23:40 PM"). Falls back to the input if unparseable. */ export function formatAbsoluteTs(iso: string): string { const ms = Date.parse(iso); if (Number.isNaN(ms)) return iso; return new Date(ms).toLocaleString("en-US", { month: "2-digit", day: "2-digit", year: "numeric", hour: "numeric", minute: "2-digit", second: "2-digit", hour12: true, }); } /** * Format seconds into a duration string for display (e.g., "1m 12s", "23s"). */ export function formatDurationSecs(secs: number): string { if (secs < 60) return `${Math.round(secs)}s`; const minutes = Math.floor(secs / 60); const remainSecs = Math.round(secs % 60); if (minutes < 60) { return remainSecs > 0 ? `${minutes}m ${remainSecs}s` : `${minutes}m`; } const hours = Math.floor(minutes / 60); const remainMin = minutes % 60; return remainMin > 0 ? `${hours}h ${remainMin}m` : `${hours}h`; } export function formatDurationMs(ms: number, fractionDigits = 1): string { if (ms < 1000) return `${Math.round(ms)}ms`; if (ms < 60_000) { const seconds = ms / 1000; return `${Number.isInteger(seconds) ? seconds : seconds.toFixed(fractionDigits)}s`; } if (ms < 3_600_000) { const minutes = Math.floor(ms / 60_000); const seconds = Math.round((ms % 60_000) / 1000); return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`; } if (ms < 86_400_000) { const hours = Math.floor(ms / 3_600_000); const minutes = Math.round((ms % 3_600_000) / 60_000); return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`; } const days = Math.floor(ms / 86_400_000); const hours = Math.round((ms % 86_400_000) / 3_600_000); return hours > 0 ? `${days}d ${hours}h` : `${days}d`; } export function formatTokenCount( value: number, options: { compactDecimal?: boolean } = {}, ): string { if (options.compactDecimal) { if (value < 1_000_000) return `${(value / 1000).toFixed(1)}k`; return `${(value / 1_000_000).toFixed(1)}M`; } if (value < 1000) return `${value}`; if (value < 1_000_000) return `${Math.round(value / 1000)}k`; return `${Math.round(value / 1_000_000)}M`; } const BYTES_PER_GIB = 1024 * 1024 * 1024; const BYTES_PER_MIB = 1024 * 1024; const BYTES_PER_KIB = 1024; /** * Format a byte count as a memory/disk size (e.g. "8 GiB", "2,173 GiB", "742 B"). * Large values get thousands separators. Pass `fractionDigits: 0` to round to * whole units. */ export function formatBytesAsMemory(bytes: number, fractionDigits = 1): string { const scaled = (unit: number) => (bytes / unit).toLocaleString("en-US", { maximumFractionDigits: fractionDigits }); if (bytes >= BYTES_PER_GIB) return `${scaled(BYTES_PER_GIB)} GiB`; if (bytes >= BYTES_PER_MIB) return `${scaled(BYTES_PER_MIB)} MiB`; if (bytes >= BYTES_PER_KIB) return `${scaled(BYTES_PER_KIB)} KiB`; return `${bytes} B`; } /** * Format a CPU-core count for display (whole cores as integer; fractional as 2-decimal). */ export function formatCpuCores(cores: number): string { return Number.isInteger(cores) ? cores.toString() : cores.toFixed(2); } /** * Format a USD-micros amount as a dollar string ("$1.23"). Returns null when the * input is null/undefined so callers can render their own empty placeholder. */ export function formatUsdMicros(usdMicros: number | null | undefined): string | null { if (usdMicros == null) return null; return `$${(usdMicros / 1_000_000).toFixed(2)}`; }