diff --git a/console/src/lib/format.test.ts b/console/src/lib/format.test.ts new file mode 100644 index 00000000..2e7f7bfc --- /dev/null +++ b/console/src/lib/format.test.ts @@ -0,0 +1,26 @@ +import { describe, expect, it } from 'vitest'; +import { pluralize } from './format'; + +describe('pluralize', () => { + it('returns singular when n is 1', () => { + expect(pluralize(1, 'call')).toBe('1 call'); + }); + + it('returns plural when n is 0', () => { + expect(pluralize(0, 'call')).toBe('0 calls'); + }); + + it('returns plural when n is greater than 1', () => { + expect(pluralize(2, 'call')).toBe('2 calls'); + expect(pluralize(100, 'call')).toBe('100 calls'); + }); + + it('uses a custom plural form for irregular plurals', () => { + expect(pluralize(1, 'person', 'people')).toBe('1 person'); + expect(pluralize(3, 'person', 'people')).toBe('3 people'); + }); + + it('handles negative numbers as plural', () => { + expect(pluralize(-1, 'call')).toBe('-1 calls'); + }); +}); diff --git a/console/src/lib/format.ts b/console/src/lib/format.ts index 853b3a02..8e31910a 100644 --- a/console/src/lib/format.ts +++ b/console/src/lib/format.ts @@ -57,6 +57,14 @@ export function formatUptime(totalSeconds: number): string { return parts.join(' '); } +export function pluralize( + n: number, + word: string, + plural = `${word}s`, +): string { + return `${n} ${n === 1 ? word : plural}`; +} + export function parseStringList(value: string): string[] { return value .split(/[\n,]/g) diff --git a/console/src/routes/channels-catalog.ts b/console/src/routes/channels-catalog.ts index 22333297..886babbd 100644 --- a/console/src/routes/channels-catalog.ts +++ b/console/src/routes/channels-catalog.ts @@ -1,4 +1,5 @@ import type { AdminConfig } from '../api/types'; +import { pluralize } from '../lib/format'; export type ChannelKind = | 'discord' @@ -51,9 +52,6 @@ export function countTeamsOverrides(config: AdminConfig): number { }, 0); } -function pluralize(count: number, singular: string, plural = `${singular}s`) { - return `${count} ${count === 1 ? singular : plural}`; -} function describeDiscord( config: AdminConfig, diff --git a/console/src/routes/dashboard.tsx b/console/src/routes/dashboard.tsx index 64ddce2d..7e8d92db 100644 --- a/console/src/routes/dashboard.tsx +++ b/console/src/routes/dashboard.tsx @@ -16,6 +16,7 @@ import { formatTokenBreakdown, formatUptime, formatUsd, + pluralize, } from '../lib/format'; import { compareDateTime, compareNumber, compareText } from '../lib/sort'; @@ -170,7 +171,7 @@ export function DashboardPage() { {formatUsd(overview.usage.daily.totalCostUsd)} across{' '} - {overview.usage.daily.callCount} calls + {pluralize(overview.usage.daily.callCount, 'call')}
@@ -186,7 +187,7 @@ export function DashboardPage() { {formatUsd(overview.usage.monthly.totalCostUsd)} across{' '} - {overview.usage.monthly.callCount} calls + {pluralize(overview.usage.monthly.callCount, 'call')}
@@ -205,7 +206,7 @@ export function DashboardPage() { inputTokens: row.totalInputTokens ?? 0, outputTokens: row.totalOutputTokens ?? 0, })}{' '} - · {row.callCount} calls this month + · {pluralize(row.callCount, 'call')} this month {formatUsd(row.totalCostUsd)} diff --git a/console/src/routes/models.tsx b/console/src/routes/models.tsx index 50789664..3a2c4f72 100644 --- a/console/src/routes/models.tsx +++ b/console/src/routes/models.tsx @@ -15,6 +15,7 @@ import { formatRelativeTime, formatTokenBreakdown, formatUsd, + pluralize, } from '../lib/format'; import { compareNumber, compareText } from '../lib/sort'; @@ -308,7 +309,7 @@ export function ModelsPage() { {modelsQuery.isLoading ? (
Loading model catalog...
@@ -376,7 +377,7 @@ export function ModelsPage() { {formatUsd(model.usageMonthly.totalCostUsd)} ·{' '} - {model.usageMonthly.callCount} calls + {pluralize(model.usageMonthly.callCount, 'call')} ) : ( @@ -421,7 +422,7 @@ export function ModelsPage() { inputTokens: model.usageDaily.totalInputTokens ?? 0, outputTokens: model.usageDaily.totalOutputTokens ?? 0, })}{' '} - · {model.usageDaily.callCount} calls today + · {pluralize(model.usageDaily.callCount, 'call')} today {formatUsd(model.usageDaily.totalCostUsd ?? 0)}