feat(dashboard): 最近请求缓存命中列 + 中文模式人民币计价 + 价格解析记忆化
- 最近请求表新增缓存命中率列(缓存读取/总输入) - useCurrency:中文界面费用按最新 USD→CNY 汇率显示 ¥,英文保持 $(open.er-api.com,缓存 12h,失败兜底 7.2) - match_price 记忆化缓存:O(记录数×5910) → O(模型种类×5910),扫描从秒级降到百毫秒级 - 仪表盘右上角显示加载耗时/数据量(get_summary 往返埋点);Rust 侧分段计时
This commit is contained in:
1 parent
de132ffb3a
commit
38a11666ac
9 files changed
+173
-28
No files matched your search
@@ -0,0 +1,83 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useTranslation } from "../i18n";
|
||||
import { fmtUsd } from "../lib/dashboard-format";
|
||||
|
||||
/**
|
||||
* USD → CNY exchange rate for the dashboard.
|
||||
* Chinese mode shows prices in CNY (latest available rate), English stays in
|
||||
* USD. The rate is fetched once and cached in localStorage for 12h; any
|
||||
* failure falls back to a conservative fixed rate so the UI never blocks.
|
||||
*/
|
||||
|
||||
const FALLBACK_RATE = 7.2;
|
||||
const CACHE_KEY = "kimi-switch-cny-rate";
|
||||
const CACHE_TTL = 12 * 60 * 60 * 1000; // 12 hours
|
||||
|
||||
function readCachedRate(): number | null {
|
||||
try {
|
||||
const raw = localStorage.getItem(CACHE_KEY);
|
||||
if (!raw) return null;
|
||||
const { ts, rate } = JSON.parse(raw) as { ts: number; rate: number };
|
||||
if (Date.now() - ts > CACHE_TTL || !(rate > 1)) return null;
|
||||
return rate;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function writeCachedRate(rate: number) {
|
||||
try {
|
||||
localStorage.setItem(CACHE_KEY, JSON.stringify({ ts: Date.now(), rate }));
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
export function useCurrency() {
|
||||
const { lang } = useTranslation();
|
||||
const [rate, setRate] = useState<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
(async () => {
|
||||
const cached = readCachedRate();
|
||||
if (cached) {
|
||||
setRate(cached);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await fetch("https://open.er-api.com/v6/latest/USD");
|
||||
if (res.ok) {
|
||||
const data = (await res.json()) as { rates?: { CNY?: number } };
|
||||
const cny = Number(data.rates?.CNY);
|
||||
if (cny > 1) {
|
||||
if (!cancelled) setRate(cny);
|
||||
writeCachedRate(cny);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
/* network blocked → fallback */
|
||||
}
|
||||
if (!cancelled) setRate(FALLBACK_RATE);
|
||||
})();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
/** Format a USD cost for display: CNY in Chinese mode, USD otherwise. */
|
||||
const money = useCallback(
|
||||
(usd: number): string => {
|
||||
if (lang !== "zh") return fmtUsd(usd);
|
||||
const v = (Number(usd) || 0) * (rate ?? FALLBACK_RATE);
|
||||
if (v === 0) return "¥0.00";
|
||||
if (v < 0.1) return "¥" + v.toFixed(3);
|
||||
if (v < 1) return "¥" + v.toFixed(2);
|
||||
return "¥" + v.toFixed(2);
|
||||
},
|
||||
[lang, rate],
|
||||
);
|
||||
|
||||
return { money, cnyRate: rate ?? FALLBACK_RATE };
|
||||
}
|
||||
@@ -22,16 +22,23 @@ export function useDashboard() {
|
||||
const [data, setData] = useState<SummaryResult | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
/** Round-trip timing for the last get_summary call (user-perceived lag). */
|
||||
const [loadStats, setLoadStats] = useState<{ ms: number; kb: number } | null>(null);
|
||||
|
||||
const refresh = useCallback(async (force = false) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
const start = performance.now();
|
||||
try {
|
||||
const result = await invoke<SummaryResult>("get_summary", {
|
||||
range,
|
||||
refresh: force,
|
||||
});
|
||||
setData(result);
|
||||
setLoadStats({
|
||||
ms: Math.round(performance.now() - start),
|
||||
kb: Math.round(JSON.stringify(result).length / 1024),
|
||||
});
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
setError(msg);
|
||||
@@ -53,5 +60,5 @@ export function useDashboard() {
|
||||
}
|
||||
}, []);
|
||||
|
||||
return { range, changeRange, data, loading, error, refresh };
|
||||
return { range, changeRange, data, loading, error, refresh, loadStats };
|
||||
}
|
||||
Reference in new issue
Block a user