Show output tokens in detail stats
This commit is contained in:
@@ -10,6 +10,7 @@ import { TimeRangeSelector } from "@/components/TimeRangeSelector";
|
||||
import { TrendChart } from "@/components/charts/TrendChart";
|
||||
import { buildQuery, formatNumber, formatTokens, formatUSD } from "@/lib/utils";
|
||||
import { sortDetailBreakdown, type DetailBreakdownItem, type DetailBreakdownSortKey } from "@/lib/detail-sort";
|
||||
import { getDetailStats, type DetailStatKey } from "@/lib/detail-stats";
|
||||
import { useTimeRange } from "@/lib/time-range-context";
|
||||
import { useI18n } from "@/lib/i18n";
|
||||
|
||||
@@ -79,6 +80,16 @@ export default function DetailPage() {
|
||||
{ key: "quota", label: t("th.cost"), align: "right" },
|
||||
];
|
||||
|
||||
const statIcons: Record<DetailStatKey, typeof Hash> = {
|
||||
calls: Hash,
|
||||
total_tokens: Zap,
|
||||
quota: DollarSign,
|
||||
prompt_tokens: MessageSquare,
|
||||
completion_tokens: MessageSquare,
|
||||
cache_creation_tokens: DatabaseZap,
|
||||
cache_read_tokens: BookOpen,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
@@ -98,13 +109,17 @@ export default function DetailPage() {
|
||||
<div className="flex h-64 items-center justify-center"><div className="h-6 w-6 animate-spin rounded-full spinner" /></div>
|
||||
) : data ? (
|
||||
<>
|
||||
<div className="grid grid-cols-2 gap-4 md:grid-cols-6">
|
||||
<StatsCard title={t("dash.totalCalls")} value={data.calls} icon={Hash} delay={0} />
|
||||
<StatsCard title={t("th.totalToken")} value={data.total_tokens} format="tokens" icon={Zap} delay={0.05} />
|
||||
<StatsCard title={t("th.cost")} value={data.quota / 500000} format="usd" icon={DollarSign} delay={0.1} />
|
||||
<StatsCard title={t("th.input")} value={data.prompt_tokens} format="tokens" icon={MessageSquare} delay={0.15} />
|
||||
<StatsCard title={t("th.cacheCreation")} value={data.cache_creation_tokens} format="tokens" icon={DatabaseZap} delay={0.2} />
|
||||
<StatsCard title={t("th.cacheRead")} value={data.cache_read_tokens} format="tokens" icon={BookOpen} delay={0.25} />
|
||||
<div className="grid grid-cols-2 gap-4 md:grid-cols-4 xl:grid-cols-7">
|
||||
{getDetailStats(data).map((stat, i) => (
|
||||
<StatsCard
|
||||
key={stat.key}
|
||||
title={t(stat.labelKey)}
|
||||
value={stat.value}
|
||||
format={stat.format}
|
||||
icon={statIcons[stat.key]}
|
||||
delay={i * 0.05}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<motion.div initial={{ opacity: 0, y: 16 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: 0.15 }} className="glass p-5">
|
||||
|
||||
32
lib/detail-stats.test.ts
Normal file
32
lib/detail-stats.test.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { getDetailStats } from "./detail-stats";
|
||||
|
||||
describe("getDetailStats", () => {
|
||||
test("includes completion tokens in detail page stat cards", () => {
|
||||
const stats = getDetailStats({
|
||||
calls: 12,
|
||||
total_tokens: 1000,
|
||||
quota: 250000,
|
||||
prompt_tokens: 400,
|
||||
completion_tokens: 350,
|
||||
cache_creation_tokens: 150,
|
||||
cache_read_tokens: 100,
|
||||
});
|
||||
|
||||
expect(stats.map((stat) => stat.key)).toEqual([
|
||||
"calls",
|
||||
"total_tokens",
|
||||
"quota",
|
||||
"prompt_tokens",
|
||||
"completion_tokens",
|
||||
"cache_creation_tokens",
|
||||
"cache_read_tokens",
|
||||
]);
|
||||
expect(stats.find((stat) => stat.key === "completion_tokens")).toEqual({
|
||||
key: "completion_tokens",
|
||||
labelKey: "th.output",
|
||||
value: 350,
|
||||
format: "tokens",
|
||||
});
|
||||
});
|
||||
});
|
||||
41
lib/detail-stats.ts
Normal file
41
lib/detail-stats.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import type { TranslationKey } from "@/lib/i18n";
|
||||
|
||||
export type DetailStatKey =
|
||||
| "calls"
|
||||
| "total_tokens"
|
||||
| "quota"
|
||||
| "prompt_tokens"
|
||||
| "completion_tokens"
|
||||
| "cache_creation_tokens"
|
||||
| "cache_read_tokens";
|
||||
|
||||
export type DetailStatFormat = "number" | "tokens" | "usd";
|
||||
|
||||
export interface DetailStatsData {
|
||||
calls: number;
|
||||
total_tokens: number;
|
||||
quota: number;
|
||||
prompt_tokens: number;
|
||||
completion_tokens: number;
|
||||
cache_creation_tokens: number;
|
||||
cache_read_tokens: number;
|
||||
}
|
||||
|
||||
export interface DetailStatItem {
|
||||
key: DetailStatKey;
|
||||
labelKey: TranslationKey;
|
||||
value: number;
|
||||
format?: DetailStatFormat;
|
||||
}
|
||||
|
||||
export function getDetailStats(data: DetailStatsData): DetailStatItem[] {
|
||||
return [
|
||||
{ key: "calls", labelKey: "dash.totalCalls", value: data.calls },
|
||||
{ key: "total_tokens", labelKey: "th.totalToken", value: data.total_tokens, format: "tokens" },
|
||||
{ key: "quota", labelKey: "th.cost", value: data.quota / 500000, format: "usd" },
|
||||
{ key: "prompt_tokens", labelKey: "th.input", value: data.prompt_tokens, format: "tokens" },
|
||||
{ key: "completion_tokens", labelKey: "th.output", value: data.completion_tokens, format: "tokens" },
|
||||
{ key: "cache_creation_tokens", labelKey: "th.cacheCreation", value: data.cache_creation_tokens, format: "tokens" },
|
||||
{ key: "cache_read_tokens", labelKey: "th.cacheRead", value: data.cache_read_tokens, format: "tokens" },
|
||||
];
|
||||
}
|
||||
Reference in New Issue
Block a user