Next.js full-stack analytics dashboard for new-api. - Direct PostgreSQL readonly queries on logs table - 5 pages: Dashboard, Rankings, Aggregation, Logs, Detail - Dark/Light/System theme with CSS variables - Chinese/English i18n (default Chinese) - Recharts with dual Y-axis for input/output tokens - Lucide icons + Motion animations - Docker + docker-compose with external sinobridge network, port 8019
69 lines
1.9 KiB
TypeScript
69 lines
1.9 KiB
TypeScript
"use client";
|
|
|
|
import {
|
|
ResponsiveContainer,
|
|
BarChart,
|
|
Bar,
|
|
XAxis,
|
|
YAxis,
|
|
CartesianGrid,
|
|
Tooltip,
|
|
Cell,
|
|
} from "recharts";
|
|
import { formatTokens } from "@/lib/utils";
|
|
|
|
interface RankItem {
|
|
name: string;
|
|
total_tokens: number;
|
|
calls: number;
|
|
}
|
|
|
|
const BAR_COLORS = [
|
|
"#00e5ff", "#00bcd4", "#0097a7", "#7c4dff",
|
|
"#651fff", "#536dfe", "#448aff", "#40c4ff",
|
|
"#18ffff", "#84ffff",
|
|
];
|
|
|
|
export function RankingBar({
|
|
data,
|
|
title,
|
|
}: {
|
|
data: RankItem[];
|
|
title: string;
|
|
}) {
|
|
if (!data.length) return null;
|
|
const sliced = data.slice(0, 10);
|
|
|
|
return (
|
|
<div>
|
|
<h3 className="mb-3 text-xs font-medium uppercase tracking-widest text-gray-500">{title}</h3>
|
|
<ResponsiveContainer width="100%" height={300}>
|
|
<BarChart
|
|
data={sliced}
|
|
layout="vertical"
|
|
margin={{ top: 0, right: 20, left: 0, bottom: 0 }}
|
|
>
|
|
<CartesianGrid strokeDasharray="3 3" stroke="rgba(0,229,255,0.06)" horizontal={false} />
|
|
<XAxis type="number" tickFormatter={(v) => formatTokens(v)} tick={{ fontSize: 10, fill: "rgba(200,214,229,0.4)" }} stroke="rgba(0,229,255,0.1)" />
|
|
<YAxis type="category" dataKey="name" width={100} tick={{ fontSize: 11, fill: "rgba(200,214,229,0.6)" }} stroke="transparent" />
|
|
<Tooltip
|
|
contentStyle={{
|
|
background: "rgba(6,8,13,0.95)",
|
|
border: "1px solid rgba(0,229,255,0.2)",
|
|
borderRadius: "8px",
|
|
color: "#c8d6e5",
|
|
fontSize: "12px",
|
|
}}
|
|
formatter={(v) => [formatTokens(Number(v)), "Total Tokens"]}
|
|
/>
|
|
<Bar dataKey="total_tokens" radius={[0, 4, 4, 0]}>
|
|
{sliced.map((_, i) => (
|
|
<Cell key={i} fill={BAR_COLORS[i % BAR_COLORS.length]} fillOpacity={0.7} />
|
|
))}
|
|
</Bar>
|
|
</BarChart>
|
|
</ResponsiveContainer>
|
|
</div>
|
|
);
|
|
}
|