diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index de3203d..f984afb 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -551,7 +551,7 @@ "save_success": "Configuration saved successfully", "error_yaml_not_supported": "Server did not return YAML. Verify the /config.yaml endpoint is available.", "editor_placeholder": "key: value", - "search_placeholder": "Type then click the search button (or press Enter) to search", + "search_placeholder": "Search config...", "search_button": "Search", "search_no_results": "No results", "search_prev": "Previous", diff --git a/src/i18n/locales/zh-CN.json b/src/i18n/locales/zh-CN.json index d83abdd..c277f76 100644 --- a/src/i18n/locales/zh-CN.json +++ b/src/i18n/locales/zh-CN.json @@ -551,7 +551,7 @@ "save_success": "配置已保存", "error_yaml_not_supported": "服务器未返回 YAML 格式,请确认 /config.yaml 接口可用", "editor_placeholder": "key: value", - "search_placeholder": "输入关键字后点击右侧搜索按钮(或 Enter)进行搜索", + "search_placeholder": "搜索配置内容...", "search_button": "搜索", "search_no_results": "无结果", "search_prev": "上一个", diff --git a/src/pages/ConfigPage.tsx b/src/pages/ConfigPage.tsx index 5aefe62..bc082ae 100644 --- a/src/pages/ConfigPage.tsx +++ b/src/pages/ConfigPage.tsx @@ -237,7 +237,7 @@ export function ConfigPage() { onChange={(e) => handleSearchChange(e.target.value)} onKeyDown={handleSearchKeyDown} placeholder={t('config_management.search_placeholder', { - defaultValue: '输入关键字后点击右侧搜索按钮(或 Enter)进行搜索' + defaultValue: '搜索配置内容...' })} disabled={disableControls || loading} className={styles.searchInput} diff --git a/src/pages/SystemPage.tsx b/src/pages/SystemPage.tsx index 9286aa8..3f5242c 100644 --- a/src/pages/SystemPage.tsx +++ b/src/pages/SystemPage.tsx @@ -2,10 +2,9 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Card } from '@/components/ui/Card'; import { Button } from '@/components/ui/Button'; -import { useAuthStore, useConfigStore, useNotificationStore } from '@/stores'; -import { modelsApi } from '@/services/api/models'; +import { useAuthStore, useConfigStore, useNotificationStore, useModelsStore } from '@/stores'; import { apiKeysApi } from '@/services/api/apiKeys'; -import { classifyModels, type ModelInfo } from '@/utils/models'; +import { classifyModels } from '@/utils/models'; import styles from './SystemPage.module.scss'; export function SystemPage() { @@ -15,10 +14,12 @@ export function SystemPage() { const config = useConfigStore((state) => state.config); const fetchConfig = useConfigStore((state) => state.fetchConfig); - const [models, setModels] = useState([]); - const [loadingModels, setLoadingModels] = useState(false); + const models = useModelsStore((state) => state.models); + const modelsLoading = useModelsStore((state) => state.loading); + const modelsError = useModelsStore((state) => state.error); + const fetchModelsFromStore = useModelsStore((state) => state.fetchModels); + const [modelStatus, setModelStatus] = useState<{ type: 'success' | 'warning' | 'error' | 'muted'; message: string }>(); - const [error, setError] = useState(''); const apiKeysCache = useRef([]); @@ -68,13 +69,12 @@ export function SystemPage() { } }, [config?.apiKeys]); - const fetchModels = async ({ forceRefreshKeys = false }: { forceRefreshKeys?: boolean } = {}) => { + const fetchModels = async ({ forceRefresh = false }: { forceRefresh?: boolean } = {}) => { if (auth.connectionStatus !== 'connected') { setModelStatus({ type: 'warning', message: t('notification.connection_required') }); - setModels([]); return; } @@ -83,18 +83,15 @@ export function SystemPage() { return; } - if (forceRefreshKeys) { + if (forceRefresh) { apiKeysCache.current = []; } - setLoadingModels(true); - setError(''); setModelStatus({ type: 'muted', message: t('system_info.models_loading') }); try { const apiKeys = await resolveApiKeysForModels(); const primaryKey = apiKeys[0]; - const list = await modelsApi.fetchModels(auth.apiBase, primaryKey); - setModels(list); + const list = await fetchModelsFromStore(auth.apiBase, primaryKey, forceRefresh); const hasModels = list.length > 0; setModelStatus({ type: hasModels ? 'success' : 'warning', @@ -102,11 +99,7 @@ export function SystemPage() { }); } catch (err: any) { const message = `${t('system_info.models_error')}: ${err?.message || ''}`; - setError(message); - setModels([]); setModelStatus({ type: 'error', message }); - } finally { - setLoadingModels(false); } }; @@ -156,15 +149,15 @@ export function SystemPage() { fetchModels({ forceRefreshKeys: true })} loading={loadingModels}> + } >

{t('system_info.models_desc')}

{modelStatus &&
{modelStatus.message}
} - {error &&
{error}
} - {loadingModels ? ( + {modelsError &&
{modelsError}
} + {modelsLoading ? (
{t('common.loading')}
) : models.length === 0 ? (
{t('system_info.models_empty')}
diff --git a/src/stores/index.ts b/src/stores/index.ts index be3bce0..01429fd 100644 --- a/src/stores/index.ts +++ b/src/stores/index.ts @@ -7,3 +7,4 @@ export { useThemeStore } from './useThemeStore'; export { useLanguageStore } from './useLanguageStore'; export { useAuthStore } from './useAuthStore'; export { useConfigStore } from './useConfigStore'; +export { useModelsStore } from './useModelsStore'; diff --git a/src/stores/useModelsStore.ts b/src/stores/useModelsStore.ts new file mode 100644 index 0000000..7a70d80 --- /dev/null +++ b/src/stores/useModelsStore.ts @@ -0,0 +1,76 @@ +/** + * 模型列表状态管理(带缓存) + */ + +import { create } from 'zustand'; +import { modelsApi } from '@/services/api/models'; +import { CACHE_EXPIRY_MS } from '@/utils/constants'; +import type { ModelInfo } from '@/utils/models'; + +interface ModelsCache { + data: ModelInfo[]; + timestamp: number; + apiBase: string; +} + +interface ModelsState { + models: ModelInfo[]; + loading: boolean; + error: string | null; + cache: ModelsCache | null; + + fetchModels: (apiBase: string, apiKey?: string, forceRefresh?: boolean) => Promise; + clearCache: () => void; + isCacheValid: (apiBase: string) => boolean; +} + +export const useModelsStore = create((set, get) => ({ + models: [], + loading: false, + error: null, + cache: null, + + fetchModels: async (apiBase, apiKey, forceRefresh = false) => { + const { cache, isCacheValid } = get(); + + // 检查缓存 + if (!forceRefresh && isCacheValid(apiBase) && cache) { + set({ models: cache.data, error: null }); + return cache.data; + } + + set({ loading: true, error: null }); + + try { + const list = await modelsApi.fetchModels(apiBase, apiKey); + const now = Date.now(); + + set({ + models: list, + loading: false, + cache: { data: list, timestamp: now, apiBase } + }); + + return list; + } catch (error: any) { + const message = error?.message || 'Failed to fetch models'; + set({ + error: message, + loading: false, + models: [] + }); + throw error; + } + }, + + clearCache: () => { + set({ cache: null, models: [] }); + }, + + isCacheValid: (apiBase) => { + const { cache } = get(); + if (!cache) return false; + if (cache.apiBase !== apiBase) return false; + return Date.now() - cache.timestamp < CACHE_EXPIRY_MS; + } +}));