feat: harden analytics dashboard

This commit is contained in:
2026-05-27 15:19:31 +08:00
parent 5e0ca6a504
commit 356039d9cf
34 changed files with 1424 additions and 879 deletions

View File

@@ -20,7 +20,7 @@ export function ClientProviders({ children }: { children: ReactNode }) {
) : (
<>
<Sidebar />
<main className="ml-[220px] min-h-screen p-6 lg:p-8">{children}</main>
<main className="min-h-screen px-4 pb-6 pt-24 sm:px-6 lg:ml-[220px] lg:p-8">{children}</main>
</>
)}
</TimeRangeProvider>

View File

@@ -26,9 +26,9 @@ export function Sidebar() {
];
return (
<aside className="fixed left-0 top-0 z-30 flex h-screen w-[220px] flex-col glass !rounded-none !border-l-0 !border-t-0 !border-b-0">
<aside className="fixed left-0 top-0 z-30 flex h-20 w-full flex-row glass !rounded-none !border-l-0 !border-t-0 lg:h-screen lg:w-[220px] lg:flex-col lg:!border-b-0">
{/* Logo */}
<div className="flex h-16 items-center gap-3 border-b border-t px-5" style={{ borderColor: "var(--surface-border)" }}>
<div className="flex h-full shrink-0 items-center gap-3 border-r px-4 lg:h-16 lg:border-b lg:border-r-0 lg:border-t lg:px-5" style={{ borderColor: "var(--surface-border)" }}>
<div className="flex h-8 w-8 items-center justify-center rounded-lg border" style={{ borderColor: "var(--surface-border)", background: "var(--btn-active-bg)" }}>
<Activity className="h-4 w-4 text-t-accent" style={{ color: "var(--accent)" }} />
</div>
@@ -38,14 +38,14 @@ export function Sidebar() {
</div>
</div>
<nav className="flex-1 space-y-1 p-3 pt-4">
<nav className="flex flex-1 items-center gap-1 overflow-x-auto px-2 lg:block lg:space-y-1 lg:p-3 lg:pt-4">
{nav.map((item) => {
const active = item.href === "/" ? pathname === "/" : pathname.startsWith(item.href);
const Icon = item.icon;
return (
<Link key={item.href} href={item.href}>
<motion.div
className="relative flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm transition-colors"
className="relative flex min-w-max items-center gap-2 rounded-lg px-3 py-2 text-sm transition-colors lg:gap-3 lg:py-2.5"
style={{ color: active ? "var(--text-accent)" : "var(--text-muted)" }}
whileHover={{ x: 2 }}
transition={{ type: "spring", stiffness: 400, damping: 30 }}
@@ -59,10 +59,10 @@ export function Sidebar() {
/>
)}
<Icon className="relative z-10 h-4 w-4" />
<span className="relative z-10 font-medium">{item.label}</span>
<span className="relative z-10 hidden font-medium sm:inline">{item.label}</span>
{active && (
<motion.div
className="absolute left-0 top-1/2 h-5 w-[2px] -translate-y-1/2 rounded-full"
className="absolute bottom-0 left-1/2 h-[2px] w-5 -translate-x-1/2 rounded-full lg:left-0 lg:top-1/2 lg:h-5 lg:w-[2px] lg:-translate-x-0 lg:-translate-y-1/2"
style={{ background: "var(--accent)" }}
layoutId="sidebar-indicator"
transition={{ type: "spring", stiffness: 350, damping: 30 }}
@@ -75,7 +75,7 @@ export function Sidebar() {
</nav>
{/* Controls */}
<div className="space-y-3 p-4 border-t" style={{ borderColor: "var(--surface-border)" }}>
<div className="hidden space-y-3 border-t p-4 lg:block" style={{ borderColor: "var(--surface-border)" }}>
{!isEmbedded && (
<div className="flex gap-1 rounded-lg p-0.5" style={{ background: "var(--row-hover)", border: "1px solid var(--surface-border)" }}>
{themes.map(({ value, icon: Icon }) => (

View File

@@ -11,6 +11,7 @@ import {
Cell,
} from "recharts";
import { formatTokens } from "@/lib/utils";
import { useI18n } from "@/lib/i18n";
interface RankItem {
name: string;
@@ -31,6 +32,7 @@ export function RankingBar({
data: RankItem[];
title: string;
}) {
const { t } = useI18n();
if (!data.length) return null;
const sliced = data.slice(0, 10);
@@ -54,7 +56,7 @@ export function RankingBar({
color: "#c8d6e5",
fontSize: "12px",
}}
formatter={(v) => [formatTokens(Number(v)), "Total Tokens"]}
formatter={(v) => [formatTokens(Number(v)), t("th.totalToken")]}
/>
<Bar dataKey="total_tokens" radius={[0, 4, 4, 0]}>
{sliced.map((_, i) => (

View File

@@ -2,6 +2,7 @@
import { ResponsiveContainer, LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend } from "recharts";
import { formatTokens, formatUSD } from "@/lib/utils";
import { quotaToUsd } from "@/lib/metrics";
import { useI18n } from "@/lib/i18n";
interface TrendPoint { date: string; calls: number; total_tokens: number; prompt_tokens: number; completion_tokens: number; quota?: number; }
@@ -105,11 +106,11 @@ export function TrendChart({ data, metric = "total_tokens" }: { data: TrendPoint
<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)" />
<YAxis tickFormatter={(v) => formatUSD(quotaToUsd(v))} tick={{ fontSize: 11 }} stroke="var(--chart-grid)" />
<Tooltip
contentStyle={tooltipStyle}
labelFormatter={(label) => formatTooltipLabel(String(label))}
formatter={(value) => [formatUSD(Number(value) / 500000), t("th.cost")]}
formatter={(value) => [formatUSD(quotaToUsd(Number(value))), t("th.cost")]}
/>
<Legend />
<Line type="monotone" dataKey="quota" name={t("th.cost")} stroke="var(--accent)" strokeWidth={2} dot={false} />