fix(usage-stats): avoid partial stats on initial SSE race

Skip creating partial stats from SSE before the initial REST load
completes, keeping real-time merges limited to existing full stats.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
weimaozhen
2026-06-13 10:23:56 +07:00
committed by decolua
parent b40e96d0ef
commit 564f2ece0d

View File

@@ -204,6 +204,7 @@ export default function UsageStats({ period: periodProp, setPeriod: setPeriodPro
const [providers, setProviders] = useState([]); const [providers, setProviders] = useState([]);
const [periodLocal, setPeriodLocal] = useState("today"); const [periodLocal, setPeriodLocal] = useState("today");
const isInitialLoad = useRef(true); const isInitialLoad = useRef(true);
const hasLoadedStats = useRef(false);
const period = periodProp ?? periodLocal; const period = periodProp ?? periodLocal;
const setPeriod = setPeriodProp ?? setPeriodLocal; const setPeriod = setPeriodProp ?? setPeriodLocal;
@@ -242,14 +243,17 @@ export default function UsageStats({ period: periodProp, setPeriod: setPeriodPro
fetch(`/api/usage/stats?period=${period}`) fetch(`/api/usage/stats?period=${period}`)
.then((r) => r.ok ? r.json() : null) .then((r) => r.ok ? r.json() : null)
.then((data) => { .then((data) => {
if (data) setStats((prev) => ({ ...prev, ...data })); if (data) {
hasLoadedStats.current = true;
setStats((prev) => ({ ...prev, ...data }));
}
}) })
.catch(() => {}) .catch(() => {})
.finally(() => { .finally(() => {
setLoading(false); setLoading(false);
setFetching(false); setFetching(false);
}); });
}, [period]); // eslint-disable-line react-hooks/exhaustive-deps }, [period]);
// SSE connection - real-time updates for activeRequests + recentRequests only // SSE connection - real-time updates for activeRequests + recentRequests only
useEffect(() => { useEffect(() => {
@@ -259,14 +263,17 @@ export default function UsageStats({ period: periodProp, setPeriod: setPeriodPro
try { try {
const data = JSON.parse(e.data); const data = JSON.parse(e.data);
// Always merge only real-time fields, never overwrite full stats from REST // Always merge only real-time fields, never overwrite full stats from REST
setStats((prev) => ({ setStats((prev) => {
...(prev || {}), if (!prev) return prev;
activeRequests: data.activeRequests, return {
recentRequests: data.recentRequests, ...prev,
errorProvider: data.errorProvider, activeRequests: data.activeRequests,
pending: data.pending, recentRequests: data.recentRequests,
})); errorProvider: data.errorProvider,
setLoading(false); pending: data.pending,
};
});
if (hasLoadedStats.current) setLoading(false);
} catch (err) { } catch (err) {
console.error("[SSE CLIENT] parse error:", err); console.error("[SSE CLIENT] parse error:", err);
} }