feat(usage): add last refresh timestamp in header

This commit is contained in:
Supra4E8C
2026-02-13 13:33:47 +08:00
parent 180a4ccab4
commit c4ca9be7b5
6 changed files with 22 additions and 3 deletions

View File

@@ -17,6 +17,7 @@ export interface UseUsageDataReturn {
usage: UsagePayload | null; usage: UsagePayload | null;
loading: boolean; loading: boolean;
error: string; error: string;
lastRefreshedAt: Date | null;
modelPrices: Record<string, ModelPrice>; modelPrices: Record<string, ModelPrice>;
setModelPrices: (prices: Record<string, ModelPrice>) => void; setModelPrices: (prices: Record<string, ModelPrice>) => void;
loadUsage: () => Promise<void>; loadUsage: () => Promise<void>;
@@ -38,6 +39,7 @@ export function useUsageData(): UseUsageDataReturn {
const [modelPrices, setModelPrices] = useState<Record<string, ModelPrice>>({}); const [modelPrices, setModelPrices] = useState<Record<string, ModelPrice>>({});
const [exporting, setExporting] = useState(false); const [exporting, setExporting] = useState(false);
const [importing, setImporting] = useState(false); const [importing, setImporting] = useState(false);
const [lastRefreshedAt, setLastRefreshedAt] = useState<Date | null>(null);
const importInputRef = useRef<HTMLInputElement | null>(null); const importInputRef = useRef<HTMLInputElement | null>(null);
const loadUsage = useCallback(async () => { const loadUsage = useCallback(async () => {
@@ -47,6 +49,7 @@ export function useUsageData(): UseUsageDataReturn {
const data = await usageApi.getUsage(); const data = await usageApi.getUsage();
const payload = (data?.usage ?? data) as unknown; const payload = (data?.usage ?? data) as unknown;
setUsage(payload && typeof payload === 'object' ? (payload as UsagePayload) : null); setUsage(payload && typeof payload === 'object' ? (payload as UsagePayload) : null);
setLastRefreshedAt(new Date());
} catch (err: unknown) { } catch (err: unknown) {
const message = err instanceof Error ? err.message : t('usage_stats.loading_error'); const message = err instanceof Error ? err.message : t('usage_stats.loading_error');
setError(message); setError(message);
@@ -140,6 +143,7 @@ export function useUsageData(): UseUsageDataReturn {
usage, usage,
loading, loading,
error, error,
lastRefreshedAt,
modelPrices, modelPrices,
setModelPrices: handleSetModelPrices, setModelPrices: handleSetModelPrices,
loadUsage, loadUsage,

View File

@@ -817,7 +817,8 @@
"credential_name": "Credential", "credential_name": "Credential",
"token_breakdown": "Token Type Breakdown", "token_breakdown": "Token Type Breakdown",
"input_tokens": "Input Tokens", "input_tokens": "Input Tokens",
"output_tokens": "Output Tokens" "output_tokens": "Output Tokens",
"last_updated": "Updated"
}, },
"stats": { "stats": {
"success": "Success", "success": "Success",

View File

@@ -820,7 +820,8 @@
"credential_name": "Учётные данные", "credential_name": "Учётные данные",
"token_breakdown": "Распределение типов токенов", "token_breakdown": "Распределение типов токенов",
"input_tokens": "Входные токены", "input_tokens": "Входные токены",
"output_tokens": "Выходные токены" "output_tokens": "Выходные токены",
"last_updated": "Обновлено"
}, },
"stats": { "stats": {
"success": "Успех", "success": "Успех",

View File

@@ -817,7 +817,8 @@
"credential_name": "凭证", "credential_name": "凭证",
"token_breakdown": "Token 类型分布", "token_breakdown": "Token 类型分布",
"input_tokens": "输入 Tokens", "input_tokens": "输入 Tokens",
"output_tokens": "输出 Tokens" "output_tokens": "输出 Tokens",
"last_updated": "更新于"
}, },
"stats": { "stats": {
"success": "成功", "success": "成功",

View File

@@ -25,6 +25,12 @@
flex-wrap: wrap; flex-wrap: wrap;
} }
.lastRefreshed {
font-size: 11px;
color: var(--text-tertiary);
white-space: nowrap;
}
.timeRangeGroup { .timeRangeGroup {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;

View File

@@ -137,6 +137,7 @@ export function UsagePage() {
usage, usage,
loading, loading,
error, error,
lastRefreshedAt,
modelPrices, modelPrices,
setModelPrices, setModelPrices,
loadUsage, loadUsage,
@@ -308,6 +309,11 @@ export function UsagePage() {
style={{ display: 'none' }} style={{ display: 'none' }}
onChange={handleImportChange} onChange={handleImportChange}
/> />
{lastRefreshedAt && (
<span className={styles.lastRefreshed}>
{t('usage_stats.last_updated')}: {lastRefreshedAt.toLocaleTimeString()}
</span>
)}
</div> </div>
</div> </div>