big fixes
fixes feedback from thecodacus
This commit is contained in:
@@ -6,7 +6,6 @@ import type { IProviderConfig } from '~/types/model';
|
||||
import { logStore } from '~/lib/stores/logs';
|
||||
import { motion } from 'framer-motion';
|
||||
import { classNames } from '~/utils/classNames';
|
||||
import { settingsStyles } from '~/components/settings/settings.styles';
|
||||
import { toast } from 'react-toastify';
|
||||
import { providerBaseUrlEnvKeys } from '~/utils/constants';
|
||||
import { SiAmazon, SiGoogle, SiHuggingface, SiPerplexity, SiOpenai } from 'react-icons/si';
|
||||
@@ -168,7 +167,7 @@ const CloudProvidersTab = () => {
|
||||
<motion.div
|
||||
key={provider.name}
|
||||
className={classNames(
|
||||
settingsStyles.card,
|
||||
'rounded-lg border bg-bolt-elements-background text-bolt-elements-textPrimary shadow-sm',
|
||||
'bg-bolt-elements-background-depth-2',
|
||||
'hover:bg-bolt-elements-background-depth-3',
|
||||
'transition-all duration-200',
|
||||
|
||||
@@ -6,7 +6,6 @@ import type { IProviderConfig } from '~/types/model';
|
||||
import { logStore } from '~/lib/stores/logs';
|
||||
import { motion } from 'framer-motion';
|
||||
import { classNames } from '~/utils/classNames';
|
||||
import { settingsStyles } from '~/components/settings/settings.styles';
|
||||
import { BsRobot } from 'react-icons/bs';
|
||||
import type { IconType } from 'react-icons';
|
||||
import { BiChip } from 'react-icons/bi';
|
||||
@@ -473,7 +472,13 @@ export function LocalProvidersTab() {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div
|
||||
className={classNames(
|
||||
'rounded-lg border bg-bolt-elements-background text-bolt-elements-textPrimary shadow-sm p-4',
|
||||
'hover:bg-bolt-elements-background-depth-2',
|
||||
'transition-all duration-200',
|
||||
)}
|
||||
>
|
||||
{/* Service Status Indicator - Move to top */}
|
||||
<div
|
||||
className={classNames(
|
||||
@@ -526,7 +531,6 @@ export function LocalProvidersTab() {
|
||||
<motion.div
|
||||
key={provider.name}
|
||||
className={classNames(
|
||||
settingsStyles.card,
|
||||
'bg-bolt-elements-background-depth-2',
|
||||
'hover:bg-bolt-elements-background-depth-3',
|
||||
'transition-all duration-200',
|
||||
@@ -728,9 +732,11 @@ export function LocalProvidersTab() {
|
||||
onClick={() => handleUpdateOllamaModel(model.name)}
|
||||
disabled={model.status === 'updating'}
|
||||
className={classNames(
|
||||
settingsStyles.button.base,
|
||||
settingsStyles.button.secondary,
|
||||
'hover:bg-purple-500/10 hover:text-purple-500',
|
||||
'rounded-md px-4 py-2 text-sm',
|
||||
'bg-purple-500 text-white',
|
||||
'hover:bg-purple-600',
|
||||
'dark:bg-purple-500 dark:hover:bg-purple-600',
|
||||
'transition-all duration-200',
|
||||
)}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
@@ -746,9 +752,11 @@ export function LocalProvidersTab() {
|
||||
}}
|
||||
disabled={model.status === 'updating'}
|
||||
className={classNames(
|
||||
settingsStyles.button.base,
|
||||
settingsStyles.button.secondary,
|
||||
'hover:bg-red-500/10 hover:text-red-500',
|
||||
'rounded-md px-4 py-2 text-sm',
|
||||
'bg-red-500 text-white',
|
||||
'hover:bg-red-600',
|
||||
'dark:bg-red-500 dark:hover:bg-red-600',
|
||||
'transition-all duration-200',
|
||||
)}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
@@ -839,10 +847,11 @@ export function LocalProvidersTab() {
|
||||
onClick={() => handleManualInstall(manualInstall.modelString)}
|
||||
disabled={!manualInstall.modelString || !!isInstallingModel}
|
||||
className={classNames(
|
||||
settingsStyles.button.base,
|
||||
settingsStyles.button.primary,
|
||||
'hover:bg-purple-500/10 hover:text-purple-500',
|
||||
'min-w-[120px] justify-center',
|
||||
'rounded-md px-4 py-2 text-sm',
|
||||
'bg-purple-500 text-white',
|
||||
'hover:bg-purple-600',
|
||||
'dark:bg-purple-500 dark:hover:bg-purple-600',
|
||||
'transition-all duration-200',
|
||||
)}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
@@ -867,10 +876,11 @@ export function LocalProvidersTab() {
|
||||
error('Installation cancelled');
|
||||
}}
|
||||
className={classNames(
|
||||
settingsStyles.button.base,
|
||||
settingsStyles.button.secondary,
|
||||
'hover:bg-red-500/10 hover:text-red-500',
|
||||
'min-w-[100px] justify-center',
|
||||
'rounded-md px-4 py-2 text-sm',
|
||||
'bg-red-500 text-white',
|
||||
'hover:bg-red-600',
|
||||
'dark:bg-red-500 dark:hover:bg-red-600',
|
||||
'transition-all duration-200',
|
||||
)}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
|
||||
@@ -2,8 +2,6 @@ import React, { useEffect, useState } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { toast } from 'react-toastify';
|
||||
import { classNames } from '~/utils/classNames';
|
||||
import { settingsStyles } from '~/components/settings/settings.styles';
|
||||
import { DialogTitle, DialogDescription } from '~/components/ui/Dialog';
|
||||
|
||||
interface OllamaModel {
|
||||
name: string;
|
||||
@@ -197,108 +195,135 @@ export default function OllamaModelUpdater() {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center p-4">
|
||||
<div className={settingsStyles['loading-spinner']} />
|
||||
<div
|
||||
className={classNames(
|
||||
'rounded-full border-4 border-t-4 border-b-4 border-purple-500',
|
||||
'h-16 w-16 animate-spin',
|
||||
)}
|
||||
/>
|
||||
<span className="ml-2 text-bolt-elements-textSecondary">Loading models...</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<DialogTitle>Ollama Model Manager</DialogTitle>
|
||||
<DialogDescription>Update your local Ollama models to their latest versions</DialogDescription>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="i-ph:arrows-clockwise text-purple-500" />
|
||||
<span className="text-sm text-bolt-elements-textPrimary">{models.length} models available</span>
|
||||
<div
|
||||
className={classNames(
|
||||
'rounded-lg border bg-bolt-elements-background text-bolt-elements-textPrimary shadow-sm p-4',
|
||||
'hover:bg-bolt-elements-background-depth-2',
|
||||
'transition-all duration-200',
|
||||
)}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<h2 className="text-2xl font-bold">Ollama Model Manager</h2>
|
||||
<p>Update your local Ollama models to their latest versions</p>
|
||||
</div>
|
||||
<motion.button
|
||||
onClick={handleBulkUpdate}
|
||||
disabled={isBulkUpdating}
|
||||
className={classNames(
|
||||
settingsStyles.button.base,
|
||||
settingsStyles.button.primary,
|
||||
'hover:bg-purple-500/10 hover:text-purple-500',
|
||||
'dark:hover:bg-purple-500/20 dark:hover:text-purple-500',
|
||||
)}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
>
|
||||
{isBulkUpdating ? (
|
||||
<>
|
||||
<div className={settingsStyles['loading-spinner']} />
|
||||
Updating All...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="i-ph:arrows-clockwise" />
|
||||
Update All Models
|
||||
</>
|
||||
)}
|
||||
</motion.button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
{models.map((model) => (
|
||||
<div
|
||||
key={model.name}
|
||||
className={classNames(
|
||||
'flex items-center justify-between p-3 rounded-lg',
|
||||
'bg-[#F8F8F8] dark:bg-[#1A1A1A]',
|
||||
'border border-[#E5E5E5] dark:border-[#333333]',
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="i-ph:cube text-purple-500" />
|
||||
<span className="text-sm text-bolt-elements-textPrimary">{model.name}</span>
|
||||
{model.status === 'updating' && <div className={settingsStyles['loading-spinner']} />}
|
||||
{model.status === 'updated' && <div className="i-ph:check-circle text-green-500" />}
|
||||
{model.status === 'error' && <div className="i-ph:x-circle text-red-500" />}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-xs text-bolt-elements-textSecondary">
|
||||
<span>Version: {model.digest.substring(0, 7)}</span>
|
||||
{model.status === 'updated' && model.newDigest && (
|
||||
<>
|
||||
<div className="i-ph:arrow-right w-3 h-3" />
|
||||
<span className="text-green-500">{model.newDigest.substring(0, 7)}</span>
|
||||
</>
|
||||
)}
|
||||
{model.progress && (
|
||||
<span className="ml-2">
|
||||
{model.progress.status}{' '}
|
||||
{model.progress.total > 0 && (
|
||||
<>({Math.round((model.progress.current / model.progress.total) * 100)}%)</>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
{model.details && (
|
||||
<span className="ml-2">
|
||||
({model.details.parameter_size}, {model.details.quantization_level})
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<motion.button
|
||||
onClick={() => handleSingleUpdate(model.name)}
|
||||
disabled={model.status === 'updating'}
|
||||
className={classNames(
|
||||
settingsStyles.button.base,
|
||||
settingsStyles.button.secondary,
|
||||
'hover:bg-purple-500/10 hover:text-purple-500',
|
||||
'dark:bg-[#1A1A1A] dark:hover:bg-purple-500/20 dark:text-bolt-elements-textPrimary dark:hover:text-purple-500',
|
||||
)}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
>
|
||||
<div className="i-ph:arrows-clockwise" />
|
||||
Update
|
||||
</motion.button>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="i-ph:arrows-clockwise text-purple-500" />
|
||||
<span className="text-sm text-bolt-elements-textPrimary">{models.length} models available</span>
|
||||
</div>
|
||||
))}
|
||||
<motion.button
|
||||
onClick={handleBulkUpdate}
|
||||
disabled={isBulkUpdating}
|
||||
className={classNames(
|
||||
'rounded-md px-4 py-2 text-sm',
|
||||
'bg-purple-500 text-white',
|
||||
'hover:bg-purple-600',
|
||||
'dark:bg-purple-500 dark:hover:bg-purple-600',
|
||||
'transition-all duration-200',
|
||||
)}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
>
|
||||
{isBulkUpdating ? (
|
||||
<>
|
||||
<div
|
||||
className={classNames(
|
||||
'rounded-full border-4 border-t-4 border-b-4 border-purple-500',
|
||||
'h-4 w-4 animate-spin mr-2',
|
||||
)}
|
||||
/>
|
||||
Updating All...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="i-ph:arrows-clockwise" />
|
||||
Update All Models
|
||||
</>
|
||||
)}
|
||||
</motion.button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
{models.map((model) => (
|
||||
<div
|
||||
key={model.name}
|
||||
className={classNames(
|
||||
'flex items-center justify-between p-3 rounded-lg',
|
||||
'bg-[#F8F8F8] dark:bg-[#1A1A1A]',
|
||||
'border border-[#E5E5E5] dark:border-[#333333]',
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="i-ph:cube text-purple-500" />
|
||||
<span className="text-sm text-bolt-elements-textPrimary">{model.name}</span>
|
||||
{model.status === 'updating' && (
|
||||
<div
|
||||
className={classNames(
|
||||
'rounded-full border-4 border-t-4 border-b-4 border-purple-500',
|
||||
'h-4 w-4 animate-spin',
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{model.status === 'updated' && <div className="i-ph:check-circle text-green-500" />}
|
||||
{model.status === 'error' && <div className="i-ph:x-circle text-red-500" />}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-xs text-bolt-elements-textSecondary">
|
||||
<span>Version: {model.digest.substring(0, 7)}</span>
|
||||
{model.status === 'updated' && model.newDigest && (
|
||||
<>
|
||||
<div className="i-ph:arrow-right w-3 h-3" />
|
||||
<span className="text-green-500">{model.newDigest.substring(0, 7)}</span>
|
||||
</>
|
||||
)}
|
||||
{model.progress && (
|
||||
<span className="ml-2">
|
||||
{model.progress.status}{' '}
|
||||
{model.progress.total > 0 && (
|
||||
<>({Math.round((model.progress.current / model.progress.total) * 100)}%)</>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
{model.details && (
|
||||
<span className="ml-2">
|
||||
({model.details.parameter_size}, {model.details.quantization_level})
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<motion.button
|
||||
onClick={() => handleSingleUpdate(model.name)}
|
||||
disabled={model.status === 'updating'}
|
||||
className={classNames(
|
||||
'rounded-md px-4 py-2 text-sm',
|
||||
'bg-purple-500 text-white',
|
||||
'hover:bg-purple-600',
|
||||
'dark:bg-purple-500 dark:hover:bg-purple-600',
|
||||
'transition-all duration-200',
|
||||
)}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
>
|
||||
<div className="i-ph:arrows-clockwise" />
|
||||
Update
|
||||
</motion.button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import type { ServiceStatus } from './types';
|
||||
import { ProviderStatusCheckerFactory } from './provider-factory';
|
||||
|
||||
export default function ServiceStatusTab() {
|
||||
const [serviceStatuses, setServiceStatuses] = useState<ServiceStatus[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const checkAllProviders = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
const providers = ProviderStatusCheckerFactory.getProviderNames();
|
||||
const statuses: ServiceStatus[] = [];
|
||||
|
||||
for (const provider of providers) {
|
||||
try {
|
||||
const checker = ProviderStatusCheckerFactory.getChecker(provider);
|
||||
const result = await checker.checkStatus();
|
||||
|
||||
statuses.push({
|
||||
provider,
|
||||
...result,
|
||||
lastChecked: new Date().toISOString(),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(`Error checking ${provider} status:`, err);
|
||||
statuses.push({
|
||||
provider,
|
||||
status: 'degraded',
|
||||
message: 'Unable to check service status',
|
||||
incidents: ['Error checking service status'],
|
||||
lastChecked: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setServiceStatuses(statuses);
|
||||
} catch (err) {
|
||||
console.error('Error checking provider statuses:', err);
|
||||
setError('Failed to check service statuses');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
checkAllProviders();
|
||||
|
||||
// Set up periodic checks every 5 minutes
|
||||
const interval = setInterval(checkAllProviders, 5 * 60 * 1000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
const getStatusColor = (status: ServiceStatus['status']) => {
|
||||
switch (status) {
|
||||
case 'operational':
|
||||
return 'text-green-500 dark:text-green-400';
|
||||
case 'degraded':
|
||||
return 'text-yellow-500 dark:text-yellow-400';
|
||||
case 'down':
|
||||
return 'text-red-500 dark:text-red-400';
|
||||
default:
|
||||
return 'text-gray-500 dark:text-gray-400';
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusIcon = (status: ServiceStatus['status']) => {
|
||||
switch (status) {
|
||||
case 'operational':
|
||||
return 'i-ph:check-circle';
|
||||
case 'degraded':
|
||||
return 'i-ph:warning';
|
||||
case 'down':
|
||||
return 'i-ph:x-circle';
|
||||
default:
|
||||
return 'i-ph:question';
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<div className="animate-spin i-ph:circle-notch w-8 h-8 text-purple-500" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center h-full text-red-500 dark:text-red-400">
|
||||
<div className="i-ph:warning w-8 h-8 mb-2" />
|
||||
<p>{error}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
{serviceStatuses.map((service) => (
|
||||
<div
|
||||
key={service.provider}
|
||||
className="p-4 bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700"
|
||||
>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">{service.provider}</h3>
|
||||
<div className={`flex items-center ${getStatusColor(service.status)}`}>
|
||||
<div className={`${getStatusIcon(service.status)} w-5 h-5 mr-2`} />
|
||||
<span className="capitalize">{service.status}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-gray-600 dark:text-gray-300 mb-2">{service.message}</p>
|
||||
{service.incidents && service.incidents.length > 0 && (
|
||||
<div className="mt-2">
|
||||
<h4 className="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-1">Recent Incidents:</h4>
|
||||
<ul className="text-sm text-gray-600 dark:text-gray-400 space-y-1">
|
||||
{service.incidents.map((incident, index) => (
|
||||
<li key={index}>{incident}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
<div className="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
Last checked: {new Date(service.lastChecked).toLocaleString()}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,122 +1,91 @@
|
||||
import type { ProviderName, ProviderConfig, StatusCheckResult } from './types';
|
||||
import { OpenAIStatusChecker } from './providers/openai';
|
||||
import { BaseProviderChecker } from './base-provider';
|
||||
|
||||
// Import other provider implementations as they are created
|
||||
import { AmazonBedrockStatusChecker } from './providers/amazon-bedrock';
|
||||
import { CohereStatusChecker } from './providers/cohere';
|
||||
import { DeepseekStatusChecker } from './providers/deepseek';
|
||||
import { GoogleStatusChecker } from './providers/google';
|
||||
import { GroqStatusChecker } from './providers/groq';
|
||||
import { HuggingFaceStatusChecker } from './providers/huggingface';
|
||||
import { HyperbolicStatusChecker } from './providers/hyperbolic';
|
||||
import { MistralStatusChecker } from './providers/mistral';
|
||||
import { OpenRouterStatusChecker } from './providers/openrouter';
|
||||
import { PerplexityStatusChecker } from './providers/perplexity';
|
||||
import { TogetherStatusChecker } from './providers/together';
|
||||
import { XAIStatusChecker } from './providers/xai';
|
||||
|
||||
export class ProviderStatusCheckerFactory {
|
||||
private static _providerConfigs: Record<ProviderName, ProviderConfig> = {
|
||||
OpenAI: {
|
||||
statusUrl: 'https://status.openai.com/',
|
||||
apiUrl: 'https://api.openai.com/v1/models',
|
||||
headers: {
|
||||
Authorization: 'Bearer $OPENAI_API_KEY',
|
||||
},
|
||||
testModel: 'gpt-3.5-turbo',
|
||||
},
|
||||
Anthropic: {
|
||||
statusUrl: 'https://status.anthropic.com/',
|
||||
apiUrl: 'https://api.anthropic.com/v1/messages',
|
||||
headers: {
|
||||
'x-api-key': '$ANTHROPIC_API_KEY',
|
||||
'anthropic-version': '2024-02-29',
|
||||
},
|
||||
testModel: 'claude-3-sonnet-20240229',
|
||||
},
|
||||
AmazonBedrock: {
|
||||
statusUrl: 'https://health.aws.amazon.com/health/status',
|
||||
apiUrl: 'https://bedrock.us-east-1.amazonaws.com/models',
|
||||
headers: {
|
||||
Authorization: 'Bearer $AWS_BEDROCK_CONFIG',
|
||||
},
|
||||
headers: {},
|
||||
testModel: 'anthropic.claude-3-sonnet-20240229-v1:0',
|
||||
},
|
||||
Cohere: {
|
||||
statusUrl: 'https://status.cohere.com/',
|
||||
apiUrl: 'https://api.cohere.ai/v1/models',
|
||||
headers: {
|
||||
Authorization: 'Bearer $COHERE_API_KEY',
|
||||
},
|
||||
headers: {},
|
||||
testModel: 'command',
|
||||
},
|
||||
Deepseek: {
|
||||
statusUrl: 'https://status.deepseek.com/',
|
||||
apiUrl: 'https://api.deepseek.com/v1/models',
|
||||
headers: {
|
||||
Authorization: 'Bearer $DEEPSEEK_API_KEY',
|
||||
},
|
||||
headers: {},
|
||||
testModel: 'deepseek-chat',
|
||||
},
|
||||
Google: {
|
||||
statusUrl: 'https://status.cloud.google.com/',
|
||||
apiUrl: 'https://generativelanguage.googleapis.com/v1/models',
|
||||
headers: {
|
||||
'x-goog-api-key': '$GOOGLE_API_KEY',
|
||||
},
|
||||
headers: {},
|
||||
testModel: 'gemini-pro',
|
||||
},
|
||||
Groq: {
|
||||
statusUrl: 'https://groqstatus.com/',
|
||||
apiUrl: 'https://api.groq.com/v1/models',
|
||||
headers: {
|
||||
Authorization: 'Bearer $GROQ_API_KEY',
|
||||
},
|
||||
headers: {},
|
||||
testModel: 'mixtral-8x7b-32768',
|
||||
},
|
||||
HuggingFace: {
|
||||
statusUrl: 'https://status.huggingface.co/',
|
||||
apiUrl: 'https://api-inference.huggingface.co/models',
|
||||
headers: {
|
||||
Authorization: 'Bearer $HUGGINGFACE_API_KEY',
|
||||
},
|
||||
headers: {},
|
||||
testModel: 'mistralai/Mixtral-8x7B-Instruct-v0.1',
|
||||
},
|
||||
Hyperbolic: {
|
||||
statusUrl: 'https://status.hyperbolic.ai/',
|
||||
apiUrl: 'https://api.hyperbolic.ai/v1/models',
|
||||
headers: {
|
||||
Authorization: 'Bearer $HYPERBOLIC_API_KEY',
|
||||
},
|
||||
headers: {},
|
||||
testModel: 'hyperbolic-1',
|
||||
},
|
||||
Mistral: {
|
||||
statusUrl: 'https://status.mistral.ai/',
|
||||
apiUrl: 'https://api.mistral.ai/v1/models',
|
||||
headers: {
|
||||
Authorization: 'Bearer $MISTRAL_API_KEY',
|
||||
},
|
||||
headers: {},
|
||||
testModel: 'mistral-tiny',
|
||||
},
|
||||
OpenRouter: {
|
||||
statusUrl: 'https://status.openrouter.ai/',
|
||||
apiUrl: 'https://openrouter.ai/api/v1/models',
|
||||
headers: {
|
||||
Authorization: 'Bearer $OPEN_ROUTER_API_KEY',
|
||||
},
|
||||
headers: {},
|
||||
testModel: 'anthropic/claude-3-sonnet',
|
||||
},
|
||||
Perplexity: {
|
||||
statusUrl: 'https://status.perplexity.com/',
|
||||
apiUrl: 'https://api.perplexity.ai/v1/models',
|
||||
headers: {
|
||||
Authorization: 'Bearer $PERPLEXITY_API_KEY',
|
||||
},
|
||||
headers: {},
|
||||
testModel: 'pplx-7b-chat',
|
||||
},
|
||||
Together: {
|
||||
statusUrl: 'https://status.together.ai/',
|
||||
apiUrl: 'https://api.together.xyz/v1/models',
|
||||
headers: {
|
||||
Authorization: 'Bearer $TOGETHER_API_KEY',
|
||||
},
|
||||
headers: {},
|
||||
testModel: 'mistralai/Mixtral-8x7B-Instruct-v0.1',
|
||||
},
|
||||
XAI: {
|
||||
statusUrl: 'https://status.x.ai/',
|
||||
apiUrl: 'https://api.x.ai/v1/models',
|
||||
headers: {
|
||||
Authorization: 'Bearer $XAI_API_KEY',
|
||||
},
|
||||
headers: {},
|
||||
testModel: 'grok-1',
|
||||
},
|
||||
};
|
||||
@@ -128,12 +97,31 @@ export class ProviderStatusCheckerFactory {
|
||||
throw new Error(`No configuration found for provider: ${provider}`);
|
||||
}
|
||||
|
||||
// Return specific provider implementation or fallback to base implementation
|
||||
switch (provider) {
|
||||
case 'OpenAI':
|
||||
return new OpenAIStatusChecker(config);
|
||||
|
||||
// Add other provider implementations as they are created
|
||||
case 'AmazonBedrock':
|
||||
return new AmazonBedrockStatusChecker(config);
|
||||
case 'Cohere':
|
||||
return new CohereStatusChecker(config);
|
||||
case 'Deepseek':
|
||||
return new DeepseekStatusChecker(config);
|
||||
case 'Google':
|
||||
return new GoogleStatusChecker(config);
|
||||
case 'Groq':
|
||||
return new GroqStatusChecker(config);
|
||||
case 'HuggingFace':
|
||||
return new HuggingFaceStatusChecker(config);
|
||||
case 'Hyperbolic':
|
||||
return new HyperbolicStatusChecker(config);
|
||||
case 'Mistral':
|
||||
return new MistralStatusChecker(config);
|
||||
case 'OpenRouter':
|
||||
return new OpenRouterStatusChecker(config);
|
||||
case 'Perplexity':
|
||||
return new PerplexityStatusChecker(config);
|
||||
case 'Together':
|
||||
return new TogetherStatusChecker(config);
|
||||
case 'XAI':
|
||||
return new XAIStatusChecker(config);
|
||||
default:
|
||||
return new (class extends BaseProviderChecker {
|
||||
async checkStatus(): Promise<StatusCheckResult> {
|
||||
@@ -154,7 +142,13 @@ export class ProviderStatusCheckerFactory {
|
||||
return Object.keys(this._providerConfigs) as ProviderName[];
|
||||
}
|
||||
|
||||
static getProviderConfig(provider: ProviderName): ProviderConfig | undefined {
|
||||
return this._providerConfigs[provider];
|
||||
static getProviderConfig(provider: ProviderName): ProviderConfig {
|
||||
const config = this._providerConfigs[provider];
|
||||
|
||||
if (!config) {
|
||||
throw new Error(`Unknown provider: ${provider}`);
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import { BaseProviderChecker } from '~/components/settings/providers/service-status/base-provider';
|
||||
import type { StatusCheckResult } from '~/components/settings/providers/service-status/types';
|
||||
|
||||
export class AmazonBedrockStatusChecker extends BaseProviderChecker {
|
||||
async checkStatus(): Promise<StatusCheckResult> {
|
||||
try {
|
||||
// Check AWS health status page
|
||||
const statusPageResponse = await fetch('https://health.aws.amazon.com/health/status');
|
||||
const text = await statusPageResponse.text();
|
||||
|
||||
// Check for Bedrock and general AWS status
|
||||
const hasBedrockIssues =
|
||||
text.includes('Amazon Bedrock') &&
|
||||
(text.includes('Service is experiencing elevated error rates') ||
|
||||
text.includes('Service disruption') ||
|
||||
text.includes('Degraded Service'));
|
||||
|
||||
const hasGeneralIssues = text.includes('Service disruption') || text.includes('Multiple services affected');
|
||||
|
||||
// Extract incidents
|
||||
const incidents: string[] = [];
|
||||
const incidentMatches = text.matchAll(/(\d{4}-\d{2}-\d{2})\s+(.*?)\s+Impact:(.*?)(?=\n|$)/g);
|
||||
|
||||
for (const match of incidentMatches) {
|
||||
const [, date, title, impact] = match;
|
||||
|
||||
if (title.includes('Bedrock') || title.includes('AWS')) {
|
||||
incidents.push(`${date}: ${title.trim()} - Impact: ${impact.trim()}`);
|
||||
}
|
||||
}
|
||||
|
||||
let status: StatusCheckResult['status'] = 'operational';
|
||||
let message = 'All services operational';
|
||||
|
||||
if (hasBedrockIssues) {
|
||||
status = 'degraded';
|
||||
message = 'Amazon Bedrock service issues reported';
|
||||
} else if (hasGeneralIssues) {
|
||||
status = 'degraded';
|
||||
message = 'AWS experiencing general issues';
|
||||
}
|
||||
|
||||
// If status page check fails, fallback to endpoint check
|
||||
if (!statusPageResponse.ok) {
|
||||
const endpointStatus = await this.checkEndpoint('https://health.aws.amazon.com/health/status');
|
||||
const apiEndpoint = 'https://bedrock.us-east-1.amazonaws.com/models';
|
||||
const apiStatus = await this.checkEndpoint(apiEndpoint);
|
||||
|
||||
return {
|
||||
status: endpointStatus === 'reachable' && apiStatus === 'reachable' ? 'operational' : 'degraded',
|
||||
message: `Status page: ${endpointStatus}, API: ${apiStatus}`,
|
||||
incidents: ['Note: Limited status information due to CORS restrictions'],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status,
|
||||
message,
|
||||
incidents: incidents.slice(0, 5),
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error checking Amazon Bedrock status:', error);
|
||||
|
||||
// Fallback to basic endpoint check
|
||||
const endpointStatus = await this.checkEndpoint('https://health.aws.amazon.com/health/status');
|
||||
const apiEndpoint = 'https://bedrock.us-east-1.amazonaws.com/models';
|
||||
const apiStatus = await this.checkEndpoint(apiEndpoint);
|
||||
|
||||
return {
|
||||
status: endpointStatus === 'reachable' && apiStatus === 'reachable' ? 'operational' : 'degraded',
|
||||
message: `Status page: ${endpointStatus}, API: ${apiStatus}`,
|
||||
incidents: ['Note: Limited status information due to CORS restrictions'],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import { BaseProviderChecker } from '~/components/settings/providers/service-status/base-provider';
|
||||
import type { StatusCheckResult } from '~/components/settings/providers/service-status/types';
|
||||
|
||||
export class AnthropicStatusChecker extends BaseProviderChecker {
|
||||
async checkStatus(): Promise<StatusCheckResult> {
|
||||
try {
|
||||
// Check status page
|
||||
const statusPageResponse = await fetch('https://status.anthropic.com/');
|
||||
const text = await statusPageResponse.text();
|
||||
|
||||
// Check for specific Anthropic status indicators
|
||||
const isOperational = text.includes('All Systems Operational');
|
||||
const hasDegradedPerformance = text.includes('Degraded Performance');
|
||||
const hasPartialOutage = text.includes('Partial Outage');
|
||||
const hasMajorOutage = text.includes('Major Outage');
|
||||
|
||||
// Extract incidents
|
||||
const incidents: string[] = [];
|
||||
const incidentSection = text.match(/Past Incidents(.*?)(?=\n\n)/s);
|
||||
|
||||
if (incidentSection) {
|
||||
const incidentLines = incidentSection[1]
|
||||
.split('\n')
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => line && line.includes('202')); // Only get dated incidents
|
||||
|
||||
incidents.push(...incidentLines.slice(0, 5));
|
||||
}
|
||||
|
||||
let status: StatusCheckResult['status'] = 'operational';
|
||||
let message = 'All systems operational';
|
||||
|
||||
if (hasMajorOutage) {
|
||||
status = 'down';
|
||||
message = 'Major service outage';
|
||||
} else if (hasPartialOutage) {
|
||||
status = 'down';
|
||||
message = 'Partial service outage';
|
||||
} else if (hasDegradedPerformance) {
|
||||
status = 'degraded';
|
||||
message = 'Service experiencing degraded performance';
|
||||
} else if (!isOperational) {
|
||||
status = 'degraded';
|
||||
message = 'Service status unknown';
|
||||
}
|
||||
|
||||
// If status page check fails, fallback to endpoint check
|
||||
if (!statusPageResponse.ok) {
|
||||
const endpointStatus = await this.checkEndpoint('https://status.anthropic.com/');
|
||||
const apiEndpoint = 'https://api.anthropic.com/v1/messages';
|
||||
const apiStatus = await this.checkEndpoint(apiEndpoint);
|
||||
|
||||
return {
|
||||
status: endpointStatus === 'reachable' && apiStatus === 'reachable' ? 'operational' : 'degraded',
|
||||
message: `Status page: ${endpointStatus}, API: ${apiStatus}`,
|
||||
incidents: ['Note: Limited status information due to CORS restrictions'],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status,
|
||||
message,
|
||||
incidents,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error checking Anthropic status:', error);
|
||||
|
||||
// Fallback to basic endpoint check
|
||||
const endpointStatus = await this.checkEndpoint('https://status.anthropic.com/');
|
||||
const apiEndpoint = 'https://api.anthropic.com/v1/messages';
|
||||
const apiStatus = await this.checkEndpoint(apiEndpoint);
|
||||
|
||||
return {
|
||||
status: endpointStatus === 'reachable' && apiStatus === 'reachable' ? 'operational' : 'degraded',
|
||||
message: `Status page: ${endpointStatus}, API: ${apiStatus}`,
|
||||
incidents: ['Note: Limited status information due to CORS restrictions'],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import { BaseProviderChecker } from '~/components/settings/providers/service-status/base-provider';
|
||||
import type { StatusCheckResult } from '~/components/settings/providers/service-status/types';
|
||||
|
||||
export class CohereStatusChecker extends BaseProviderChecker {
|
||||
async checkStatus(): Promise<StatusCheckResult> {
|
||||
try {
|
||||
// Check status page
|
||||
const statusPageResponse = await fetch('https://status.cohere.com/');
|
||||
const text = await statusPageResponse.text();
|
||||
|
||||
// Check for specific Cohere status indicators
|
||||
const isOperational = text.includes('All Systems Operational');
|
||||
const hasIncidents = text.includes('Active Incidents');
|
||||
const hasDegradation = text.includes('Degraded Performance');
|
||||
const hasOutage = text.includes('Service Outage');
|
||||
|
||||
// Extract incidents
|
||||
const incidents: string[] = [];
|
||||
const incidentSection = text.match(/Past Incidents(.*?)(?=\n\n)/s);
|
||||
|
||||
if (incidentSection) {
|
||||
const incidentLines = incidentSection[1]
|
||||
.split('\n')
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => line && line.includes('202')); // Only get dated incidents
|
||||
|
||||
incidents.push(...incidentLines.slice(0, 5));
|
||||
}
|
||||
|
||||
// Check specific services
|
||||
const services = {
|
||||
api: {
|
||||
operational: text.includes('API Service') && text.includes('Operational'),
|
||||
degraded: text.includes('API Service') && text.includes('Degraded Performance'),
|
||||
outage: text.includes('API Service') && text.includes('Service Outage'),
|
||||
},
|
||||
generation: {
|
||||
operational: text.includes('Generation Service') && text.includes('Operational'),
|
||||
degraded: text.includes('Generation Service') && text.includes('Degraded Performance'),
|
||||
outage: text.includes('Generation Service') && text.includes('Service Outage'),
|
||||
},
|
||||
};
|
||||
|
||||
let status: StatusCheckResult['status'] = 'operational';
|
||||
let message = 'All systems operational';
|
||||
|
||||
if (services.api.outage || services.generation.outage || hasOutage) {
|
||||
status = 'down';
|
||||
message = 'Service outage detected';
|
||||
} else if (services.api.degraded || services.generation.degraded || hasDegradation || hasIncidents) {
|
||||
status = 'degraded';
|
||||
message = 'Service experiencing issues';
|
||||
} else if (!isOperational) {
|
||||
status = 'degraded';
|
||||
message = 'Service status unknown';
|
||||
}
|
||||
|
||||
// If status page check fails, fallback to endpoint check
|
||||
if (!statusPageResponse.ok) {
|
||||
const endpointStatus = await this.checkEndpoint('https://status.cohere.com/');
|
||||
const apiEndpoint = 'https://api.cohere.ai/v1/models';
|
||||
const apiStatus = await this.checkEndpoint(apiEndpoint);
|
||||
|
||||
return {
|
||||
status: endpointStatus === 'reachable' && apiStatus === 'reachable' ? 'operational' : 'degraded',
|
||||
message: `Status page: ${endpointStatus}, API: ${apiStatus}`,
|
||||
incidents: ['Note: Limited status information due to CORS restrictions'],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status,
|
||||
message,
|
||||
incidents,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error checking Cohere status:', error);
|
||||
|
||||
// Fallback to basic endpoint check
|
||||
const endpointStatus = await this.checkEndpoint('https://status.cohere.com/');
|
||||
const apiEndpoint = 'https://api.cohere.ai/v1/models';
|
||||
const apiStatus = await this.checkEndpoint(apiEndpoint);
|
||||
|
||||
return {
|
||||
status: endpointStatus === 'reachable' && apiStatus === 'reachable' ? 'operational' : 'degraded',
|
||||
message: `Status page: ${endpointStatus}, API: ${apiStatus}`,
|
||||
incidents: ['Note: Limited status information due to CORS restrictions'],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { BaseProviderChecker } from '~/components/settings/providers/service-status/base-provider';
|
||||
import type { StatusCheckResult } from '~/components/settings/providers/service-status/types';
|
||||
|
||||
export class DeepseekStatusChecker extends BaseProviderChecker {
|
||||
async checkStatus(): Promise<StatusCheckResult> {
|
||||
try {
|
||||
/*
|
||||
* Check status page - Note: Deepseek doesn't have a public status page yet
|
||||
* so we'll check their API endpoint directly
|
||||
*/
|
||||
const apiEndpoint = 'https://api.deepseek.com/v1/models';
|
||||
const apiStatus = await this.checkEndpoint(apiEndpoint);
|
||||
|
||||
// Check their website as a secondary indicator
|
||||
const websiteStatus = await this.checkEndpoint('https://deepseek.com');
|
||||
|
||||
let status: StatusCheckResult['status'] = 'operational';
|
||||
let message = 'All systems operational';
|
||||
|
||||
if (apiStatus !== 'reachable' || websiteStatus !== 'reachable') {
|
||||
status = apiStatus !== 'reachable' ? 'down' : 'degraded';
|
||||
message = apiStatus !== 'reachable' ? 'API appears to be down' : 'Service may be experiencing issues';
|
||||
}
|
||||
|
||||
return {
|
||||
status,
|
||||
message,
|
||||
incidents: [], // No public incident tracking available yet
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error checking Deepseek status:', error);
|
||||
|
||||
return {
|
||||
status: 'degraded',
|
||||
message: 'Unable to determine service status',
|
||||
incidents: ['Note: Limited status information available'],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import { BaseProviderChecker } from '~/components/settings/providers/service-status/base-provider';
|
||||
import type { StatusCheckResult } from '~/components/settings/providers/service-status/types';
|
||||
|
||||
export class GoogleStatusChecker extends BaseProviderChecker {
|
||||
async checkStatus(): Promise<StatusCheckResult> {
|
||||
try {
|
||||
// Check status page
|
||||
const statusPageResponse = await fetch('https://status.cloud.google.com/');
|
||||
const text = await statusPageResponse.text();
|
||||
|
||||
// Check for Vertex AI and general cloud status
|
||||
const hasVertexAIIssues =
|
||||
text.includes('Vertex AI') &&
|
||||
(text.includes('Incident') ||
|
||||
text.includes('Disruption') ||
|
||||
text.includes('Outage') ||
|
||||
text.includes('degraded'));
|
||||
|
||||
const hasGeneralIssues = text.includes('Major Incidents') || text.includes('Service Disruption');
|
||||
|
||||
// Extract incidents
|
||||
const incidents: string[] = [];
|
||||
const incidentMatches = text.matchAll(/(\d{4}-\d{2}-\d{2})\s+(.*?)\s+Impact:(.*?)(?=\n|$)/g);
|
||||
|
||||
for (const match of incidentMatches) {
|
||||
const [, date, title, impact] = match;
|
||||
|
||||
if (title.includes('Vertex AI') || title.includes('Cloud')) {
|
||||
incidents.push(`${date}: ${title.trim()} - Impact: ${impact.trim()}`);
|
||||
}
|
||||
}
|
||||
|
||||
let status: StatusCheckResult['status'] = 'operational';
|
||||
let message = 'All services operational';
|
||||
|
||||
if (hasVertexAIIssues) {
|
||||
status = 'degraded';
|
||||
message = 'Vertex AI service issues reported';
|
||||
} else if (hasGeneralIssues) {
|
||||
status = 'degraded';
|
||||
message = 'Google Cloud experiencing issues';
|
||||
}
|
||||
|
||||
// If status page check fails, fallback to endpoint check
|
||||
if (!statusPageResponse.ok) {
|
||||
const endpointStatus = await this.checkEndpoint('https://status.cloud.google.com/');
|
||||
const apiEndpoint = 'https://generativelanguage.googleapis.com/v1/models';
|
||||
const apiStatus = await this.checkEndpoint(apiEndpoint);
|
||||
|
||||
return {
|
||||
status: endpointStatus === 'reachable' && apiStatus === 'reachable' ? 'operational' : 'degraded',
|
||||
message: `Status page: ${endpointStatus}, API: ${apiStatus}`,
|
||||
incidents: ['Note: Limited status information due to CORS restrictions'],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status,
|
||||
message,
|
||||
incidents: incidents.slice(0, 5),
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error checking Google status:', error);
|
||||
|
||||
// Fallback to basic endpoint check
|
||||
const endpointStatus = await this.checkEndpoint('https://status.cloud.google.com/');
|
||||
const apiEndpoint = 'https://generativelanguage.googleapis.com/v1/models';
|
||||
const apiStatus = await this.checkEndpoint(apiEndpoint);
|
||||
|
||||
return {
|
||||
status: endpointStatus === 'reachable' && apiStatus === 'reachable' ? 'operational' : 'degraded',
|
||||
message: `Status page: ${endpointStatus}, API: ${apiStatus}`,
|
||||
incidents: ['Note: Limited status information due to CORS restrictions'],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import { BaseProviderChecker } from '~/components/settings/providers/service-status/base-provider';
|
||||
import type { StatusCheckResult } from '~/components/settings/providers/service-status/types';
|
||||
|
||||
export class GroqStatusChecker extends BaseProviderChecker {
|
||||
async checkStatus(): Promise<StatusCheckResult> {
|
||||
try {
|
||||
// Check status page
|
||||
const statusPageResponse = await fetch('https://groqstatus.com/');
|
||||
const text = await statusPageResponse.text();
|
||||
|
||||
const isOperational = text.includes('All Systems Operational');
|
||||
const hasIncidents = text.includes('Active Incidents');
|
||||
const hasDegradation = text.includes('Degraded Performance');
|
||||
const hasOutage = text.includes('Service Outage');
|
||||
|
||||
// Extract incidents
|
||||
const incidents: string[] = [];
|
||||
const incidentMatches = text.matchAll(/(\d{4}-\d{2}-\d{2})\s+(.*?)\s+Status:(.*?)(?=\n|$)/g);
|
||||
|
||||
for (const match of incidentMatches) {
|
||||
const [, date, title, status] = match;
|
||||
incidents.push(`${date}: ${title.trim()} - ${status.trim()}`);
|
||||
}
|
||||
|
||||
let status: StatusCheckResult['status'] = 'operational';
|
||||
let message = 'All systems operational';
|
||||
|
||||
if (hasOutage) {
|
||||
status = 'down';
|
||||
message = 'Service outage detected';
|
||||
} else if (hasDegradation || hasIncidents) {
|
||||
status = 'degraded';
|
||||
message = 'Service experiencing issues';
|
||||
} else if (!isOperational) {
|
||||
status = 'degraded';
|
||||
message = 'Service status unknown';
|
||||
}
|
||||
|
||||
// If status page check fails, fallback to endpoint check
|
||||
if (!statusPageResponse.ok) {
|
||||
const endpointStatus = await this.checkEndpoint('https://groqstatus.com/');
|
||||
const apiEndpoint = 'https://api.groq.com/v1/models';
|
||||
const apiStatus = await this.checkEndpoint(apiEndpoint);
|
||||
|
||||
return {
|
||||
status: endpointStatus === 'reachable' && apiStatus === 'reachable' ? 'operational' : 'degraded',
|
||||
message: `Status page: ${endpointStatus}, API: ${apiStatus}`,
|
||||
incidents: ['Note: Limited status information due to CORS restrictions'],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status,
|
||||
message,
|
||||
incidents: incidents.slice(0, 5),
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error checking Groq status:', error);
|
||||
|
||||
// Fallback to basic endpoint check
|
||||
const endpointStatus = await this.checkEndpoint('https://groqstatus.com/');
|
||||
const apiEndpoint = 'https://api.groq.com/v1/models';
|
||||
const apiStatus = await this.checkEndpoint(apiEndpoint);
|
||||
|
||||
return {
|
||||
status: endpointStatus === 'reachable' && apiStatus === 'reachable' ? 'operational' : 'degraded',
|
||||
message: `Status page: ${endpointStatus}, API: ${apiStatus}`,
|
||||
incidents: ['Note: Limited status information due to CORS restrictions'],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import { BaseProviderChecker } from '~/components/settings/providers/service-status/base-provider';
|
||||
import type { StatusCheckResult } from '~/components/settings/providers/service-status/types';
|
||||
|
||||
export class HuggingFaceStatusChecker extends BaseProviderChecker {
|
||||
async checkStatus(): Promise<StatusCheckResult> {
|
||||
try {
|
||||
// Check status page
|
||||
const statusPageResponse = await fetch('https://status.huggingface.co/');
|
||||
const text = await statusPageResponse.text();
|
||||
|
||||
// Check for "All services are online" message
|
||||
const allServicesOnline = text.includes('All services are online');
|
||||
|
||||
// Get last update time
|
||||
const lastUpdateMatch = text.match(/Last updated on (.*?)(EST|PST|GMT)/);
|
||||
const lastUpdate = lastUpdateMatch ? `${lastUpdateMatch[1]}${lastUpdateMatch[2]}` : '';
|
||||
|
||||
// Check individual services and their uptime percentages
|
||||
const services = {
|
||||
'Huggingface Hub': {
|
||||
operational: text.includes('Huggingface Hub') && text.includes('Operational'),
|
||||
uptime: text.match(/Huggingface Hub[\s\S]*?(\d+\.\d+)%\s*uptime/)?.[1],
|
||||
},
|
||||
'Git Hosting and Serving': {
|
||||
operational: text.includes('Git Hosting and Serving') && text.includes('Operational'),
|
||||
uptime: text.match(/Git Hosting and Serving[\s\S]*?(\d+\.\d+)%\s*uptime/)?.[1],
|
||||
},
|
||||
'Inference API': {
|
||||
operational: text.includes('Inference API') && text.includes('Operational'),
|
||||
uptime: text.match(/Inference API[\s\S]*?(\d+\.\d+)%\s*uptime/)?.[1],
|
||||
},
|
||||
'HF Endpoints': {
|
||||
operational: text.includes('HF Endpoints') && text.includes('Operational'),
|
||||
uptime: text.match(/HF Endpoints[\s\S]*?(\d+\.\d+)%\s*uptime/)?.[1],
|
||||
},
|
||||
Spaces: {
|
||||
operational: text.includes('Spaces') && text.includes('Operational'),
|
||||
uptime: text.match(/Spaces[\s\S]*?(\d+\.\d+)%\s*uptime/)?.[1],
|
||||
},
|
||||
};
|
||||
|
||||
// Create service status messages with uptime
|
||||
const serviceMessages = Object.entries(services).map(([name, info]) => {
|
||||
if (info.uptime) {
|
||||
return `${name}: ${info.uptime}% uptime`;
|
||||
}
|
||||
|
||||
return `${name}: ${info.operational ? 'Operational' : 'Issues detected'}`;
|
||||
});
|
||||
|
||||
// Determine overall status
|
||||
let status: StatusCheckResult['status'] = 'operational';
|
||||
let message = allServicesOnline
|
||||
? `All services are online (Last updated on ${lastUpdate})`
|
||||
: 'Checking individual services';
|
||||
|
||||
// Only mark as degraded if we explicitly detect issues
|
||||
const hasIssues = Object.values(services).some((service) => !service.operational);
|
||||
|
||||
if (hasIssues) {
|
||||
status = 'degraded';
|
||||
message = `Service issues detected (Last updated on ${lastUpdate})`;
|
||||
}
|
||||
|
||||
// If status page check fails, fallback to endpoint check
|
||||
if (!statusPageResponse.ok) {
|
||||
const endpointStatus = await this.checkEndpoint('https://status.huggingface.co/');
|
||||
const apiEndpoint = 'https://api-inference.huggingface.co/models';
|
||||
const apiStatus = await this.checkEndpoint(apiEndpoint);
|
||||
|
||||
return {
|
||||
status: endpointStatus === 'reachable' && apiStatus === 'reachable' ? 'operational' : 'degraded',
|
||||
message: `Status page: ${endpointStatus}, API: ${apiStatus}`,
|
||||
incidents: ['Note: Limited status information due to CORS restrictions'],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status,
|
||||
message,
|
||||
incidents: serviceMessages,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error checking HuggingFace status:', error);
|
||||
|
||||
// Fallback to basic endpoint check
|
||||
const endpointStatus = await this.checkEndpoint('https://status.huggingface.co/');
|
||||
const apiEndpoint = 'https://api-inference.huggingface.co/models';
|
||||
const apiStatus = await this.checkEndpoint(apiEndpoint);
|
||||
|
||||
return {
|
||||
status: endpointStatus === 'reachable' && apiStatus === 'reachable' ? 'operational' : 'degraded',
|
||||
message: `Status page: ${endpointStatus}, API: ${apiStatus}`,
|
||||
incidents: ['Note: Limited status information due to CORS restrictions'],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { BaseProviderChecker } from '~/components/settings/providers/service-status/base-provider';
|
||||
import type { StatusCheckResult } from '~/components/settings/providers/service-status/types';
|
||||
|
||||
export class HyperbolicStatusChecker extends BaseProviderChecker {
|
||||
async checkStatus(): Promise<StatusCheckResult> {
|
||||
try {
|
||||
/*
|
||||
* Check API endpoint directly since Hyperbolic is a newer provider
|
||||
* and may not have a public status page yet
|
||||
*/
|
||||
const apiEndpoint = 'https://api.hyperbolic.ai/v1/models';
|
||||
const apiStatus = await this.checkEndpoint(apiEndpoint);
|
||||
|
||||
// Check their website as a secondary indicator
|
||||
const websiteStatus = await this.checkEndpoint('https://hyperbolic.ai');
|
||||
|
||||
let status: StatusCheckResult['status'] = 'operational';
|
||||
let message = 'All systems operational';
|
||||
|
||||
if (apiStatus !== 'reachable' || websiteStatus !== 'reachable') {
|
||||
status = apiStatus !== 'reachable' ? 'down' : 'degraded';
|
||||
message = apiStatus !== 'reachable' ? 'API appears to be down' : 'Service may be experiencing issues';
|
||||
}
|
||||
|
||||
return {
|
||||
status,
|
||||
message,
|
||||
incidents: [], // No public incident tracking available yet
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error checking Hyperbolic status:', error);
|
||||
|
||||
return {
|
||||
status: 'degraded',
|
||||
message: 'Unable to determine service status',
|
||||
incidents: ['Note: Limited status information available'],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import { BaseProviderChecker } from '~/components/settings/providers/service-status/base-provider';
|
||||
import type { StatusCheckResult } from '~/components/settings/providers/service-status/types';
|
||||
|
||||
export class MistralStatusChecker extends BaseProviderChecker {
|
||||
async checkStatus(): Promise<StatusCheckResult> {
|
||||
try {
|
||||
// Check status page
|
||||
const statusPageResponse = await fetch('https://status.mistral.ai/');
|
||||
const text = await statusPageResponse.text();
|
||||
|
||||
const isOperational = text.includes('All Systems Operational');
|
||||
const hasIncidents = text.includes('Active Incidents');
|
||||
const hasDegradation = text.includes('Degraded Performance');
|
||||
const hasOutage = text.includes('Service Outage');
|
||||
|
||||
// Extract incidents
|
||||
const incidents: string[] = [];
|
||||
const incidentSection = text.match(/Recent Events(.*?)(?=\n\n)/s);
|
||||
|
||||
if (incidentSection) {
|
||||
const incidentLines = incidentSection[1]
|
||||
.split('\n')
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => line && !line.includes('No incidents'));
|
||||
|
||||
incidents.push(...incidentLines.slice(0, 5));
|
||||
}
|
||||
|
||||
let status: StatusCheckResult['status'] = 'operational';
|
||||
let message = 'All systems operational';
|
||||
|
||||
if (hasOutage) {
|
||||
status = 'down';
|
||||
message = 'Service outage detected';
|
||||
} else if (hasDegradation || hasIncidents) {
|
||||
status = 'degraded';
|
||||
message = 'Service experiencing issues';
|
||||
} else if (!isOperational) {
|
||||
status = 'degraded';
|
||||
message = 'Service status unknown';
|
||||
}
|
||||
|
||||
// If status page check fails, fallback to endpoint check
|
||||
if (!statusPageResponse.ok) {
|
||||
const endpointStatus = await this.checkEndpoint('https://status.mistral.ai/');
|
||||
const apiEndpoint = 'https://api.mistral.ai/v1/models';
|
||||
const apiStatus = await this.checkEndpoint(apiEndpoint);
|
||||
|
||||
return {
|
||||
status: endpointStatus === 'reachable' && apiStatus === 'reachable' ? 'operational' : 'degraded',
|
||||
message: `Status page: ${endpointStatus}, API: ${apiStatus}`,
|
||||
incidents: ['Note: Limited status information due to CORS restrictions'],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status,
|
||||
message,
|
||||
incidents,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error checking Mistral status:', error);
|
||||
|
||||
// Fallback to basic endpoint check
|
||||
const endpointStatus = await this.checkEndpoint('https://status.mistral.ai/');
|
||||
const apiEndpoint = 'https://api.mistral.ai/v1/models';
|
||||
const apiStatus = await this.checkEndpoint(apiEndpoint);
|
||||
|
||||
return {
|
||||
status: endpointStatus === 'reachable' && apiStatus === 'reachable' ? 'operational' : 'degraded',
|
||||
message: `Status page: ${endpointStatus}, API: ${apiStatus}`,
|
||||
incidents: ['Note: Limited status information due to CORS restrictions'],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import { BaseProviderChecker } from '~/components/settings/providers/service-status/base-provider';
|
||||
import type { StatusCheckResult } from '~/components/settings/providers/service-status/types';
|
||||
|
||||
export class OpenRouterStatusChecker extends BaseProviderChecker {
|
||||
async checkStatus(): Promise<StatusCheckResult> {
|
||||
try {
|
||||
// Check status page
|
||||
const statusPageResponse = await fetch('https://status.openrouter.ai/');
|
||||
const text = await statusPageResponse.text();
|
||||
|
||||
// Check for specific OpenRouter status indicators
|
||||
const isOperational = text.includes('All Systems Operational');
|
||||
const hasIncidents = text.includes('Active Incidents');
|
||||
const hasDegradation = text.includes('Degraded Performance');
|
||||
const hasOutage = text.includes('Service Outage');
|
||||
|
||||
// Extract incidents
|
||||
const incidents: string[] = [];
|
||||
const incidentSection = text.match(/Past Incidents(.*?)(?=\n\n)/s);
|
||||
|
||||
if (incidentSection) {
|
||||
const incidentLines = incidentSection[1]
|
||||
.split('\n')
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => line && line.includes('202')); // Only get dated incidents
|
||||
|
||||
incidents.push(...incidentLines.slice(0, 5));
|
||||
}
|
||||
|
||||
// Check specific services
|
||||
const services = {
|
||||
api: {
|
||||
operational: text.includes('API Service') && text.includes('Operational'),
|
||||
degraded: text.includes('API Service') && text.includes('Degraded Performance'),
|
||||
outage: text.includes('API Service') && text.includes('Service Outage'),
|
||||
},
|
||||
routing: {
|
||||
operational: text.includes('Routing Service') && text.includes('Operational'),
|
||||
degraded: text.includes('Routing Service') && text.includes('Degraded Performance'),
|
||||
outage: text.includes('Routing Service') && text.includes('Service Outage'),
|
||||
},
|
||||
};
|
||||
|
||||
let status: StatusCheckResult['status'] = 'operational';
|
||||
let message = 'All systems operational';
|
||||
|
||||
if (services.api.outage || services.routing.outage || hasOutage) {
|
||||
status = 'down';
|
||||
message = 'Service outage detected';
|
||||
} else if (services.api.degraded || services.routing.degraded || hasDegradation || hasIncidents) {
|
||||
status = 'degraded';
|
||||
message = 'Service experiencing issues';
|
||||
} else if (!isOperational) {
|
||||
status = 'degraded';
|
||||
message = 'Service status unknown';
|
||||
}
|
||||
|
||||
// If status page check fails, fallback to endpoint check
|
||||
if (!statusPageResponse.ok) {
|
||||
const endpointStatus = await this.checkEndpoint('https://status.openrouter.ai/');
|
||||
const apiEndpoint = 'https://openrouter.ai/api/v1/models';
|
||||
const apiStatus = await this.checkEndpoint(apiEndpoint);
|
||||
|
||||
return {
|
||||
status: endpointStatus === 'reachable' && apiStatus === 'reachable' ? 'operational' : 'degraded',
|
||||
message: `Status page: ${endpointStatus}, API: ${apiStatus}`,
|
||||
incidents: ['Note: Limited status information due to CORS restrictions'],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status,
|
||||
message,
|
||||
incidents,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error checking OpenRouter status:', error);
|
||||
|
||||
// Fallback to basic endpoint check
|
||||
const endpointStatus = await this.checkEndpoint('https://status.openrouter.ai/');
|
||||
const apiEndpoint = 'https://openrouter.ai/api/v1/models';
|
||||
const apiStatus = await this.checkEndpoint(apiEndpoint);
|
||||
|
||||
return {
|
||||
status: endpointStatus === 'reachable' && apiStatus === 'reachable' ? 'operational' : 'degraded',
|
||||
message: `Status page: ${endpointStatus}, API: ${apiStatus}`,
|
||||
incidents: ['Note: Limited status information due to CORS restrictions'],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import { BaseProviderChecker } from '~/components/settings/providers/service-status/base-provider';
|
||||
import type { StatusCheckResult } from '~/components/settings/providers/service-status/types';
|
||||
|
||||
export class PerplexityStatusChecker extends BaseProviderChecker {
|
||||
async checkStatus(): Promise<StatusCheckResult> {
|
||||
try {
|
||||
// Check status page
|
||||
const statusPageResponse = await fetch('https://status.perplexity.ai/');
|
||||
const text = await statusPageResponse.text();
|
||||
|
||||
// Check for specific Perplexity status indicators
|
||||
const isOperational = text.includes('All Systems Operational');
|
||||
const hasIncidents = text.includes('Active Incidents');
|
||||
const hasDegradation = text.includes('Degraded Performance');
|
||||
const hasOutage = text.includes('Service Outage');
|
||||
|
||||
// Extract incidents
|
||||
const incidents: string[] = [];
|
||||
const incidentSection = text.match(/Past Incidents(.*?)(?=\n\n)/s);
|
||||
|
||||
if (incidentSection) {
|
||||
const incidentLines = incidentSection[1]
|
||||
.split('\n')
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => line && line.includes('202')); // Only get dated incidents
|
||||
|
||||
incidents.push(...incidentLines.slice(0, 5));
|
||||
}
|
||||
|
||||
// Check specific services
|
||||
const services = {
|
||||
api: {
|
||||
operational: text.includes('API Service') && text.includes('Operational'),
|
||||
degraded: text.includes('API Service') && text.includes('Degraded Performance'),
|
||||
outage: text.includes('API Service') && text.includes('Service Outage'),
|
||||
},
|
||||
inference: {
|
||||
operational: text.includes('Inference Service') && text.includes('Operational'),
|
||||
degraded: text.includes('Inference Service') && text.includes('Degraded Performance'),
|
||||
outage: text.includes('Inference Service') && text.includes('Service Outage'),
|
||||
},
|
||||
};
|
||||
|
||||
let status: StatusCheckResult['status'] = 'operational';
|
||||
let message = 'All systems operational';
|
||||
|
||||
if (services.api.outage || services.inference.outage || hasOutage) {
|
||||
status = 'down';
|
||||
message = 'Service outage detected';
|
||||
} else if (services.api.degraded || services.inference.degraded || hasDegradation || hasIncidents) {
|
||||
status = 'degraded';
|
||||
message = 'Service experiencing issues';
|
||||
} else if (!isOperational) {
|
||||
status = 'degraded';
|
||||
message = 'Service status unknown';
|
||||
}
|
||||
|
||||
// If status page check fails, fallback to endpoint check
|
||||
if (!statusPageResponse.ok) {
|
||||
const endpointStatus = await this.checkEndpoint('https://status.perplexity.ai/');
|
||||
const apiEndpoint = 'https://api.perplexity.ai/v1/models';
|
||||
const apiStatus = await this.checkEndpoint(apiEndpoint);
|
||||
|
||||
return {
|
||||
status: endpointStatus === 'reachable' && apiStatus === 'reachable' ? 'operational' : 'degraded',
|
||||
message: `Status page: ${endpointStatus}, API: ${apiStatus}`,
|
||||
incidents: ['Note: Limited status information due to CORS restrictions'],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status,
|
||||
message,
|
||||
incidents,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error checking Perplexity status:', error);
|
||||
|
||||
// Fallback to basic endpoint check
|
||||
const endpointStatus = await this.checkEndpoint('https://status.perplexity.ai/');
|
||||
const apiEndpoint = 'https://api.perplexity.ai/v1/models';
|
||||
const apiStatus = await this.checkEndpoint(apiEndpoint);
|
||||
|
||||
return {
|
||||
status: endpointStatus === 'reachable' && apiStatus === 'reachable' ? 'operational' : 'degraded',
|
||||
message: `Status page: ${endpointStatus}, API: ${apiStatus}`,
|
||||
incidents: ['Note: Limited status information due to CORS restrictions'],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import { BaseProviderChecker } from '~/components/settings/providers/service-status/base-provider';
|
||||
import type { StatusCheckResult } from '~/components/settings/providers/service-status/types';
|
||||
|
||||
export class TogetherStatusChecker extends BaseProviderChecker {
|
||||
async checkStatus(): Promise<StatusCheckResult> {
|
||||
try {
|
||||
// Check status page
|
||||
const statusPageResponse = await fetch('https://status.together.ai/');
|
||||
const text = await statusPageResponse.text();
|
||||
|
||||
// Check for specific Together status indicators
|
||||
const isOperational = text.includes('All Systems Operational');
|
||||
const hasIncidents = text.includes('Active Incidents');
|
||||
const hasDegradation = text.includes('Degraded Performance');
|
||||
const hasOutage = text.includes('Service Outage');
|
||||
|
||||
// Extract incidents
|
||||
const incidents: string[] = [];
|
||||
const incidentSection = text.match(/Past Incidents(.*?)(?=\n\n)/s);
|
||||
|
||||
if (incidentSection) {
|
||||
const incidentLines = incidentSection[1]
|
||||
.split('\n')
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => line && line.includes('202')); // Only get dated incidents
|
||||
|
||||
incidents.push(...incidentLines.slice(0, 5));
|
||||
}
|
||||
|
||||
// Check specific services
|
||||
const services = {
|
||||
api: {
|
||||
operational: text.includes('API Service') && text.includes('Operational'),
|
||||
degraded: text.includes('API Service') && text.includes('Degraded Performance'),
|
||||
outage: text.includes('API Service') && text.includes('Service Outage'),
|
||||
},
|
||||
inference: {
|
||||
operational: text.includes('Inference Service') && text.includes('Operational'),
|
||||
degraded: text.includes('Inference Service') && text.includes('Degraded Performance'),
|
||||
outage: text.includes('Inference Service') && text.includes('Service Outage'),
|
||||
},
|
||||
};
|
||||
|
||||
let status: StatusCheckResult['status'] = 'operational';
|
||||
let message = 'All systems operational';
|
||||
|
||||
if (services.api.outage || services.inference.outage || hasOutage) {
|
||||
status = 'down';
|
||||
message = 'Service outage detected';
|
||||
} else if (services.api.degraded || services.inference.degraded || hasDegradation || hasIncidents) {
|
||||
status = 'degraded';
|
||||
message = 'Service experiencing issues';
|
||||
} else if (!isOperational) {
|
||||
status = 'degraded';
|
||||
message = 'Service status unknown';
|
||||
}
|
||||
|
||||
// If status page check fails, fallback to endpoint check
|
||||
if (!statusPageResponse.ok) {
|
||||
const endpointStatus = await this.checkEndpoint('https://status.together.ai/');
|
||||
const apiEndpoint = 'https://api.together.ai/v1/models';
|
||||
const apiStatus = await this.checkEndpoint(apiEndpoint);
|
||||
|
||||
return {
|
||||
status: endpointStatus === 'reachable' && apiStatus === 'reachable' ? 'operational' : 'degraded',
|
||||
message: `Status page: ${endpointStatus}, API: ${apiStatus}`,
|
||||
incidents: ['Note: Limited status information due to CORS restrictions'],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status,
|
||||
message,
|
||||
incidents,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error checking Together status:', error);
|
||||
|
||||
// Fallback to basic endpoint check
|
||||
const endpointStatus = await this.checkEndpoint('https://status.together.ai/');
|
||||
const apiEndpoint = 'https://api.together.ai/v1/models';
|
||||
const apiStatus = await this.checkEndpoint(apiEndpoint);
|
||||
|
||||
return {
|
||||
status: endpointStatus === 'reachable' && apiStatus === 'reachable' ? 'operational' : 'degraded',
|
||||
message: `Status page: ${endpointStatus}, API: ${apiStatus}`,
|
||||
incidents: ['Note: Limited status information due to CORS restrictions'],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { BaseProviderChecker } from '~/components/settings/providers/service-status/base-provider';
|
||||
import type { StatusCheckResult } from '~/components/settings/providers/service-status/types';
|
||||
|
||||
export class XAIStatusChecker extends BaseProviderChecker {
|
||||
async checkStatus(): Promise<StatusCheckResult> {
|
||||
try {
|
||||
/*
|
||||
* Check API endpoint directly since XAI is a newer provider
|
||||
* and may not have a public status page yet
|
||||
*/
|
||||
const apiEndpoint = 'https://api.xai.com/v1/models';
|
||||
const apiStatus = await this.checkEndpoint(apiEndpoint);
|
||||
|
||||
// Check their website as a secondary indicator
|
||||
const websiteStatus = await this.checkEndpoint('https://x.ai');
|
||||
|
||||
let status: StatusCheckResult['status'] = 'operational';
|
||||
let message = 'All systems operational';
|
||||
|
||||
if (apiStatus !== 'reachable' || websiteStatus !== 'reachable') {
|
||||
status = apiStatus !== 'reachable' ? 'down' : 'degraded';
|
||||
message = apiStatus !== 'reachable' ? 'API appears to be down' : 'Service may be experiencing issues';
|
||||
}
|
||||
|
||||
return {
|
||||
status,
|
||||
message,
|
||||
incidents: [], // No public incident tracking available yet
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error checking XAI status:', error);
|
||||
|
||||
return {
|
||||
status: 'degraded',
|
||||
message: 'Unable to determine service status',
|
||||
incidents: ['Note: Limited status information available'],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ import type { IconType } from 'react-icons';
|
||||
|
||||
export type ProviderName =
|
||||
| 'AmazonBedrock'
|
||||
| 'Anthropic'
|
||||
| 'Cohere'
|
||||
| 'Deepseek'
|
||||
| 'Google'
|
||||
@@ -10,7 +9,6 @@ export type ProviderName =
|
||||
| 'HuggingFace'
|
||||
| 'Hyperbolic'
|
||||
| 'Mistral'
|
||||
| 'OpenAI'
|
||||
| 'OpenRouter'
|
||||
| 'Perplexity'
|
||||
| 'Together'
|
||||
@@ -27,12 +25,12 @@ export type ServiceStatus = {
|
||||
incidents?: string[];
|
||||
};
|
||||
|
||||
export type ProviderConfig = {
|
||||
export interface ProviderConfig {
|
||||
statusUrl: string;
|
||||
apiUrl: string;
|
||||
headers: Record<string, string>;
|
||||
testModel: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type ApiResponse = {
|
||||
error?: {
|
||||
@@ -51,8 +49,7 @@ export type ApiResponse = {
|
||||
};
|
||||
|
||||
export type StatusCheckResult = {
|
||||
status: ServiceStatus['status'];
|
||||
message?: string;
|
||||
incidents?: string[];
|
||||
responseTime?: number;
|
||||
status: 'operational' | 'degraded' | 'down';
|
||||
message: string;
|
||||
incidents: string[];
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user