import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { createRoot } from "react-dom/client";
import {
BarChart3,
CalendarDays,
ChevronDown,
Check,
Info,
Layers3,
RefreshCcw,
Sparkles,
TrendingUp
} from "lucide-react";
import { sampleSummary } from "./sampleData";
import { formatTokensZh } from "./displayFormat";
import "./styles.css";
const providers = ["Claude", "Codex", "Gemini"];
const providerColors = {
Claude: "#2e83ff",
Codex: "#9b5cf6",
Gemini: "#2dd4cf"
};
function App() {
const [summary, setSummary] = useState(sampleSummary);
const [status, setStatus] = useState("loading");
const [lastUpdated, setLastUpdated] = useState("");
const [refreshCount, setRefreshCount] = useState(0);
const [range, setRange] = useState("近 30 天");
const [trendMode, setTrendMode] = useState("每日");
const [shareMode, setShareMode] = useState("按 Tokens");
const [toolFilter, setToolFilter] = useState("全部工具");
const scanningRef = useRef(false);
const refresh = useCallback(async ({ silent = false } = {}) => {
if (scanningRef.current) return;
scanningRef.current = true;
if (!silent) setStatus("loading");
try {
if (window.tokenLens?.scanUsage) {
const data = await window.tokenLens.scanUsage();
setSummary(data);
setStatus("ready");
setLastUpdated(new Date(data.generatedAt).toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit" }));
setRefreshCount((count) => count + 1);
} else {
setSummary(sampleSummary);
setStatus("preview");
setLastUpdated("预览数据");
}
} catch (error) {
setStatus("error");
setLastUpdated("扫描失败");
console.error(error);
} finally {
scanningRef.current = false;
}
}, []);
useEffect(() => {
refresh();
const timer = window.setInterval(() => {
refresh({ silent: true });
}, 15_000);
return () => window.clearInterval(timer);
}, [refresh]);
const totalByProvider = useMemo(() => providers.reduce((sum, provider) => {
return sum + (summary.providerTotals?.[provider]?.totalTokens || 0);
}, 0), [summary]);
const sourceRows = summary.sourceRows?.length ? summary.sourceRows : sampleSummary.sourceRows;
const filteredRows = toolFilter === "全部工具" ? sourceRows : sourceRows.filter((row) => row.provider === toolFilter);
const insights = summary.insights?.length ? summary.insights.slice(0, 3) : sampleSummary.insights;
return (
refresh()}
status={status}
lastUpdated={lastUpdated}
refreshCount={refreshCount}
range={range}
setRange={setRange}
/>
);
}
function TopBar({ onRefresh, status, lastUpdated, refreshCount, range, setRange }) {
return (
TokenLens 用量统计
{status === "error" ? "扫描异常" : "每 15 秒自动刷新"}
}
className="toolbar-dropdown"
/>
{lastUpdated ? `上次更新 ${lastUpdated}` : "准备扫描"}
已刷新 {refreshCount} 次
);
}
function MetricCards({ summary }) {
const cards = [
{
label: "今日 Tokens",
value: formatTokensZh(summary.cards?.todayTokens || 0),
detail: "本地今日已统计",
trend: "实时",
tone: "blue",
icon: Layers3
},
{
label: "本月 Tokens",
value: formatTokensZh(summary.cards?.monthTokens || 0),
detail: "按本地时区汇总",
trend: "月度",
tone: "green",
icon: Sparkles
},
{
label: "历史总量",
value: formatTokensZh(summary.cards?.totalTokens || 0),
detail: `已扫描 ${summary.cards?.sessionCount || 0} 个会话`,
trend: "全部",
tone: "violet",
icon: Check
},
{
label: "缓存命中",
value: `${summary.cards?.cacheHitRate || 0}%`,
detail: "来自缓存上下文",
trend: "缓存",
tone: "orange",
icon: TrendingUp
}
];
return (
{cards.map((card, index) => {
const Icon = card.icon;
return (
{card.label}
{card.value}
{card.trend}
{card.detail}
);
})}
);
}
function Panel({ title, control, children }) {
return (
{title}
{control}
{children}
);
}
function Dropdown({ value, options, onChange, icon, className = "" }) {
const [open, setOpen] = useState(false);
const ref = useRef(null);
useEffect(() => {
function close(event) {
if (!ref.current?.contains(event.target)) setOpen(false);
}
document.addEventListener("pointerdown", close);
return () => document.removeEventListener("pointerdown", close);
}, []);
return (
{open ? (
{options.map((option) => (
))}
) : null}
);
}
function TrendChart({ data }) {
const chartData = data.length ? data.slice(-24) : sampleSummary.dailyTrend;
const width = 640;
const height = 235;
const left = 58;
const right = 18;
const top = 18;
const bottom = 34;
const maxValue = Math.max(1, ...chartData.flatMap((item) => providers.map((provider) => item[provider] || 0)));
const plotWidth = width - left - right;
const plotHeight = height - top - bottom;
const x = (index) => left + (index / Math.max(chartData.length - 1, 1)) * plotWidth;
const y = (value) => top + plotHeight - (value / maxValue) * plotHeight;
const lines = providers.map((provider) => ({
provider,
points: chartData.map((item, index) => `${x(index)},${y(item[provider] || 0)}`).join(" ")
}));
const labelIndexes = [0, 6, 12, 18, chartData.length - 1].filter((index, pos, arr) => index >= 0 && arr.indexOf(index) === pos);
return (
<>
>
);
}
function UsageShare({ providerTotals, total }) {
const safeTotal = total || 1;
let cursor = 0;
const gradient = providers.map((provider) => {
const share = ((providerTotals[provider]?.totalTokens || 0) / safeTotal) * 360;
const start = cursor;
cursor += share;
return `${providerColors[provider]} ${start}deg ${cursor}deg`;
}).join(", ");
return (
{formatTokensZh(total)}
本月
{providers.map((provider) => {
const providerTotal = providerTotals[provider]?.totalTokens || 0;
const percent = total ? Math.round((providerTotal / total) * 100) : 0;
return (
{provider}
{formatTokensZh(providerTotal)} Tokens
{percent}%
);
})}
);
}
function ProviderBreakdown({ rows, toolFilter, setToolFilter }) {
return (
工具明细
| 工具 |
输入 |
输出 |
缓存 |
总量 |
趋势 |
|
{rows.map((row, index) => (
|
{formatTokensZh(row.inputTokens)} |
{formatTokensZh(row.outputTokens)} |
{formatTokensZh(row.cachedTokens)} |
{formatTokensZh(row.totalTokens)} |
|
⋮ |
))}
显示 {rows.length} 个本地工具 · 不上传对话内容
);
}
function ProviderName({ provider }) {
return (
{provider === "Gemini" ? "" : provider[0]}
{provider}
);
}
function Sparkline({ provider, seed }) {
const base = [
[17, 12, 16, 8, 12, 6, 10, 4, 8, 5, 9],
[18, 11, 15, 17, 10, 13, 7, 12, 6, 8, 4],
[16, 16, 10, 18, 14, 9, 12, 6, 5, 8, 8]
][seed % 3];
const points = base.map((value, index) => `${index * 7},${value}`).join(" ");
return (
);
}
function Insights({ insights }) {
return (
洞察提醒
{insights.map((insight, index) => (
{insight.tone === "good" ? "✓" : insight.tone === "warn" ? "!" : "↗"}
{index + 1} 小时
{insight.title}
{insight.copy}
))}
);
}
function Legend() {
return (
{providers.map((provider) => (
{provider}
))}
);
}
function shortDate(value) {
if (!value) return "";
const [, month, day] = value.split("-");
return `${month}/${day}`;
}
createRoot(document.getElementById("root")).render();