import { useCallback, useEffect, useRef, useState } from 'react'; import { Link } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { IconKey, IconBot, IconFileText, IconSatellite } from '@/components/ui/icons'; import { useAuthStore, useConfigStore, useModelsStore } from '@/stores'; import { apiKeysApi, providersApi, authFilesApi } from '@/services/api'; import styles from './DashboardPage.module.scss'; interface QuickStat { label: string; value: number | string; icon: React.ReactNode; path: string; loading?: boolean; sublabel?: string; } interface ProviderStats { gemini: number | null; codex: number | null; claude: number | null; openai: number | null; } export function DashboardPage() { const { t, i18n } = useTranslation(); const connectionStatus = useAuthStore((state) => state.connectionStatus); const serverVersion = useAuthStore((state) => state.serverVersion); const serverBuildDate = useAuthStore((state) => state.serverBuildDate); const apiBase = useAuthStore((state) => state.apiBase); const config = useConfigStore((state) => state.config); const models = useModelsStore((state) => state.models); const modelsLoading = useModelsStore((state) => state.loading); const fetchModelsFromStore = useModelsStore((state) => state.fetchModels); const [stats, setStats] = useState<{ apiKeys: number | null; authFiles: number | null; }>({ apiKeys: null, authFiles: null }); const [providerStats, setProviderStats] = useState({ gemini: null, codex: null, claude: null, openai: null }); const [loading, setLoading] = useState(true); const apiKeysCache = useRef([]); useEffect(() => { apiKeysCache.current = []; }, [apiBase, config?.apiKeys]); 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 { return []; } }, [config?.apiKeys]); const fetchModels = useCallback(async () => { if (connectionStatus !== 'connected' || !apiBase) { return; } try { const apiKeys = await resolveApiKeysForModels(); const primaryKey = apiKeys[0]; await fetchModelsFromStore(apiBase, primaryKey); } catch { // Ignore model fetch errors on dashboard } }, [connectionStatus, apiBase, resolveApiKeysForModels, fetchModelsFromStore]); useEffect(() => { const fetchStats = async () => { setLoading(true); try { const [keysRes, filesRes, geminiRes, codexRes, claudeRes, openaiRes] = await Promise.allSettled([ apiKeysApi.list(), authFilesApi.list(), providersApi.getGeminiKeys(), providersApi.getCodexConfigs(), providersApi.getClaudeConfigs(), providersApi.getOpenAIProviders() ]); setStats({ apiKeys: keysRes.status === 'fulfilled' ? keysRes.value.length : null, authFiles: filesRes.status === 'fulfilled' ? filesRes.value.files.length : null }); setProviderStats({ gemini: geminiRes.status === 'fulfilled' ? geminiRes.value.length : null, codex: codexRes.status === 'fulfilled' ? codexRes.value.length : null, claude: claudeRes.status === 'fulfilled' ? claudeRes.value.length : null, openai: openaiRes.status === 'fulfilled' ? openaiRes.value.length : null }); } finally { setLoading(false); } }; if (connectionStatus === 'connected') { fetchStats(); fetchModels(); } else { setLoading(false); } }, [connectionStatus, fetchModels]); // Calculate total provider keys only when all provider stats are available. const providerStatsReady = providerStats.gemini !== null && providerStats.codex !== null && providerStats.claude !== null && providerStats.openai !== null; const hasProviderStats = providerStats.gemini !== null || providerStats.codex !== null || providerStats.claude !== null || providerStats.openai !== null; const totalProviderKeys = providerStatsReady ? (providerStats.gemini ?? 0) + (providerStats.codex ?? 0) + (providerStats.claude ?? 0) + (providerStats.openai ?? 0) : 0; const quickStats: QuickStat[] = [ { label: t('nav.api_keys'), value: stats.apiKeys ?? '-', icon: , path: '/api-keys', loading: loading && stats.apiKeys === null, sublabel: t('dashboard.management_keys') }, { label: t('nav.ai_providers'), value: loading ? '-' : providerStatsReady ? totalProviderKeys : '-', icon: , path: '/ai-providers', loading: loading, sublabel: hasProviderStats ? t('dashboard.provider_keys_detail', { gemini: providerStats.gemini ?? '-', codex: providerStats.codex ?? '-', claude: providerStats.claude ?? '-', openai: providerStats.openai ?? '-' }) : undefined }, { label: t('nav.auth_files'), value: stats.authFiles ?? '-', icon: , path: '/auth-files', loading: loading && stats.authFiles === null, sublabel: t('dashboard.oauth_credentials') }, { label: t('dashboard.available_models'), value: modelsLoading ? '-' : models.length, icon: , path: '/system', loading: modelsLoading, sublabel: t('dashboard.available_models_desc') } ]; return (

{t('dashboard.title')}

{t('dashboard.subtitle')}

{t( connectionStatus === 'connected' ? 'common.connected' : connectionStatus === 'connecting' ? 'common.connecting' : 'common.disconnected' )}
{apiBase || '-'} {serverVersion && v{serverVersion}} {serverBuildDate && ( {new Date(serverBuildDate).toLocaleDateString(i18n.language)} )}
{quickStats.map((stat) => (
{stat.icon}
{stat.loading ? '...' : stat.value} {stat.label} {stat.sublabel && !stat.loading && ( {stat.sublabel} )}
))}
{config && (

{t('dashboard.current_config')}

{t('basic_settings.debug_enable')} {config.debug ? t('common.yes') : t('common.no')}
{t('basic_settings.usage_statistics_enable')} {config.usageStatisticsEnabled ? t('common.yes') : t('common.no')}
{t('basic_settings.logging_to_file_enable')} {config.loggingToFile ? t('common.yes') : t('common.no')}
{t('basic_settings.request_log_enable')} {config.requestLog ? t('common.yes') : t('common.no')}
{t('basic_settings.retry_count_label')} {config.requestRetry ?? 0}
{t('basic_settings.ws_auth_enable')} {config.wsAuth ? t('common.yes') : t('common.no')}
{config.proxyUrl && (
{t('basic_settings.proxy_url_label')} {config.proxyUrl}
)}
{t('dashboard.edit_settings')} →
)}
); }