import { create } from 'zustand'; import { usageApi } from '@/services/api'; import { useAuthStore } from '@/stores/useAuthStore'; import { collectUsageDetails, computeKeyStats, type KeyStats, type UsageDetail } from '@/utils/usage'; import i18n from '@/i18n'; export const USAGE_STATS_STALE_TIME_MS = 240_000; export type LoadUsageStatsOptions = { force?: boolean; staleTimeMs?: number; }; type UsageStatsSnapshot = Record; type UsageStatsState = { usage: UsageStatsSnapshot | null; keyStats: KeyStats; usageDetails: UsageDetail[]; loading: boolean; error: string | null; lastRefreshedAt: number | null; scopeKey: string; loadUsageStats: (options?: LoadUsageStatsOptions) => Promise; clearUsageStats: () => void; }; const createEmptyKeyStats = (): KeyStats => ({ bySource: {}, byAuthIndex: {} }); let usageRequestToken = 0; let inFlightUsageRequest: { id: number; scopeKey: string; promise: Promise } | null = null; const getErrorMessage = (error: unknown) => error instanceof Error ? error.message : typeof error === 'string' ? error : i18n.t('usage_stats.loading_error'); export const useUsageStatsStore = create((set, get) => ({ usage: null, keyStats: createEmptyKeyStats(), usageDetails: [], loading: false, error: null, lastRefreshedAt: null, scopeKey: '', loadUsageStats: async (options = {}) => { const force = options.force === true; const staleTimeMs = options.staleTimeMs ?? USAGE_STATS_STALE_TIME_MS; const { apiBase = '', managementKey = '' } = useAuthStore.getState(); const scopeKey = `${apiBase}::${managementKey}`; const state = get(); const scopeChanged = state.scopeKey !== scopeKey; // 先复用同源 in-flight 请求,避免多个页面同时发起重复 /usage。 if (inFlightUsageRequest && inFlightUsageRequest.scopeKey === scopeKey) { await inFlightUsageRequest.promise; return; } // 连接目标变化时,旧请求结果必须失效。 if (inFlightUsageRequest && inFlightUsageRequest.scopeKey !== scopeKey) { usageRequestToken += 1; inFlightUsageRequest = null; } const fresh = !scopeChanged && state.lastRefreshedAt !== null && Date.now() - state.lastRefreshedAt < staleTimeMs; if (!force && fresh) { return; } if (scopeChanged) { set({ usage: null, keyStats: createEmptyKeyStats(), usageDetails: [], error: null, lastRefreshedAt: null, scopeKey }); } const requestId = (usageRequestToken += 1); set({ loading: true, error: null, scopeKey }); const requestPromise = (async () => { try { const usageResponse = await usageApi.getUsage(); const rawUsage = usageResponse?.usage ?? usageResponse; const usage = rawUsage && typeof rawUsage === 'object' ? (rawUsage as UsageStatsSnapshot) : null; if (requestId !== usageRequestToken) return; set({ usage, keyStats: computeKeyStats(usage), usageDetails: collectUsageDetails(usage), loading: false, error: null, lastRefreshedAt: Date.now(), scopeKey }); } catch (error: unknown) { if (requestId !== usageRequestToken) return; const message = getErrorMessage(error); set({ loading: false, error: message, scopeKey }); throw new Error(message); } finally { if (inFlightUsageRequest?.id === requestId) { inFlightUsageRequest = null; } } })(); inFlightUsageRequest = { id: requestId, scopeKey, promise: requestPromise }; await requestPromise; }, clearUsageStats: () => { usageRequestToken += 1; inFlightUsageRequest = null; set({ usage: null, keyStats: createEmptyKeyStats(), usageDetails: [], loading: false, error: null, lastRefreshedAt: null, scopeKey: '' }); } }));