Skip to content

Commit 694db35

Browse files
committed
fix: 修复堆叠图重复计数问题,优化输入和缓存显示逻辑
1 parent a146e3b commit 694db35

2 files changed

Lines changed: 56 additions & 18 deletions

File tree

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
# CHANGELOG
22

3+
## 2026-03-07
4+
5+
- 修复每小时负载分布堆叠图中"输入"与"缓存"重复计数的问题:
6+
- 原始数据中 `inputTokens` 包含缓存命中部分,与 `cachedTokens` 存在子集重叠,导致堆叠图双重计数。
7+
-`hourlySeries` useMemo 输出前对每个数据点执行 `inputTokens = Math.max(0, inputTokens - cachedTokens)`,使两者在图中不重叠。
8+
- 调整普通图和全屏图的堆叠顺序为:输入 → 缓存 → 输出 → 思考,保持"输入"与"缓存"在视觉上相邻,语义更清晰。
9+
- 同步更新两个图的 tooltip 排序、图例排序,以及顶层圆角(`radius` 移至新顶层的思考柱)。
10+
11+
- Tokens 卡片"缓存"改为缓存命中率显示,"输入"增加 hover 展示未命中输入:
12+
- "缓存"行:默认标签"缓存命中率"及百分比,hover 切换为"缓存"及实际 token 数。
13+
- "输入"行:默认标签"输入"及总输入 token 数,hover 切换为"未命中输入"及 `totalInputTokens - totalCachedTokens`
14+
- 两行均使用相同的 opacity 过渡动画(duration-200),`absolute` 覆盖层不影响布局。
15+
316
## 2026-03-06
417

518
- 修复首页"无法加载实时用量:"后内容为空的问题:

app/page.tsx

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -837,9 +837,14 @@ export default function DashboardPage() {
837837

838838
const hourlySeries = useMemo(() => {
839839
if (!overviewData?.byHour) return [] as UsageSeriesPoint[];
840-
if (hourRange === "all") return overviewData.byHour;
841-
const hours = hourRange === "24h" ? 24 : 72;
842-
return buildHourlySeries(overviewData.byHour, hours, bucketTimezone);
840+
const raw = hourRange === "all"
841+
? overviewData.byHour
842+
: buildHourlySeries(overviewData.byHour, hourRange === "24h" ? 24 : 72, bucketTimezone);
843+
// 输入仅计未命中缓存部分,避免与缓存字段在堆叠图中重复计数
844+
return raw.map(p => ({
845+
...p,
846+
inputTokens: Math.max(0, (p.inputTokens ?? 0) - (p.cachedTokens ?? 0))
847+
}));
843848
}, [hourRange, overviewData?.byHour, bucketTimezone]);
844849

845850
const hourlyLineStyle = useMemo(
@@ -1556,9 +1561,17 @@ export default function DashboardPage() {
15561561
</div>
15571562
</div>
15581563
<div className="mt-4 grid grid-cols-2 gap-x-6 gap-y-2 text-sm">
1559-
<div className="flex items-center justify-between">
1560-
<span className={darkMode ? "text-slate-400" : "text-slate-500"}>输入</span>
1561-
<span className="font-medium" style={{ color: darkMode ? "#fb7185" : "#e11d48" }}>{formatNumberWithCommas(overviewData.totalInputTokens)}</span>
1564+
<div className="flex items-center justify-between group cursor-default">
1565+
<span className={`relative ${darkMode ? "text-slate-400" : "text-slate-500"}`}>
1566+
<span className="transition-opacity duration-200 group-hover:opacity-0 select-none">输入</span>
1567+
<span className="absolute left-0 top-0 whitespace-nowrap opacity-0 transition-opacity duration-200 group-hover:opacity-100">未命中输入</span>
1568+
</span>
1569+
<span className="relative font-medium" style={{ color: darkMode ? "#fb7185" : "#e11d48" }}>
1570+
<span className="transition-opacity duration-200 group-hover:opacity-0 select-none">{formatNumberWithCommas(overviewData.totalInputTokens)}</span>
1571+
<span className="absolute right-0 top-0 whitespace-nowrap opacity-0 transition-opacity duration-200 group-hover:opacity-100">
1572+
{formatNumberWithCommas(Math.max(0, overviewData.totalInputTokens - overviewData.totalCachedTokens))}
1573+
</span>
1574+
</span>
15621575
</div>
15631576
<div className="flex items-center justify-between">
15641577
<span className={darkMode ? "text-slate-400" : "text-slate-500"}>输出</span>
@@ -1568,9 +1581,21 @@ export default function DashboardPage() {
15681581
<span className={darkMode ? "text-slate-400" : "text-slate-500"}>思考</span>
15691582
<span className="font-medium" style={{ color: darkMode ? "#fbbf24" : "#d97706" }}>{formatNumberWithCommas(overviewData.totalReasoningTokens)}</span>
15701583
</div>
1571-
<div className="flex items-center justify-between">
1572-
<span className={darkMode ? "text-slate-400" : "text-slate-500"}>缓存</span>
1573-
<span className="font-medium" style={{ color: darkMode ? "#c084fc" : "#9333ea" }}>{formatNumberWithCommas(overviewData.totalCachedTokens)}</span>
1584+
<div className="flex items-center justify-between group cursor-default">
1585+
<span className={`relative ${darkMode ? "text-slate-400" : "text-slate-500"}`}>
1586+
<span className="transition-opacity duration-200 group-hover:opacity-0 select-none">缓存命中率</span>
1587+
<span className="absolute left-0 top-0 whitespace-nowrap opacity-0 transition-opacity duration-200 group-hover:opacity-100">缓存</span>
1588+
</span>
1589+
<span className="relative font-medium" style={{ color: darkMode ? "#c084fc" : "#9333ea" }}>
1590+
<span className="transition-opacity duration-200 group-hover:opacity-0 select-none">
1591+
{overviewData.totalInputTokens > 0
1592+
? `${((overviewData.totalCachedTokens / overviewData.totalInputTokens) * 100).toFixed(2)}%`
1593+
: "0.00%"}
1594+
</span>
1595+
<span className="absolute right-0 top-0 whitespace-nowrap opacity-0 transition-opacity duration-200 group-hover:opacity-100">
1596+
{formatNumberWithCommas(overviewData.totalCachedTokens)}
1597+
</span>
1598+
</span>
15741599
</div>
15751600
</div>
15761601
</div>
@@ -2065,9 +2090,9 @@ export default function DashboardPage() {
20652090
/>
20662091
{/* 堆积柱状图 - 柔和配色,仅顶部圆角,增强动画 */}
20672092
<Bar hide={!hourlyVisible.inputTokens} yAxisId="right" dataKey="inputTokens" name="输入" stackId="tokens" fill="url(#gradInput)" fillOpacity={0.8} animationDuration={600} barSize={24} />
2093+
<Bar hide={!hourlyVisible.cachedTokens} yAxisId="right" dataKey="cachedTokens" name="缓存" stackId="tokens" fill="url(#gradCached)" fillOpacity={0.8} animationDuration={600} barSize={24} />
20682094
<Bar hide={!hourlyVisible.outputTokens} yAxisId="right" dataKey="outputTokens" name="输出" stackId="tokens" fill="url(#gradOutput)" fillOpacity={0.8} animationDuration={600} barSize={24} />
2069-
<Bar hide={!hourlyVisible.reasoningTokens} yAxisId="right" dataKey="reasoningTokens" name="思考" stackId="tokens" fill="url(#gradReasoning)" fillOpacity={0.8} animationDuration={600} barSize={24} />
2070-
<Bar hide={!hourlyVisible.cachedTokens} yAxisId="right" dataKey="cachedTokens" name="缓存" stackId="tokens" fill="url(#gradCached)" fillOpacity={0.8} radius={[4, 4, 0, 0]} animationDuration={600} barSize={24} />
2095+
<Bar hide={!hourlyVisible.reasoningTokens} yAxisId="right" dataKey="reasoningTokens" name="思考" stackId="tokens" fill="url(#gradReasoning)" fillOpacity={0.8} radius={[4, 4, 0, 0]} animationDuration={600} barSize={24} />
20712096
{/* 曲线在最上层 - 带描边突出显示 */}
20722097
<Line
20732098
hide={!hourlyVisible.requests}
@@ -2772,9 +2797,9 @@ export default function DashboardPage() {
27722797
const keyMap: Record<string, string> = {
27732798
"请求数": "requests",
27742799
"输入": "inputTokens",
2800+
"缓存": "cachedTokens",
27752801
"输出": "outputTokens",
2776-
"思考": "reasoningTokens",
2777-
"缓存": "cachedTokens"
2802+
"思考": "reasoningTokens"
27782803
};
27792804
const key = keyMap[value];
27802805
const isVisible = hourlyVisible[key];
@@ -2786,9 +2811,9 @@ export default function DashboardPage() {
27862811
const colors: Record<string, string> = {
27872812
"请求数": darkMode ? "#60a5fa" : "#3b82f6",
27882813
"输入": darkMode ? "#fb7185" : "#e11d48",
2814+
"缓存": darkMode ? "#c084fc" : "#9333ea",
27892815
"输出": darkMode ? "#4ade80" : "#16a34a",
2790-
"思考": darkMode ? "#fbbf24" : "#d97706",
2791-
"缓存": darkMode ? "#c084fc" : "#9333ea"
2816+
"思考": darkMode ? "#fbbf24" : "#d97706"
27922817
};
27932818
return <span style={{ color: colors[value] || "inherit", fontWeight: 500 }} title="按住 Ctrl 点击可只显示该项">{value}</span>;
27942819
}}
@@ -2805,16 +2830,16 @@ export default function DashboardPage() {
28052830
{fullscreenHourlyMode === "area" ? (
28062831
<>
28072832
<Area hide={!hourlyVisible.inputTokens} yAxisId="right" dataKey="inputTokens" name="输入" stackId="tokens" type="monotone" stroke="#fca5a5" fill="url(#gradInputFS)" fillOpacity={0.35} animationDuration={600} />
2833+
<Area hide={!hourlyVisible.cachedTokens} yAxisId="right" dataKey="cachedTokens" name="缓存" stackId="tokens" type="monotone" stroke="#c084fc" fill="url(#gradCachedFS)" fillOpacity={0.35} animationDuration={600} />
28082834
<Area hide={!hourlyVisible.outputTokens} yAxisId="right" dataKey="outputTokens" name="输出" stackId="tokens" type="monotone" stroke="#4ade80" fill="url(#gradOutputFS)" fillOpacity={0.35} animationDuration={600} />
28092835
<Area hide={!hourlyVisible.reasoningTokens} yAxisId="right" dataKey="reasoningTokens" name="思考" stackId="tokens" type="monotone" stroke="#fbbf24" fill="url(#gradReasoningFS)" fillOpacity={0.35} animationDuration={600} />
2810-
<Area hide={!hourlyVisible.cachedTokens} yAxisId="right" dataKey="cachedTokens" name="缓存" stackId="tokens" type="monotone" stroke="#c084fc" fill="url(#gradCachedFS)" fillOpacity={0.35} animationDuration={600} />
28112836
</>
28122837
) : (
28132838
<>
28142839
<Bar hide={!hourlyVisible.inputTokens} yAxisId="right" dataKey="inputTokens" name="输入" stackId="tokens" fill="url(#gradInputFS)" fillOpacity={0.8} animationDuration={600} barSize={32} />
2815-
<Bar hide={!hourlyVisible.outputTokens} yAxisId="right" dataKey="outputTokens" name="输出" stackId="tokens" fill="url(#gradOutputFS)" fillOpacity={0.8} animationDuration={600} barSize={32} />
2816-
<Bar hide={!hourlyVisible.reasoningTokens} yAxisId="right" dataKey="reasoningTokens" name="思考" stackId="tokens" fill="url(#gradReasoningFS)" fillOpacity={0.8} animationDuration={600} barSize={32} />
28172840
<Bar hide={!hourlyVisible.cachedTokens} yAxisId="right" dataKey="cachedTokens" name="缓存" stackId="tokens" fill="url(#gradCachedFS)" fillOpacity={0.8} animationDuration={600} barSize={32} />
2841+
<Bar hide={!hourlyVisible.outputTokens} yAxisId="right" dataKey="outputTokens" name="输出" stackId="tokens" fill="url(#gradOutputFS)" fillOpacity={0.8} animationDuration={600} barSize={32} />
2842+
<Bar hide={!hourlyVisible.reasoningTokens} yAxisId="right" dataKey="reasoningTokens" name="思考" stackId="tokens" fill="url(#gradReasoningFS)" fillOpacity={0.8} radius={[4, 4, 0, 0]} animationDuration={600} barSize={32} />
28182843
</>
28192844
)}
28202845
{/* 曲线在最上层 - 带描边突出显示 */}

0 commit comments

Comments
 (0)