diff --git a/src/components/dashboard/chart-theme.ts b/src/components/dashboard/chart-theme.ts
index 9ad328e9..0fa3162c 100644
--- a/src/components/dashboard/chart-theme.ts
+++ b/src/components/dashboard/chart-theme.ts
@@ -114,3 +114,12 @@ export function formatDuration(ms: number): string {
}
return `${Math.round(ms)}ms`;
}
+
+export function formatCost(usd: number): string {
+ if (usd === 0) return "$0.00";
+ if (usd >= 1_000_000) return `$${(usd / 1_000_000).toFixed(2)}M`;
+ if (usd >= 1_000) return `$${(usd / 1_000).toFixed(2)}K`;
+ if (usd >= 0.01) return `$${usd.toFixed(2)}`;
+ // 小于一分时保留四位小数,既能让真实存在的消费显示出来,又不会过长
+ return `$${usd.toFixed(4)}`;
+}
diff --git a/src/components/dashboard/leaderboard-section.tsx b/src/components/dashboard/leaderboard-section.tsx
index 3e155842..16fd226f 100644
--- a/src/components/dashboard/leaderboard-section.tsx
+++ b/src/components/dashboard/leaderboard-section.tsx
@@ -8,7 +8,7 @@ import { Card, CardContent } from "@/components/ui/card";
import { cn } from "@/lib/utils";
import type { StatsLeaderboardResponse, DistributionItem } from "@/types/api";
-import { formatNumber, UPSTREAM_COLORS_DARK } from "./chart-theme";
+import { formatCost, formatNumber, UPSTREAM_COLORS_DARK } from "./chart-theme";
import { DashboardLoadingBlock, DashboardLoadingSurface } from "./dashboard-loading";
interface LeaderboardSectionProps {
@@ -40,12 +40,6 @@ function getTtftClass(ttftMs: number): string {
return "text-status-success";
}
-function formatCost(usd: number): string {
- if (usd === 0) return "$0.00";
- if (usd < 0.01) return `$${usd.toFixed(6)}`;
- return `$${usd.toFixed(4)}`;
-}
-
const RANK_ROW_BASE =
"flex items-center gap-3 rounded-cf-sm border-l-2 px-2.5 py-1.5 transition-colors bg-surface-300/42 hover:bg-surface-400/55";
diff --git a/src/components/dashboard/stats-cards.tsx b/src/components/dashboard/stats-cards.tsx
index 30e20b6f..fb5f125f 100644
--- a/src/components/dashboard/stats-cards.tsx
+++ b/src/components/dashboard/stats-cards.tsx
@@ -15,7 +15,7 @@ import {
import { Card, CardContent } from "@/components/ui/card";
import { cn } from "@/lib/utils";
-import { formatDuration, formatNumber } from "./chart-theme";
+import { formatCost, formatDuration, formatNumber } from "./chart-theme";
import { DashboardLoadingBlock, DashboardLoadingSurface } from "./dashboard-loading";
interface StatsCardsProps {
@@ -52,12 +52,6 @@ function formatCacheRate(rate: number): string {
return `${normalizedRate.toFixed(2)}%`;
}
-function formatCost(usd: number): string {
- if (usd === 0) return "$0.00";
- if (usd < 0.01) return `$${usd.toFixed(6)}`;
- return `$${usd.toFixed(4)}`;
-}
-
interface DeltaBadgeProps {
today: number;
yesterday: number;
diff --git a/tests/components/dashboard/leaderboard-section.test.tsx b/tests/components/dashboard/leaderboard-section.test.tsx
index 8af07f28..12b0c554 100644
--- a/tests/components/dashboard/leaderboard-section.test.tsx
+++ b/tests/components/dashboard/leaderboard-section.test.tsx
@@ -197,6 +197,36 @@ describe("LeaderboardSection", () => {
expect(screen.getByText("10.0K")).toBeInTheDocument();
});
+ it("renders costs with two decimals", () => {
+ render();
+
+ expect(screen.getByText("$12.50")).toBeInTheDocument();
+ expect(screen.getByText("$8.00")).toBeInTheDocument();
+ expect(screen.getByText("$0.50")).toBeInTheDocument();
+ });
+
+ it("renders sub-cent cost with four decimals", () => {
+ const data: StatsLeaderboardResponse = {
+ api_keys: [
+ {
+ id: "key-tiny",
+ name: "Tiny Key",
+ key_prefix: "sk-tiny",
+ request_count: 1,
+ total_tokens: 10,
+ total_cost_usd: 0.006599,
+ model_distribution: [],
+ },
+ ],
+ upstreams: [],
+ models: [],
+ };
+
+ render();
+
+ expect(screen.getByText("$0.0066")).toBeInTheDocument();
+ });
+
it("renders distribution pie charts with fixed dimensions", () => {
render();
diff --git a/tests/components/dashboard/stats-cards.test.tsx b/tests/components/dashboard/stats-cards.test.tsx
index e26dcfeb..ae29abb7 100644
--- a/tests/components/dashboard/stats-cards.test.tsx
+++ b/tests/components/dashboard/stats-cards.test.tsx
@@ -200,6 +200,38 @@ describe("StatsCards", () => {
});
});
+ describe("Cost formatting", () => {
+ it("renders zero cost as $0.00", () => {
+ render();
+
+ expect(screen.getByText("$0.00")).toBeInTheDocument();
+ });
+
+ it("renders sub-cent cost with four decimals", () => {
+ render();
+
+ expect(screen.getByText("$0.0066")).toBeInTheDocument();
+ });
+
+ it("renders regular cost with two decimals", () => {
+ render();
+
+ expect(screen.getByText("$12.50")).toBeInTheDocument();
+ });
+
+ it("abbreviates thousand-dollar cost with K suffix", () => {
+ render();
+
+ expect(screen.getByText("$1.23K")).toBeInTheDocument();
+ });
+
+ it("abbreviates million-dollar cost with M suffix", () => {
+ render();
+
+ expect(screen.getByText("$2.50M")).toBeInTheDocument();
+ });
+ });
+
describe("Icons", () => {
it("renders activity icon for requests", () => {
render(