-
-
+
);
}
diff --git a/src/components/usage/UsageSummaryCards.tsx b/src/components/usage/UsageSummaryCards.tsx
index 5256eadd4..01b6dd7bd 100644
--- a/src/components/usage/UsageSummaryCards.tsx
+++ b/src/components/usage/UsageSummaryCards.tsx
@@ -1,7 +1,9 @@
import { useMemo } from "react";
import { useTranslation } from "react-i18next";
-import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { Card, CardContent } from "@/components/ui/card";
import { useUsageSummary } from "@/lib/query/usage";
+import { Activity, DollarSign, Layers, Database, Loader2 } from "lucide-react";
+import { motion } from "framer-motion";
interface UsageSummaryCardsProps {
days: number;
@@ -10,7 +12,6 @@ interface UsageSummaryCardsProps {
export function UsageSummaryCards({ days }: UsageSummaryCardsProps) {
const { t } = useTranslation();
- // 使用 useMemo 稳定时间戳,只在 days 变化时重新计算
const { startDate, endDate } = useMemo(() => {
const end = Math.floor(Date.now() / 1000);
const start = end - days * 24 * 60 * 60;
@@ -18,25 +19,110 @@ export function UsageSummaryCards({ days }: UsageSummaryCardsProps) {
}, [days]);
const { data: summary, isLoading } = useUsageSummary(startDate, endDate);
- const totalRequests = summary?.totalRequests ?? 0;
- const totalCost = parseFloat(summary?.totalCost || "0").toFixed(4);
- const totalInputTokens = summary?.totalInputTokens ?? 0;
- const totalOutputTokens = summary?.totalOutputTokens ?? 0;
- const totalTokens = totalInputTokens + totalOutputTokens;
- const cacheWriteTokens = summary?.totalCacheCreationTokens ?? 0;
- const cacheReadTokens = summary?.totalCacheReadTokens ?? 0;
- const totalCacheTokens = cacheWriteTokens + cacheReadTokens;
+
+ const stats = useMemo(() => {
+ const totalRequests = summary?.totalRequests ?? 0;
+ const totalCost = parseFloat(summary?.totalCost || "0");
+
+ const inputTokens = summary?.totalInputTokens ?? 0;
+ const outputTokens = summary?.totalOutputTokens ?? 0;
+ const totalTokens = inputTokens + outputTokens;
+
+ const cacheWriteTokens = summary?.totalCacheCreationTokens ?? 0;
+ const cacheReadTokens = summary?.totalCacheReadTokens ?? 0;
+ const totalCacheTokens = cacheWriteTokens + cacheReadTokens;
+
+ return [
+ {
+ title: t("usage.totalRequests", "总请求数"),
+ value: totalRequests.toLocaleString(),
+ icon: Activity,
+ color: "text-blue-500",
+ bg: "bg-blue-500/10",
+ subValue: null,
+ },
+ {
+ title: t("usage.totalCost", "总成本"),
+ value: `$${totalCost.toFixed(4)}`,
+ icon: DollarSign,
+ color: "text-green-500",
+ bg: "bg-green-500/10",
+ subValue: null,
+ },
+ {
+ title: t("usage.totalTokens", "总 Token 数"),
+ value: totalTokens.toLocaleString(),
+ icon: Layers,
+ color: "text-purple-500",
+ bg: "bg-purple-500/10",
+ subValue: (
+
+
+ Input
+
+ {(inputTokens / 1000).toFixed(1)}k
+
+
+
+ Output
+
+ {(outputTokens / 1000).toFixed(1)}k
+
+
+
+ ),
+ },
+ {
+ title: t("usage.cacheTokens", "缓存 Token"),
+ value: totalCacheTokens.toLocaleString(),
+ icon: Database,
+ color: "text-orange-500",
+ bg: "bg-orange-500/10",
+ subValue: (
+
+
+ Write
+
+ {(cacheWriteTokens / 1000).toFixed(1)}k
+
+
+
+ Read
+
+ {(cacheReadTokens / 1000).toFixed(1)}k
+
+
+
+ ),
+ },
+ ];
+ }, [summary, t]);
+
+ const container = {
+ hidden: { opacity: 0 },
+ show: {
+ opacity: 1,
+ transition: {
+ staggerChildren: 0.1,
+ },
+ },
+ };
+
+ const item = {
+ hidden: { opacity: 0, y: 20 },
+ show: { opacity: 1, y: 0 },
+ };
if (isLoading) {
return (
{[...Array(4)].map((_, i) => (
-
-
-
-
-
-
+
+
+
))}
@@ -45,75 +131,39 @@ export function UsageSummaryCards({ days }: UsageSummaryCardsProps) {
}
return (
-
-
-
-
- {t("usage.totalRequests", "总请求数")}
-
-
-
-
- {totalRequests.toLocaleString()}
-
-
-
+
+ {stats.map((stat, i) => (
+
+
+
+
+
+ {stat.title}
+
+
+
+
+
-
-
-
- {t("usage.totalCost", "总成本")}
-
-
-
- ${totalCost}
-
-
+
+
+ {stat.value}
+
+
-
-
-
- {t("usage.totalTokens", "总 Token 数")}
-
-
-
-
- {totalTokens.toLocaleString()}
-
-
-
- {t("usage.inputTokens", "输入")}:{" "}
- {totalInputTokens.toLocaleString()}
-
-
- {t("usage.outputTokens", "输出")}:{" "}
- {totalOutputTokens.toLocaleString()}
-
-
-
-
-
-
-
-
- {t("usage.cacheTokens", "缓存 Token")}
-
-
-
-
- {totalCacheTokens.toLocaleString()}
-
-
-
- {t("usage.cacheWrite", "写入")}:{" "}
- {cacheWriteTokens.toLocaleString()}
-
-
- {t("usage.cacheRead", "读取")}: {cacheReadTokens.toLocaleString()}
-
-
-
-
-
+ {stat.subValue || (
+ /* Placeholder to properly align cards if no subvalue (first 2 cards) - effectively adding empty space or using flex-1 equivalent */
+
+ )}
+
+
+
+ ))}
+
);
}
diff --git a/src/components/usage/UsageTrendChart.tsx b/src/components/usage/UsageTrendChart.tsx
index 2fa2f8363..7d82a6282 100644
--- a/src/components/usage/UsageTrendChart.tsx
+++ b/src/components/usage/UsageTrendChart.tsx
@@ -1,7 +1,7 @@
import { useTranslation } from "react-i18next";
import {
- LineChart,
- Line,
+ AreaChart,
+ Area,
XAxis,
YAxis,
CartesianGrid,
@@ -10,6 +10,7 @@ import {
Legend,
} from "recharts";
import { useUsageTrends } from "@/lib/query/usage";
+import { Loader2 } from "lucide-react";
interface UsageTrendChartProps {
days: number;
@@ -20,7 +21,11 @@ export function UsageTrendChart({ days }: UsageTrendChartProps) {
const { data: trends, isLoading } = useUsageTrends(days);
if (isLoading) {
- return
;
+ return (
+
+
+
+ );
}
const isToday = days === 1;
@@ -38,8 +43,6 @@ export function UsageTrendChart({ days }: UsageTrendChartProps) {
hour: pointDate.getHours(),
inputTokens: stat.totalInputTokens,
outputTokens: stat.totalOutputTokens,
- cacheCreationTokens: stat.totalCacheCreationTokens,
- cacheReadTokens: stat.totalCacheReadTokens,
cost: parseFloat(stat.totalCost),
};
}) || [];
@@ -56,8 +59,6 @@ export function UsageTrendChart({ days }: UsageTrendChartProps) {
label: `${hour.toString().padStart(2, "0")}:00`,
inputTokens: bucket?.inputTokens ?? 0,
outputTokens: bucket?.outputTokens ?? 0,
- cacheCreationTokens: bucket?.cacheCreationTokens ?? 0,
- cacheReadTokens: bucket?.cacheReadTokens ?? 0,
cost: bucket?.cost ?? 0,
};
});
@@ -65,96 +66,129 @@ export function UsageTrendChart({ days }: UsageTrendChartProps) {
const displayData = isToday ? hourlyData : chartData;
- const rangeLabel = isToday
- ? t("usage.rangeToday", "今天 (按小时)")
- : days === 7
- ? t("usage.rangeLast7Days", "过去 7 天")
- : t("usage.rangeLast30Days", "过去 30 天");
+ const CustomTooltip = ({ active, payload, label }: any) => {
+ if (active && payload && payload.length) {
+ return (
+
+
{label}
+ {payload.map((entry: any, index: number) => (
+
+
+
{entry.name}:
+
+ {entry.name.includes(t("usage.cost", "成本"))
+ ? `$${typeof entry.value === "number" ? entry.value.toFixed(6) : entry.value}`
+ : entry.value.toLocaleString()}
+
+
+ ))}
+
+ );
+ }
+ return null;
+ };
return (
-
-
+
+
{t("usage.trends", "使用趋势")}
-
{rangeLabel}
+
+ {isToday
+ ? t("usage.rangeToday", "今天 (按小时)")
+ : days === 7
+ ? t("usage.rangeLast7Days", "过去 7 天")
+ : t("usage.rangeLast30Days", "过去 30 天")}
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `${(value / 1000).toFixed(0)}k`}
+ />
+ `$${value}`}
+ />
+ } />
+
+
+
+
+
+
+
);
}