Fix Models token usage refresh after gateway restart (#391)
Co-authored-by: zuolingxuan <zuolingxuan@bytedance.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
31e80f256b
commit
ed40a3b7f4
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
ChevronLeft,
|
ChevronLeft,
|
||||||
@@ -30,6 +30,8 @@ type UsageHistoryEntry = {
|
|||||||
|
|
||||||
type UsageWindow = '7d' | '30d' | 'all';
|
type UsageWindow = '7d' | '30d' | 'all';
|
||||||
type UsageGroupBy = 'model' | 'day';
|
type UsageGroupBy = 'model' | 'day';
|
||||||
|
const USAGE_FETCH_MAX_ATTEMPTS = 6;
|
||||||
|
const USAGE_FETCH_RETRY_DELAY_MS = 1500;
|
||||||
|
|
||||||
export function Models() {
|
export function Models() {
|
||||||
const { t } = useTranslation(['dashboard', 'settings']);
|
const { t } = useTranslation(['dashboard', 'settings']);
|
||||||
@@ -42,23 +44,106 @@ export function Models() {
|
|||||||
const [usageWindow, setUsageWindow] = useState<UsageWindow>('7d');
|
const [usageWindow, setUsageWindow] = useState<UsageWindow>('7d');
|
||||||
const [usagePage, setUsagePage] = useState(1);
|
const [usagePage, setUsagePage] = useState(1);
|
||||||
const [selectedUsageEntry, setSelectedUsageEntry] = useState<UsageHistoryEntry | null>(null);
|
const [selectedUsageEntry, setSelectedUsageEntry] = useState<UsageHistoryEntry | null>(null);
|
||||||
|
const usageFetchTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
|
const usageFetchGenerationRef = useRef(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
trackUiEvent('models.page_viewed');
|
trackUiEvent('models.page_viewed');
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isGatewayRunning) {
|
if (usageFetchTimerRef.current) {
|
||||||
hostApiFetch<UsageHistoryEntry[]>('/api/usage/recent-token-history')
|
clearTimeout(usageFetchTimerRef.current);
|
||||||
.then((entries) => {
|
usageFetchTimerRef.current = null;
|
||||||
setUsageHistory(Array.isArray(entries) ? entries : []);
|
|
||||||
setUsagePage(1);
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
setUsageHistory([]);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}, [isGatewayRunning]);
|
|
||||||
|
if (!isGatewayRunning) return;
|
||||||
|
|
||||||
|
const generation = usageFetchGenerationRef.current + 1;
|
||||||
|
usageFetchGenerationRef.current = generation;
|
||||||
|
const restartMarker = `${gatewayStatus.pid ?? 'na'}:${gatewayStatus.connectedAt ?? 'na'}`;
|
||||||
|
trackUiEvent('models.token_usage_fetch_started', {
|
||||||
|
generation,
|
||||||
|
restartMarker,
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetchUsageHistoryWithRetry = async (attempt: number) => {
|
||||||
|
trackUiEvent('models.token_usage_fetch_attempt', {
|
||||||
|
generation,
|
||||||
|
attempt,
|
||||||
|
restartMarker,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
const entries = await hostApiFetch<UsageHistoryEntry[]>('/api/usage/recent-token-history');
|
||||||
|
if (usageFetchGenerationRef.current !== generation) return;
|
||||||
|
|
||||||
|
const normalized = Array.isArray(entries) ? entries : [];
|
||||||
|
setUsageHistory(normalized);
|
||||||
|
setUsagePage(1);
|
||||||
|
trackUiEvent('models.token_usage_fetch_succeeded', {
|
||||||
|
generation,
|
||||||
|
attempt,
|
||||||
|
records: normalized.length,
|
||||||
|
restartMarker,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (normalized.length === 0 && attempt < USAGE_FETCH_MAX_ATTEMPTS) {
|
||||||
|
trackUiEvent('models.token_usage_fetch_retry_scheduled', {
|
||||||
|
generation,
|
||||||
|
attempt,
|
||||||
|
reason: 'empty',
|
||||||
|
restartMarker,
|
||||||
|
});
|
||||||
|
usageFetchTimerRef.current = setTimeout(() => {
|
||||||
|
void fetchUsageHistoryWithRetry(attempt + 1);
|
||||||
|
}, USAGE_FETCH_RETRY_DELAY_MS);
|
||||||
|
} else if (normalized.length === 0) {
|
||||||
|
trackUiEvent('models.token_usage_fetch_exhausted', {
|
||||||
|
generation,
|
||||||
|
attempt,
|
||||||
|
reason: 'empty',
|
||||||
|
restartMarker,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (usageFetchGenerationRef.current !== generation) return;
|
||||||
|
trackUiEvent('models.token_usage_fetch_failed_attempt', {
|
||||||
|
generation,
|
||||||
|
attempt,
|
||||||
|
restartMarker,
|
||||||
|
message: error instanceof Error ? error.message : String(error),
|
||||||
|
});
|
||||||
|
if (attempt < USAGE_FETCH_MAX_ATTEMPTS) {
|
||||||
|
trackUiEvent('models.token_usage_fetch_retry_scheduled', {
|
||||||
|
generation,
|
||||||
|
attempt,
|
||||||
|
reason: 'error',
|
||||||
|
restartMarker,
|
||||||
|
});
|
||||||
|
usageFetchTimerRef.current = setTimeout(() => {
|
||||||
|
void fetchUsageHistoryWithRetry(attempt + 1);
|
||||||
|
}, USAGE_FETCH_RETRY_DELAY_MS);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setUsageHistory([]);
|
||||||
|
trackUiEvent('models.token_usage_fetch_exhausted', {
|
||||||
|
generation,
|
||||||
|
attempt,
|
||||||
|
reason: 'error',
|
||||||
|
restartMarker,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void fetchUsageHistoryWithRetry(1);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (usageFetchTimerRef.current) {
|
||||||
|
clearTimeout(usageFetchTimerRef.current);
|
||||||
|
usageFetchTimerRef.current = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [isGatewayRunning, gatewayStatus.connectedAt, gatewayStatus.pid]);
|
||||||
|
|
||||||
const visibleUsageHistory = isGatewayRunning ? usageHistory : [];
|
const visibleUsageHistory = isGatewayRunning ? usageHistory : [];
|
||||||
const filteredUsageHistory = filterUsageHistoryByWindow(visibleUsageHistory, usageWindow);
|
const filteredUsageHistory = filterUsageHistoryByWindow(visibleUsageHistory, usageWindow);
|
||||||
|
|||||||
Reference in New Issue
Block a user