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 { IconGithub, IconBookOpen, IconExternalLink, IconCode } from '@/components/ui/icons'; import { useAuthStore, useConfigStore, useNotificationStore, useModelsStore } from '@/stores'; import { apiKeysApi } from '@/services/api/apiKeys'; import { classifyModels } from '@/utils/models'; import { STORAGE_KEY_AUTH } from '@/utils/constants'; import styles from './SystemPage.module.scss'; export function SystemPage() { const { t, i18n } = useTranslation(); const { showNotification } = useNotificationStore(); const auth = useAuthStore(); const config = useConfigStore((state) => state.config); const fetchConfig = useConfigStore((state) => state.fetchConfig); 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 apiKeysCache = useRef([]); const otherLabel = useMemo( () => (i18n.language?.toLowerCase().startsWith('zh') ? '其他' : 'Other'), [i18n.language] ); const groupedModels = useMemo(() => classifyModels(models, { otherLabel }), [models, otherLabel]); const normalizeApiKeyList = (input: any): string[] => { if (!Array.isArray(input)) return []; const seen = new Set(); const keys: string[] = []; input.forEach((item) => { const value = typeof item === 'string' ? item : item?.['api-key'] ?? item?.apiKey ?? ''; const trimmed = String(value || '').trim(); if (!trimmed || seen.has(trimmed)) return; seen.add(trimmed); keys.push(trimmed); }); return keys; }; const resolveApiKeysForModels = useCallback(async () => { if (apiKeysCache.current.length) { return apiKeysCache.current; } const configKeys = normalizeApiKeyList(config?.apiKeys); if (configKeys.length) { apiKeysCache.current = configKeys; return configKeys; } try { const list = await apiKeysApi.list(); const normalized = normalizeApiKeyList(list); if (normalized.length) { apiKeysCache.current = normalized; } return normalized; } catch (err) { console.warn('Auto loading API keys for models failed:', err); return []; } }, [config?.apiKeys]); const fetchModels = async ({ forceRefresh = false }: { forceRefresh?: boolean } = {}) => { if (auth.connectionStatus !== 'connected') { setModelStatus({ type: 'warning', message: t('notification.connection_required') }); return; } if (!auth.apiBase) { showNotification(t('notification.connection_required'), 'warning'); return; } if (forceRefresh) { apiKeysCache.current = []; } setModelStatus({ type: 'muted', message: t('system_info.models_loading') }); try { const apiKeys = await resolveApiKeysForModels(); const primaryKey = apiKeys[0]; const list = await fetchModelsFromStore(auth.apiBase, primaryKey, forceRefresh); const hasModels = list.length > 0; setModelStatus({ type: hasModels ? 'success' : 'warning', message: hasModels ? t('system_info.models_count', { count: list.length }) : t('system_info.models_empty') }); } catch (err: any) { const message = `${t('system_info.models_error')}: ${err?.message || ''}`; setModelStatus({ type: 'error', message }); } }; const handleClearLoginStorage = () => { if (!window.confirm(t('system_info.clear_login_confirm'))) return; auth.logout(); if (typeof localStorage === 'undefined') return; const keysToRemove = [STORAGE_KEY_AUTH, 'isLoggedIn', 'apiBase', 'apiUrl', 'managementKey']; keysToRemove.forEach((key) => localStorage.removeItem(key)); showNotification(t('notification.login_storage_cleared'), 'success'); }; useEffect(() => { fetchConfig().catch(() => { // ignore }); }, [fetchConfig]); useEffect(() => { fetchModels(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [auth.connectionStatus, auth.apiBase]); return (

{t('system_info.title')}

fetchConfig(undefined, true)}> {t('common.refresh')} } >
{t('connection.server_address')}
{auth.apiBase || '-'}
{t('footer.api_version')}
{auth.serverVersion || t('system_info.version_unknown')}
{t('footer.build_date')}
{auth.serverBuildDate ? new Date(auth.serverBuildDate).toLocaleString() : t('system_info.version_unknown')}
{t('connection.status')}
{t(`common.${auth.connectionStatus}_status` as any)}

{t('system_info.quick_links_desc')}

fetchModels({ forceRefresh: true })} loading={modelsLoading}> {t('common.refresh')} } >

{t('system_info.models_desc')}

{modelStatus &&
{modelStatus.message}
} {modelsError &&
{modelsError}
} {modelsLoading ? (
{t('common.loading')}
) : models.length === 0 ? (
{t('system_info.models_empty')}
) : (
{groupedModels.map((group) => (
{group.label}
{t('system_info.models_count', { count: group.items.length })}
{group.items.map((model) => ( {model.name} {model.alias && {model.alias}} ))}
))}
)}

{t('system_info.clear_login_desc')}

); }