Add cost metrics to analytics dashboard
This commit is contained in:
@@ -101,7 +101,7 @@ export function Sidebar() {
|
||||
style={{ color: "var(--text-muted)", background: "var(--row-hover)", border: "1px solid var(--surface-border)" }}
|
||||
>
|
||||
<Languages className="h-3.5 w-3.5" />
|
||||
<span className="font-medium">{locale === "zh" ? "中文" : "English"}</span>
|
||||
<span className="font-medium" suppressHydrationWarning>{locale === "zh" ? "中文" : "English"}</span>
|
||||
</button>
|
||||
|
||||
{/* Status */}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "motion/react";
|
||||
import { formatNumber, formatTokens } from "@/lib/utils";
|
||||
import { formatNumber, formatTokens, formatUSD } from "@/lib/utils";
|
||||
import { type LucideIcon } from "lucide-react";
|
||||
|
||||
interface StatsCardProps {
|
||||
title: string;
|
||||
value: number;
|
||||
format?: "number" | "tokens";
|
||||
format?: "number" | "tokens" | "usd";
|
||||
icon: LucideIcon;
|
||||
delay?: number;
|
||||
}
|
||||
|
||||
export function StatsCard({ title, value, format = "number", icon: Icon, delay = 0 }: StatsCardProps) {
|
||||
const display = format === "tokens" ? formatTokens(value) : formatNumber(value);
|
||||
const display = format === "tokens" ? formatTokens(value) : format === "usd" ? formatUSD(value) : formatNumber(value);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
|
||||
@@ -1,24 +1,42 @@
|
||||
"use client";
|
||||
|
||||
import { ResponsiveContainer, LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend } from "recharts";
|
||||
import { formatTokens } from "@/lib/utils";
|
||||
import { formatTokens, formatUSD } from "@/lib/utils";
|
||||
import { useI18n } from "@/lib/i18n";
|
||||
|
||||
interface TrendPoint { date: string; calls: number; total_tokens: number; prompt_tokens: number; completion_tokens: number; }
|
||||
interface TrendPoint { date: string; calls: number; total_tokens: number; prompt_tokens: number; completion_tokens: number; quota?: number; }
|
||||
|
||||
export function TrendChart({ data, metric = "total_tokens" }: { data: TrendPoint[]; metric?: "total_tokens" | "calls" }) {
|
||||
export function TrendChart({ data, metric = "total_tokens" }: { data: TrendPoint[]; metric?: "total_tokens" | "calls" | "quota" }) {
|
||||
const { t, locale } = useI18n();
|
||||
|
||||
if (!data.length)
|
||||
return <div className="flex h-64 items-center justify-center" style={{ color: "var(--text-muted)" }}>{t("common.noData")}</div>;
|
||||
|
||||
// 本地化日期格式
|
||||
const parseTrendDate = (dateStr: string) => {
|
||||
const [datePart, timePart] = dateStr.replace("T", " ").split(" ");
|
||||
const [year, month, day] = datePart.split("-").map(Number);
|
||||
const hour = timePart ? timePart.slice(0, 5) : "";
|
||||
return { year, month, day, hour };
|
||||
};
|
||||
|
||||
const formatDateLabel = (dateStr: string) => {
|
||||
const d = new Date(dateStr);
|
||||
const { year, month, day, hour } = parseTrendDate(dateStr);
|
||||
const d = new Date(year, month - 1, day);
|
||||
if (locale === "zh") {
|
||||
return `${d.getMonth() + 1}/${d.getDate()}`;
|
||||
return hour ? `${month}/${day} ${hour}` : `${month}/${day}`;
|
||||
}
|
||||
return d.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
||||
const dateLabel = d.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
||||
return hour ? `${dateLabel}, ${hour}` : dateLabel;
|
||||
};
|
||||
|
||||
const formatTooltipLabel = (label: string) => {
|
||||
const { year, month, day, hour } = parseTrendDate(label);
|
||||
const d = new Date(year, month - 1, day);
|
||||
if (locale === "zh") {
|
||||
return hour ? `${year}/${month}/${day} ${hour}` : `${year}/${month}/${day}`;
|
||||
}
|
||||
const dateLabel = d.toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric" });
|
||||
return hour ? `${dateLabel}, ${hour}` : dateLabel;
|
||||
};
|
||||
|
||||
const tooltipStyle = {
|
||||
@@ -66,12 +84,7 @@ export function TrendChart({ data, metric = "total_tokens" }: { data: TrendPoint
|
||||
/>
|
||||
<Tooltip
|
||||
contentStyle={tooltipStyle}
|
||||
labelFormatter={(label) => {
|
||||
const d = new Date(String(label));
|
||||
return locale === "zh"
|
||||
? `${d.getFullYear()}/${d.getMonth() + 1}/${d.getDate()}`
|
||||
: d.toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric" });
|
||||
}}
|
||||
labelFormatter={(label) => formatTooltipLabel(String(label))}
|
||||
formatter={(value, name) => [
|
||||
formatTokens(Number(value)),
|
||||
name === t("th.input") ? t("th.input") : t("th.output"),
|
||||
@@ -85,6 +98,26 @@ export function TrendChart({ data, metric = "total_tokens" }: { data: TrendPoint
|
||||
);
|
||||
}
|
||||
|
||||
// 金额模式:单 Y 轴
|
||||
if (metric === "quota") {
|
||||
return (
|
||||
<ResponsiveContainer width="100%" height={320}>
|
||||
<LineChart data={data} margin={{ top: 5, right: 20, left: 10, bottom: 5 }}>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="var(--chart-grid)" />
|
||||
<XAxis dataKey="date" tickFormatter={formatDateLabel} tick={{ fontSize: 11 }} stroke="var(--chart-grid)" />
|
||||
<YAxis tickFormatter={(v) => formatUSD(v / 500000)} tick={{ fontSize: 11 }} stroke="var(--chart-grid)" />
|
||||
<Tooltip
|
||||
contentStyle={tooltipStyle}
|
||||
labelFormatter={(label) => formatTooltipLabel(String(label))}
|
||||
formatter={(value) => [formatUSD(Number(value) / 500000), t("th.cost")]}
|
||||
/>
|
||||
<Legend />
|
||||
<Line type="monotone" dataKey="quota" name={t("th.cost")} stroke="var(--accent)" strokeWidth={2} dot={false} />
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
);
|
||||
}
|
||||
|
||||
// 调用量模式:单 Y 轴
|
||||
return (
|
||||
<ResponsiveContainer width="100%" height={320}>
|
||||
@@ -94,12 +127,7 @@ export function TrendChart({ data, metric = "total_tokens" }: { data: TrendPoint
|
||||
<YAxis tickFormatter={(v) => formatTokens(v)} tick={{ fontSize: 11 }} stroke="var(--chart-grid)" />
|
||||
<Tooltip
|
||||
contentStyle={tooltipStyle}
|
||||
labelFormatter={(label) => {
|
||||
const d = new Date(String(label));
|
||||
return locale === "zh"
|
||||
? `${d.getFullYear()}/${d.getMonth() + 1}/${d.getDate()}`
|
||||
: d.toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric" });
|
||||
}}
|
||||
labelFormatter={(label) => formatTooltipLabel(String(label))}
|
||||
formatter={(value, name) => [
|
||||
formatTokens(Number(value)),
|
||||
name === t("th.calls") ? t("th.calls") : String(name),
|
||||
|
||||
Reference in New Issue
Block a user