mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-02-19 03:00:49 +08:00
feat(usage): add last refresh timestamp in header
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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": "Успех",
|
||||||
|
|||||||
@@ -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": "成功",
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user