import { useEffect, useState, useCallback } from 'react'; import Tooltip from '../ui/Tooltip'; import Warning from '../icons/Warning'; import Alert from '../icons/Alert'; import Info from '../icons/Info'; import { useTranslation } from 'react-i18next'; import Spin from '../ui/Spin'; type Status = | 'operational' | 'degraded_performance' | 'partial_outage' | 'major_outage' | undefined; export interface Props { forceStatus?: Status; provider?: string; } interface ProviderConfig { statusUrl: string; statusPage: string; apiComponentName?: string | string[]; // Component name(s) to look for } const PROVIDER_CONFIGS: Record = { OpenAI: { statusUrl: 'https://status.openai.com/api/v2/summary.json', statusPage: 'https://status.openai.com/', apiComponentName: ['Chat', 'Completions (legacy)'], }, Mistral: { statusUrl: 'https://status.mistral-data.com/api/v2/summary.json', statusPage: 'https://status.mistral-data.com/', apiComponentName: ['Mistral'], }, Anthropic: { statusUrl: 'https://status.anthropic.com/api/v2/summary.json', statusPage: 'https://status.anthropic.com/', apiComponentName: ['api.anthropic.com'], }, }; // Modified to handle fetch errors, different component names, and check incidents const fetchProviderStatus = async (config: ProviderConfig): Promise => { try { // Use cross-fetch instead of native fetch const response = await fetch(config.statusUrl); if (!response.ok) { console.warn(`Status API returned ${response.status}`); return 'operational'; // Fallback to operational on API errors } const data = await response.json(); // Skip incident checking - just rely on component status which is more reliable // If no incidents or they're all resolved, check specific API components if (data.components) { // First try with primary API component names if (config.apiComponentName) { const apiComponentNames = Array.isArray(config.apiComponentName) ? config.apiComponentName : [config.apiComponentName]; for (const name of apiComponentNames) { const apiComponent = data.components.find( (component: { name: string }) => component.name === name ); if (apiComponent && apiComponent.status !== 'operational') { return apiComponent.status as Status; } } } // Check if any component is degraded (might indicate API issues) const anyDegradedComponent = data.components.some( (component: { status: string }) => component.status !== 'operational' ); if (anyDegradedComponent) { return 'degraded_performance'; } } // If no specific component issues found, check overall status if (data.status && data.status.indicator !== 'none') { if (data.status.indicator === 'critical') return 'major_outage'; if (data.status.indicator === 'major') return 'partial_outage'; if (data.status.indicator === 'minor') return 'degraded_performance'; } return 'operational'; // Default to operational if no issues detected } catch (error) { console.error('Error fetching provider status:', error); return 'operational'; // Fallback to operational on network errors } }; const CompletionProviderStatus = ({ forceStatus, provider = 'Anthropic', }: Props) => { const { t } = useTranslation(); const [status, setStatus] = useState(forceStatus ?? 'operational'); const [isLoading, setIsLoading] = useState(false); const config = PROVIDER_CONFIGS[provider]; const getStatus = useCallback(async () => { if (!config) return 'operational'; return fetchProviderStatus(config); }, [config]); useEffect(() => { let mounted = true; const abortController = new AbortController(); const checkStatus = async () => { if (forceStatus) return; setIsLoading(true); try { const newStatus = await getStatus(); if (mounted) { setStatus(newStatus); } } catch (error) { console.error('Failed to check status:', error); if (mounted) { setStatus('operational'); // Fallback on error } } finally { if (mounted) { setIsLoading(false); } } }; checkStatus(); return () => { mounted = false; abortController.abort(); }; }, [forceStatus, getStatus]); if (isLoading) { return (
); } if (!status || status === 'operational') { return null; } // Get appropriate icon and message based on status severity const getStatusDetails = () => { switch (status) { case 'major_outage': return { Icon: Alert, message: t('completionProviderMajorOutage', { provider: provider ?? t('completionProviderFallbackName'), fallback: t('completionProviderDown', { provider: provider ?? t('completionProviderFallbackName'), }) }), className: "memori--completion-provider-status--icon-error" }; case 'partial_outage': return { Icon: Warning, message: t('completionProviderPartialOutage', { provider: provider ?? t('completionProviderFallbackName'), fallback: t('completionProviderDown', { provider: provider ?? t('completionProviderFallbackName'), }) }), className: "memori--completion-provider-status--icon-warning" }; case 'degraded_performance': default: return { Icon: Info, message: t('completionProviderDegraded', { provider: provider ?? t('completionProviderFallbackName'), fallback: t('completionProviderDown', { provider: provider ?? t('completionProviderFallbackName'), }) }), className: "memori--completion-provider-status--icon-info" }; } }; const { Icon, message, className } = getStatusDetails(); return (

{message}

{config?.statusPage && (

{t('completionProviderCheckStatusPage')}

)} } >
); }; export default CompletionProviderStatus;