/** * 配置状态管理 * 从原项目 src/core/config-service.js 迁移 */ import { create } from 'zustand'; import type { Config } from '@/types'; import type { RawConfigSection } from '@/types/config'; import { configApi } from '@/services/api/config'; import { CACHE_EXPIRY_MS } from '@/utils/constants'; interface ConfigCache { data: any; timestamp: number; } interface ConfigState { config: Config | null; cache: Map; loading: boolean; error: string | null; // 操作 fetchConfig: (section?: RawConfigSection, forceRefresh?: boolean) => Promise; updateConfigValue: (section: RawConfigSection, value: any) => void; clearCache: (section?: RawConfigSection) => void; isCacheValid: (section?: RawConfigSection) => boolean; } let configRequestToken = 0; let inFlightConfigRequest: { id: number; promise: Promise } | null = null; const SECTION_KEYS: RawConfigSection[] = [ 'debug', 'proxy-url', 'request-retry', 'quota-exceeded', 'usage-statistics-enabled', 'request-log', 'logging-to-file', 'ws-auth', 'api-keys', 'ampcode', 'gemini-api-key', 'codex-api-key', 'claude-api-key', 'openai-compatibility', 'oauth-excluded-models' ]; const extractSectionValue = (config: Config | null, section?: RawConfigSection) => { if (!config) return undefined; switch (section) { case 'debug': return config.debug; case 'proxy-url': return config.proxyUrl; case 'request-retry': return config.requestRetry; case 'quota-exceeded': return config.quotaExceeded; case 'usage-statistics-enabled': return config.usageStatisticsEnabled; case 'request-log': return config.requestLog; case 'logging-to-file': return config.loggingToFile; case 'ws-auth': return config.wsAuth; case 'api-keys': return config.apiKeys; case 'ampcode': return config.ampcode; case 'gemini-api-key': return config.geminiApiKeys; case 'codex-api-key': return config.codexApiKeys; case 'claude-api-key': return config.claudeApiKeys; case 'openai-compatibility': return config.openaiCompatibility; case 'oauth-excluded-models': return config.oauthExcludedModels; default: if (!section) return undefined; return config.raw?.[section]; } }; export const useConfigStore = create((set, get) => ({ config: null, cache: new Map(), loading: false, error: null, fetchConfig: async (section, forceRefresh = false) => { const { cache, isCacheValid } = get(); // 检查缓存 const cacheKey = section || '__full__'; if (!forceRefresh && isCacheValid(section)) { const cached = cache.get(cacheKey); if (cached) { return cached.data; } } // section 缓存未命中但 full 缓存可用时,直接复用已获取到的配置,避免重复 /config 请求 if (!forceRefresh && section && isCacheValid()) { const fullCached = cache.get('__full__'); if (fullCached?.data) { return extractSectionValue(fullCached.data as Config, section); } } // 同一时刻合并多个 /config 请求(如 StrictMode 或多个页面同时触发) if (inFlightConfigRequest) { const data = await inFlightConfigRequest.promise; return section ? extractSectionValue(data, section) : data; } // 获取新数据 set({ loading: true, error: null }); const requestId = (configRequestToken += 1); try { const requestPromise = configApi.getConfig(); inFlightConfigRequest = { id: requestId, promise: requestPromise }; const data = await requestPromise; const now = Date.now(); // 如果在请求过程中连接已被切换/登出,则忽略旧请求的结果,避免覆盖新会话的状态 if (requestId !== configRequestToken) { return section ? extractSectionValue(data, section) : data; } // 更新缓存 const newCache = new Map(cache); newCache.set('__full__', { data, timestamp: now }); SECTION_KEYS.forEach((key) => { const value = extractSectionValue(data, key); if (value !== undefined) { newCache.set(key, { data: value, timestamp: now }); } }); set({ config: data, cache: newCache, loading: false }); return section ? extractSectionValue(data, section) : data; } catch (error: any) { if (requestId === configRequestToken) { set({ error: error.message || 'Failed to fetch config', loading: false }); } throw error; } finally { if (inFlightConfigRequest?.id === requestId) { inFlightConfigRequest = null; } } }, updateConfigValue: (section, value) => { set((state) => { const raw = { ...(state.config?.raw || {}) }; raw[section] = value; const nextConfig: Config = { ...(state.config || {}), raw }; switch (section) { case 'debug': nextConfig.debug = value; break; case 'proxy-url': nextConfig.proxyUrl = value; break; case 'request-retry': nextConfig.requestRetry = value; break; case 'quota-exceeded': nextConfig.quotaExceeded = value; break; case 'usage-statistics-enabled': nextConfig.usageStatisticsEnabled = value; break; case 'request-log': nextConfig.requestLog = value; break; case 'logging-to-file': nextConfig.loggingToFile = value; break; case 'ws-auth': nextConfig.wsAuth = value; break; case 'api-keys': nextConfig.apiKeys = value; break; case 'ampcode': nextConfig.ampcode = value; break; case 'gemini-api-key': nextConfig.geminiApiKeys = value; break; case 'codex-api-key': nextConfig.codexApiKeys = value; break; case 'claude-api-key': nextConfig.claudeApiKeys = value; break; case 'openai-compatibility': nextConfig.openaiCompatibility = value; break; case 'oauth-excluded-models': nextConfig.oauthExcludedModels = value; break; default: break; } return { config: nextConfig }; }); // 清除该 section 的缓存 get().clearCache(section); }, clearCache: (section) => { const { cache } = get(); const newCache = new Map(cache); if (section) { newCache.delete(section); // 同时清除完整配置缓存 newCache.delete('__full__'); set({ cache: newCache }); return; } else { newCache.clear(); } // 清除全部缓存一般代表“切换连接/登出/全量刷新”,需要让 in-flight 的旧请求失效 configRequestToken += 1; inFlightConfigRequest = null; set({ config: null, cache: newCache, loading: false, error: null }); }, isCacheValid: (section) => { const { cache } = get(); const cacheKey = section || '__full__'; const cached = cache.get(cacheKey); if (!cached) return false; return Date.now() - cached.timestamp < CACHE_EXPIRY_MS; } }));