import React, { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { useQuery } from '@tanstack/react-query'; import { groupBy } from 'lodash-es'; import { ipc } from '@/ipc/manager'; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Loader2, Eye, Zap, Code, Brain, Star } from 'lucide-react'; interface ModelCapability { id: string; object: string; displayName?: string; capabilities: { vision: boolean; thinking: boolean; streaming: boolean; jsonMode: boolean; recommended: boolean; }; limits: { maxTokens?: number; maxOutputTokens?: number; thinkingBudget?: number; }; } const CAPABILITY_ICONS = { vision: Eye, thinking: Brain, streaming: Zap, jsonMode: Code, recommended: Star, } as const; const CAPABILITY_LABELS = { vision: 'Vision', thinking: 'Thinking', streaming: 'Streaming', jsonMode: 'JSON Mode', recommended: 'Recommended', } as const; function getProvider(modelId: string): string { const id = modelId.toLowerCase(); if (id.startsWith('gemini')) return 'Google'; if (id.startsWith('claude')) return 'Anthropic'; if (id.startsWith('gpt') || id.startsWith('o1') || id.startsWith('o3') || id.startsWith('text-')) return 'OpenAI'; if (id.startsWith('deepseek')) return 'DeepSeek'; if (id.startsWith('meta-llama') || id.startsWith('llama')) return 'Meta'; if (id.startsWith('mistral') || id.startsWith('mixtral')) return 'Mistral'; if (id.startsWith('qwen')) return 'Alibaba'; if (id.startsWith('x-ai') || id.startsWith('grok')) return 'xAI'; if (id.startsWith('sonar')) return 'Perplexity'; if (id.startsWith('command')) return 'Cohere'; return 'Other'; } export const CapabilitiesTab: React.FC = () => { const { t } = useTranslation(); const { data: capabilitiesData, isLoading } = useQuery({ queryKey: ['proxyAdvanced', 'modelCapabilities'], queryFn: () => ipc.client.proxyAdvanced.getModelCapabilities(), refetchInterval: 60000, }); const models: ModelCapability[] = capabilitiesData?.success ? (capabilitiesData.data as ModelCapability[]) : []; const groupedModels = useMemo(() => { return groupBy(models, (m) => getProvider(m.id)); }, [models]); // Sort providers alphabetically, but keep 'Other' at the bottom const sortedProviders = Object.keys(groupedModels).sort((a, b) => { if (a === 'Other') return 1; if (b === 'Other') return -1; return a.localeCompare(b); }); return (
{/* Header */}

{t('proxy.advanced.capabilities.title', 'Model Capabilities')}

{t('proxy.advanced.capabilities.description', 'Discover what each model can do')}

{/* Capabilities Grid */} {isLoading ? (
) : (
{sortedProviders.map((provider) => (

{provider}

{groupedModels[provider].map((model) => (
{model.displayName || model.id} {model.object}
{model.id}
{Object.entries(model.capabilities).map(([key, supported]) => { const iconKey = key as keyof typeof CAPABILITY_ICONS; const Icon = CAPABILITY_ICONS[iconKey]; const label = CAPABILITY_LABELS[iconKey]; return (
{label}
); })}
{Object.values(model.limits).some((v) => v != null) && (

Limits

{model.limits.maxTokens != null && ( Context: {model.limits.maxTokens.toLocaleString()} )} {model.limits.maxOutputTokens != null && ( Output: {model.limits.maxOutputTokens.toLocaleString()} )} {model.limits.thinkingBudget != null && ( Think: {model.limits.thinkingBudget.toLocaleString()} )}
)}
))}
))} {models.length === 0 && (
No models found. Check your provider configurations.
)}
)}
); };