Files
paseo/packages/app/src/usage/format.ts
T

144 lines
4.9 KiB
TypeScript

import { i18n } from "@/i18n/i18next";
import type { UsageDisplayAs } from "./preferences";
import type { UsageBalanceUnit } from "./types";
const providerUsageLabelKeys = {
session: "providerUsage.labels.session",
weekly: "providerUsage.labels.weekly",
monthly: "providerUsage.labels.monthly",
code_review: "providerUsage.labels.codeReview",
credits: "providerUsage.labels.credits",
monthly_credits: "providerUsage.labels.monthlyCredits",
} as const;
export function formatProviderUsageLabel(id: string, fallback: string): string {
const key = providerUsageLabelKeys[id as keyof typeof providerUsageLabelKeys];
return key ? i18n.t(key) : fallback;
}
export function clampPct(value: number): number {
return Math.max(0, Math.min(100, value));
}
export function formatPct(value: number, locale?: string): string {
const resolvedLocale = locale ?? i18n.resolvedLanguage;
return new Intl.NumberFormat(resolvedLocale, {
style: "percent",
maximumFractionDigits: 0,
}).format(clampPct(value) / 100);
}
/** A localized percentage that says whether it is used or remaining. */
export function formatDisplayPct(
value: number,
displayAs: UsageDisplayAs,
locale?: string,
): string {
const percentage = formatPct(value, locale);
return displayAs === "used"
? i18n.t("providerUsage.values.used", { percentage })
: i18n.t("providerUsage.values.remaining", { amount: percentage });
}
type RelativeDuration = { unit: "now" } | { unit: "minutes" | "hours" | "days"; count: number };
function formatCount(value: number): string {
return new Intl.NumberFormat(i18n.resolvedLanguage).format(value);
}
function relativeDuration(iso: string, now: number): RelativeDuration | null {
const diffMs = new Date(iso).getTime() - now;
if (!Number.isFinite(diffMs)) return null;
if (diffMs <= 0) return { unit: "now" };
const diffMinutes = Math.floor(diffMs / 60_000);
const diffHours = Math.floor(diffMinutes / 60);
const diffDays = Math.floor(diffHours / 24);
if (diffDays > 0) return { unit: "days", count: diffDays };
if (diffHours > 0) return { unit: "hours", count: diffHours };
return { unit: "minutes", count: diffMinutes };
}
function formatRelativeDuration(duration: RelativeDuration): string | null {
switch (duration.unit) {
case "now":
return null;
case "days":
return i18n.t("providerUsage.duration.days", {
count: duration.count,
value: formatCount(duration.count),
});
case "hours":
return i18n.t("providerUsage.duration.hours", {
count: duration.count,
value: formatCount(duration.count),
});
case "minutes":
return i18n.t("providerUsage.duration.minutes", {
count: duration.count,
value: formatCount(duration.count),
});
}
}
export function formatResetLabel(iso: string | null | undefined, now = Date.now()): string | null {
if (!iso) return null;
const rel = relativeDuration(iso, now);
if (!rel) return null;
if (rel.unit === "now") return i18n.t("providerUsage.timing.resettingNow");
const duration = formatRelativeDuration(rel);
return duration ? i18n.t("providerUsage.timing.resetsIn", { duration }) : null;
}
export function formatRunsOutLabel(
iso: string | null | undefined,
now = Date.now(),
): string | null {
if (!iso) return null;
const rel = relativeDuration(iso, now);
if (!rel) return null;
if (rel.unit === "now") return i18n.t("providerUsage.timing.runsOutNow");
const duration = formatRelativeDuration(rel);
return duration ? i18n.t("providerUsage.timing.runsOutIn", { duration }) : null;
}
export function formatAgo(iso: string | null | undefined, now = Date.now()): string | null {
if (!iso) return null;
const diffMs = now - new Date(iso).getTime();
if (!Number.isFinite(diffMs)) return null;
if (diffMs < 60_000) return i18n.t("providerUsage.timing.justNow");
const diffMinutes = Math.floor(diffMs / 60_000);
const diffHours = Math.floor(diffMinutes / 60);
const diffDays = Math.floor(diffHours / 24);
if (diffDays > 0) {
return i18n.t("providerUsage.timing.daysAgo", {
count: diffDays,
value: formatCount(diffDays),
});
}
if (diffHours > 0) {
return i18n.t("providerUsage.timing.hoursAgo", {
count: diffHours,
value: formatCount(diffHours),
});
}
return i18n.t("providerUsage.timing.minutesAgo", {
count: diffMinutes,
value: formatCount(diffMinutes),
});
}
/** A balance amount as the app's language writes it: "$1,234.50", "12,345". */
export function formatAmount(value: number, unit: UsageBalanceUnit, locale?: string): string {
switch (unit) {
case "usd":
return new Intl.NumberFormat(locale, { style: "currency", currency: "USD" }).format(value);
case "tokens":
return new Intl.NumberFormat(locale, {
notation: "compact",
maximumFractionDigits: 1,
}).format(value);
default:
return new Intl.NumberFormat(locale, { maximumFractionDigits: 2 }).format(value);
}
}