Skip to content

Commit a3165f8

Browse files
committed
chore: update recharts to version 3.6.0 and adjust dependencies
- Updated recharts from version 2.12.5 to 3.6.0 in package.json and pnpm-lock.yaml. - Adjusted peer dependencies for recharts to support react versions 16.8.0 through 19.0.0. - Updated related dependencies including redux and react-redux to their latest versions. - Removed unused dependencies and updated integrity checks in pnpm-lock.yaml.
1 parent 2bac9b7 commit a3165f8

3 files changed

Lines changed: 285 additions & 114 deletions

File tree

app/page.tsx

Lines changed: 153 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { useEffect, useState, useCallback, useMemo, useRef, type FormEvent } from "react";
44
import { ResponsiveContainer, LineChart, Line, CartesianGrid, XAxis, YAxis, Tooltip, BarChart, Bar, Legend, ComposedChart, PieChart, Pie, Cell } from "recharts";
5+
import type { TooltipProps } from "recharts";
56
import { formatCurrency, formatNumber, formatCompactNumber, formatNumberWithCommas, formatHourLabel } from "@/lib/utils";
67
import { AlertTriangle, Info, LucideIcon, Activity, Save, RefreshCw, Moon, Sun, Pencil, Trash2, X, Maximize2 } from "lucide-react";
78
import type { ModelPrice, UsageOverview, UsageSeriesPoint } from "@/lib/types";
@@ -29,6 +30,24 @@ const hourFormatter = new Intl.DateTimeFormat("en-CA", {
2930

3031
const HOUR_MS = 60 * 60 * 1000;
3132

33+
type TooltipValue = number | string | Array<number | string> | undefined;
34+
35+
function normalizeTooltipValue(value: TooltipValue) {
36+
if (Array.isArray(value)) return normalizeTooltipValue(value[0]);
37+
const numeric = typeof value === "number" ? value : Number(value ?? 0);
38+
return Number.isFinite(numeric) ? numeric : 0;
39+
}
40+
41+
const trendTooltipFormatter: TooltipProps<number, string>["formatter"] = (value, name) => {
42+
const numericValue = normalizeTooltipValue(value);
43+
return name === "费用" ? [formatCurrency(numericValue), name] : [formatNumberWithCommas(numericValue), name];
44+
};
45+
46+
const numericTooltipFormatter: TooltipProps<number, string>["formatter"] = (value, name) => {
47+
const numericValue = normalizeTooltipValue(value);
48+
return [formatNumberWithCommas(numericValue), name];
49+
};
50+
3251
function formatHourKeyFromTs(ts: number) {
3352
const parts = hourFormatter.formatToParts(new Date(ts));
3453
const month = parts.find((p) => p.type === "month")?.value ?? "00";
@@ -135,13 +154,70 @@ export default function DashboardPage() {
135154
};
136155

137156
const handleHourlyLegendClick = (e: any) => {
138-
const { dataKey } = e;
157+
const key = e.dataKey ?? e.payload?.dataKey ?? e.id;
158+
if (!key) return;
139159
setHourlyVisible((prev) => ({
140160
...prev,
141-
[dataKey]: !prev[dataKey as string],
161+
[key]: !prev[key as string],
142162
}));
143163
};
144164

165+
const TrendLegend: any = Legend;
166+
167+
const trendConfig = useMemo(() => {
168+
const defs = {
169+
requests: { color: "#3b82f6", formatter: (v: any) => formatCompactNumber(v), name: "请求数" },
170+
tokens: { color: "#10b981", formatter: (v: any) => formatCompactNumber(v), name: "Tokens" },
171+
cost: { color: "#fbbf24", formatter: (v: any) => formatCurrency(v), name: "费用" },
172+
};
173+
174+
const visibleKeys = (Object.keys(trendVisible) as Array<keyof typeof trendVisible>).filter((k) => trendVisible[k]);
175+
176+
// Default mapping
177+
let lineAxisMap: Record<string, string> = {
178+
requests: "left",
179+
tokens: "right",
180+
cost: "cost",
181+
};
182+
183+
let leftAxisKey = "requests";
184+
let rightAxisKey = "tokens";
185+
let rightAxisVisible = true;
186+
187+
if (visibleKeys.length === 2) {
188+
if (!trendVisible.requests) {
189+
// requests hidden -> tokens (left), cost (right)
190+
lineAxisMap = { requests: "left", tokens: "left", cost: "right" };
191+
leftAxisKey = "tokens";
192+
rightAxisKey = "cost";
193+
} else if (!trendVisible.tokens) {
194+
// tokens hidden -> requests (left), cost (right)
195+
lineAxisMap = { requests: "left", tokens: "right", cost: "right" };
196+
leftAxisKey = "requests";
197+
rightAxisKey = "cost";
198+
} else {
199+
// cost hidden -> requests (left), tokens (right)
200+
lineAxisMap = { requests: "left", tokens: "right", cost: "cost" };
201+
leftAxisKey = "requests";
202+
rightAxisKey = "tokens";
203+
}
204+
} else if (visibleKeys.length === 1) {
205+
const key = visibleKeys[0];
206+
lineAxisMap = { requests: "left", tokens: "left", cost: "left" };
207+
leftAxisKey = key;
208+
rightAxisVisible = false;
209+
} else if (visibleKeys.length === 0) {
210+
rightAxisVisible = false;
211+
}
212+
213+
return {
214+
lineAxisMap,
215+
leftAxis: defs[leftAxisKey as keyof typeof defs],
216+
rightAxis: defs[rightAxisKey as keyof typeof defs],
217+
rightAxisVisible
218+
};
219+
}, [trendVisible]);
220+
145221
const cancelPieLegendClear = useCallback(() => {
146222
if (pieLegendClearTimerRef.current !== null) {
147223
window.clearTimeout(pieLegendClearTimerRef.current);
@@ -717,13 +793,19 @@ export default function DashboardPage() {
717793
<LineChart data={overviewData.byDay} margin={{ top: 0, right: 12, left: 0, bottom: 0 }}>
718794
<CartesianGrid stroke="#334155" strokeDasharray="5 5" />
719795
<XAxis dataKey="label" stroke="#94a3b8" fontSize={12} />
720-
<YAxis yAxisId="left" stroke="#3b82f6" tickFormatter={(v) => formatCompactNumber(v)} fontSize={12} />
796+
<YAxis
797+
yAxisId="left"
798+
stroke={trendConfig.leftAxis.color}
799+
tickFormatter={trendConfig.leftAxis.formatter}
800+
fontSize={12}
801+
/>
721802
<YAxis
722803
yAxisId="right"
723804
orientation="right"
724-
stroke="#10b981"
725-
tickFormatter={(v) => formatCompactNumber(v)}
805+
stroke={trendConfig.rightAxis.color}
806+
tickFormatter={trendConfig.rightAxis.formatter}
726807
fontSize={12}
808+
hide={!trendConfig.rightAxisVisible}
727809
/>
728810
<YAxis
729811
yAxisId="cost"
@@ -732,20 +814,22 @@ export default function DashboardPage() {
732814
tickFormatter={(v) => formatCurrency(v)}
733815
fontSize={12}
734816
hide
817+
width={0}
735818
/>
736819
<Tooltip
737820
contentStyle={{ borderRadius: 12, backgroundColor: "rgba(0,0,0,0.8)", border: "1px solid rgba(100,116,139,0.6)", color: "#f8fafc" }}
738-
formatter={(value: number, name: string) => name === "费用" ? [formatCurrency(value), name] : [formatNumberWithCommas(value), name]}
821+
formatter={trendTooltipFormatter}
739822
/>
740-
<Legend
823+
<TrendLegend
741824
height={24}
742825
iconSize={10}
743826
wrapperStyle={{ paddingTop: 0, paddingBottom: 0, lineHeight: "24px", cursor: "pointer" }}
744827
onClick={handleTrendLegendClick}
828+
itemSorter={(item: any) => ({ requests: 0, tokens: 1, cost: 2 } as Record<string, number>)[item?.dataKey] ?? 999}
745829
/>
746-
<Line hide={!trendVisible.requests} yAxisId="left" type="monotone" dataKey="requests" stroke="#3b82f6" strokeWidth={2} name="请求数" dot={{ r: 3 }} />
747-
<Line hide={!trendVisible.tokens} yAxisId="right" type="monotone" dataKey="tokens" stroke="#10b981" strokeWidth={2} name="Tokens" dot={{ r: 3 }} />
748-
<Line hide={!trendVisible.cost} yAxisId="cost" type="monotone" dataKey="cost" stroke="#fbbf24" strokeWidth={2} name="费用" dot={{ r: 3 }} />
830+
<Line hide={!trendVisible.requests} yAxisId={trendConfig.lineAxisMap.requests} type="monotone" dataKey="requests" stroke="#3b82f6" strokeWidth={2} name="请求数" dot={{ r: 3 }} />
831+
<Line hide={!trendVisible.tokens} yAxisId={trendConfig.lineAxisMap.tokens} type="monotone" dataKey="tokens" stroke="#10b981" strokeWidth={2} name="Tokens" dot={{ r: 3 }} />
832+
<Line hide={!trendVisible.cost} yAxisId={trendConfig.lineAxisMap.cost} type="monotone" dataKey="cost" stroke="#fbbf24" strokeWidth={2} name="费用" dot={{ r: 3 }} />
749833
</LineChart>
750834
</ResponsiveContainer>
751835
)}
@@ -962,18 +1046,26 @@ export default function DashboardPage() {
9621046
<YAxis yAxisId="right" orientation="right" stroke={darkMode ? "#94a3b8" : "#64748b"} tickFormatter={(v) => formatCompactNumber(v)} fontSize={12} />
9631047
<Tooltip
9641048
contentStyle={{ borderRadius: 12, backgroundColor: "rgba(0,0,0,0.8)", border: "1px solid rgba(100,116,139,0.6)", color: "#f8fafc" }}
965-
formatter={(value: number, name: string) => [formatNumberWithCommas(value), name]}
1049+
formatter={numericTooltipFormatter}
9661050
labelFormatter={(label) => formatHourLabel(label)}
9671051
/>
968-
<Legend
1052+
<TrendLegend
9691053
wrapperStyle={{ cursor: "pointer" }}
9701054
onClick={handleHourlyLegendClick}
1055+
itemSorter={(item: any) => ({ requests: 0, inputTokens: 1, outputTokens: 2, reasoningTokens: 3, cachedTokens: 4 } as Record<string, number>)[item?.dataKey] ?? 999}
1056+
payload={[
1057+
{ value: "请求数", type: "line", id: "requests", color: "#3b82f6", dataKey: "requests" },
1058+
{ value: "输入", type: "square", id: "inputTokens", color: "#60a5fa", dataKey: "inputTokens" },
1059+
{ value: "输出", type: "square", id: "outputTokens", color: "#4ade80", dataKey: "outputTokens" },
1060+
{ value: "思考", type: "square", id: "reasoningTokens", color: "#fbbf24", dataKey: "reasoningTokens" },
1061+
{ value: "缓存", type: "square", id: "cachedTokens", color: "#c084fc", dataKey: "cachedTokens" },
1062+
]}
9711063
/>
972-
{/* 堆积柱状图 - 柔和配色,仅顶部圆角 */}
973-
<Bar hide={!hourlyVisible.inputTokens} yAxisId="right" dataKey="inputTokens" name="输入" stackId="tokens" fill="#60a5fa" />
974-
<Bar hide={!hourlyVisible.outputTokens} yAxisId="right" dataKey="outputTokens" name="输出" stackId="tokens" fill="#4ade80" />
975-
<Bar hide={!hourlyVisible.reasoningTokens} yAxisId="right" dataKey="reasoningTokens" name="思考" stackId="tokens" fill="#fbbf24" />
976-
<Bar hide={!hourlyVisible.cachedTokens} yAxisId="right" dataKey="cachedTokens" name="缓存" stackId="tokens" fill="#c084fc" radius={[4, 4, 0, 0]} />
1064+
{/* 堆积柱状图 - 柔和配色,仅顶部圆角,增强动画 */}
1065+
<Bar hide={!hourlyVisible.inputTokens} yAxisId="right" dataKey="inputTokens" name="输入" stackId="tokens" fill="#60a5fa" animationDuration={600} />
1066+
<Bar hide={!hourlyVisible.outputTokens} yAxisId="right" dataKey="outputTokens" name="输出" stackId="tokens" fill="#4ade80" animationDuration={600} />
1067+
<Bar hide={!hourlyVisible.reasoningTokens} yAxisId="right" dataKey="reasoningTokens" name="思考" stackId="tokens" fill="#fbbf24" animationDuration={600} />
1068+
<Bar hide={!hourlyVisible.cachedTokens} yAxisId="right" dataKey="cachedTokens" name="缓存" stackId="tokens" fill="#c084fc" radius={[4, 4, 0, 0]} animationDuration={600} />
9771069
{/* 曲线在最上层 - 带描边突出显示 */}
9781070
<Line
9791071
hide={!hourlyVisible.requests}
@@ -1276,22 +1368,42 @@ export default function DashboardPage() {
12761368
<LineChart data={overviewData.byDay} margin={{ top: 0, right: 40, left: 0, bottom: 0 }}>
12771369
<CartesianGrid stroke="#334155" strokeDasharray="5 5" />
12781370
<XAxis dataKey="label" stroke="#94a3b8" fontSize={12} />
1279-
<YAxis yAxisId="left" stroke="#3b82f6" tickFormatter={(v) => formatCompactNumber(v)} fontSize={12} />
1280-
<YAxis yAxisId="right" orientation="right" stroke="#10b981" tickFormatter={(v) => formatCompactNumber(v)} fontSize={12} />
1281-
<YAxis yAxisId="cost" orientation="right" stroke="#fbbf24" tickFormatter={(v) => formatCurrency(v)} fontSize={12} />
1371+
<YAxis
1372+
yAxisId="left"
1373+
stroke={trendConfig.leftAxis.color}
1374+
tickFormatter={trendConfig.leftAxis.formatter}
1375+
fontSize={12}
1376+
/>
1377+
<YAxis
1378+
yAxisId="right"
1379+
orientation="right"
1380+
stroke={trendConfig.rightAxis.color}
1381+
tickFormatter={trendConfig.rightAxis.formatter}
1382+
fontSize={12}
1383+
hide={!trendConfig.rightAxisVisible}
1384+
/>
1385+
<YAxis
1386+
yAxisId="cost"
1387+
orientation="right"
1388+
stroke="#fbbf24"
1389+
tickFormatter={(v) => formatCurrency(v)}
1390+
fontSize={12}
1391+
hide={trendConfig.lineAxisMap.cost !== 'cost'}
1392+
/>
12821393
<Tooltip
12831394
contentStyle={{ borderRadius: 12, backgroundColor: "rgba(0,0,0,0.8)", border: "1px solid rgba(100,116,139,0.6)", color: "#f8fafc" }}
1284-
formatter={(value: number, name: string) => name === "费用" ? [formatCurrency(value), name] : [formatNumberWithCommas(value), name]}
1395+
formatter={trendTooltipFormatter}
12851396
/>
1286-
<Legend
1397+
<TrendLegend
12871398
height={24}
12881399
iconSize={10}
12891400
wrapperStyle={{ paddingTop: 0, paddingBottom: 0, lineHeight: "24px", cursor: "pointer" }}
12901401
onClick={handleTrendLegendClick}
1402+
itemSorter={(item: any) => ({ requests: 0, tokens: 1, cost: 2 } as Record<string, number>)[item?.dataKey] ?? 999}
12911403
/>
1292-
<Line hide={!trendVisible.requests} yAxisId="left" type="monotone" dataKey="requests" stroke="#3b82f6" strokeWidth={2} name="请求数" dot={{ r: 3 }} />
1293-
<Line hide={!trendVisible.tokens} yAxisId="right" type="monotone" dataKey="tokens" stroke="#10b981" strokeWidth={2} name="Tokens" dot={{ r: 3 }} />
1294-
<Line hide={!trendVisible.cost} yAxisId="cost" type="monotone" dataKey="cost" stroke="#fbbf24" strokeWidth={2} name="费用" dot={{ r: 3 }} />
1404+
<Line hide={!trendVisible.requests} yAxisId={trendConfig.lineAxisMap.requests} type="monotone" dataKey="requests" stroke="#3b82f6" strokeWidth={2} name="请求数" dot={{ r: 3 }} />
1405+
<Line hide={!trendVisible.tokens} yAxisId={trendConfig.lineAxisMap.tokens} type="monotone" dataKey="tokens" stroke="#10b981" strokeWidth={2} name="Tokens" dot={{ r: 3 }} />
1406+
<Line hide={!trendVisible.cost} yAxisId={trendConfig.lineAxisMap.cost} type="monotone" dataKey="cost" stroke="#fbbf24" strokeWidth={2} name="费用" dot={{ r: 3 }} />
12951407
</LineChart>
12961408
</ResponsiveContainer>
12971409
)}
@@ -1419,17 +1531,27 @@ export default function DashboardPage() {
14191531
<YAxis yAxisId="right" orientation="right" stroke={darkMode ? "#94a3b8" : "#64748b"} tickFormatter={(v) => formatCompactNumber(v)} fontSize={12} />
14201532
<Tooltip
14211533
contentStyle={{ borderRadius: 12, backgroundColor: "rgba(0,0,0,0.8)", border: "1px solid rgba(100,116,139,0.6)", color: "#f8fafc" }}
1422-
formatter={(value: number, name: string) => [formatNumberWithCommas(value), name]}
1534+
formatter={numericTooltipFormatter}
14231535
labelFormatter={(label) => formatHourLabel(label)}
14241536
/>
1425-
<Legend
1537+
<TrendLegend
14261538
wrapperStyle={{ cursor: "pointer" }}
14271539
onClick={handleHourlyLegendClick}
1540+
itemSorter={(item: any) => ({ requests: 0, inputTokens: 1, outputTokens: 2, reasoningTokens: 3, cachedTokens: 4 } as Record<string, number>)[item?.dataKey] ?? 999}
1541+
payload={[
1542+
{ value: "请求数", type: "line", id: "requests", color: "#3b82f6", dataKey: "requests" },
1543+
{ value: "输入", type: "square", id: "inputTokens", color: "#60a5fa", dataKey: "inputTokens" },
1544+
{ value: "输出", type: "square", id: "outputTokens", color: "#4ade80", dataKey: "outputTokens" },
1545+
{ value: "思考", type: "square", id: "reasoningTokens", color: "#fbbf24", dataKey: "reasoningTokens" },
1546+
{ value: "缓存", type: "square", id: "cachedTokens", color: "#c084fc", dataKey: "cachedTokens" },
1547+
]}
14281548
/>
1429-
<Bar hide={!hourlyVisible.inputTokens} yAxisId="right" dataKey="inputTokens" name="输入" stackId="tokens" fill="#60a5fa" />
1430-
<Bar hide={!hourlyVisible.outputTokens} yAxisId="right" dataKey="outputTokens" name="输出" stackId="tokens" fill="#4ade80" />
1431-
<Bar hide={!hourlyVisible.reasoningTokens} yAxisId="right" dataKey="reasoningTokens" name="思考" stackId="tokens" fill="#fbbf24" />
1432-
<Bar hide={!hourlyVisible.cachedTokens} yAxisId="right" dataKey="cachedTokens" name="缓存" stackId="tokens" fill="#c084fc" radius={[4, 4, 0, 0]} />
1549+
{/* 堆积柱状图 - 柔和配色,仅顶部圆角,增强动画 */}
1550+
<Bar hide={!hourlyVisible.inputTokens} yAxisId="right" dataKey="inputTokens" name="输入" stackId="tokens" fill="#60a5fa" animationDuration={600} />
1551+
<Bar hide={!hourlyVisible.outputTokens} yAxisId="right" dataKey="outputTokens" name="输出" stackId="tokens" fill="#4ade80" animationDuration={600} />
1552+
<Bar hide={!hourlyVisible.reasoningTokens} yAxisId="right" dataKey="reasoningTokens" name="思考" stackId="tokens" fill="#fbbf24" animationDuration={600} />
1553+
<Bar hide={!hourlyVisible.cachedTokens} yAxisId="right" dataKey="cachedTokens" name="缓存" stackId="tokens" fill="#c084fc" animationDuration={600} />
1554+
{/* 曲线在最上层 - 带描边突出显示 */}
14331555
<Line
14341556
hide={!hourlyVisible.requests}
14351557
yAxisId="left"

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"next": "^14.2.5",
2121
"react": "^18.2.0",
2222
"react-dom": "^18.2.0",
23-
"recharts": "^2.12.5",
23+
"recharts": "^3.6.0",
2424
"tailwindcss-animate": "^1.0.7",
2525
"zod": "^3.22.4"
2626
},

0 commit comments

Comments
 (0)