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 { Badge } from '@/components/ui/badge'; import { Loader2, Eye, Zap, Code, Brain, Star, Layers } 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'; } /** Format large token counts with K/M suffixes */ function formatTokenCount(value: number): string { if (value >= 1_000_000) { return `${(value / 1_000_000).toFixed(1)}M`; } if (value >= 1_000) { return `${(value / 1_000).toFixed(0)}K`; } return value.toLocaleString(); } 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]); 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')}

{models.length > 0 && ( {models.length} models )}
{/* Content */} {isLoading ? (
) : (
{sortedProviders.map((provider) => (
{/* Provider header */}

{provider}

{groupedModels[provider].length}
{/* Model cards */}
{groupedModels[provider].map((model) => { const enabledCount = Object.values(model.capabilities).filter(Boolean).length; const totalCount = Object.values(model.capabilities).length; const hasLimits = Object.values(model.limits).some((v) => v != null); return (
{/* Model header */}
{model.displayName || model.id}
{enabledCount}/{totalCount}

{model.id}

{/* Capability pills */}
{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}
); })}
{/* Limits section */} {hasLimits && (
{model.limits.maxTokens != null && ( CTX {formatTokenCount(model.limits.maxTokens)} )} {model.limits.maxOutputTokens != null && ( OUT {formatTokenCount(model.limits.maxOutputTokens)} )} {model.limits.thinkingBudget != null && ( THINK {formatTokenCount(model.limits.thinkingBudget)} )}
)}
); })}
))} {models.length === 0 && (

No models found

Check your provider configurations

)}
)}
); };