import React, { useState } from 'react'; import { Star, HelpCircle, ChevronDown, ChevronRight, ExternalLink } from 'lucide-react'; import type { ProviderCatalog, ModelInfo } from './types'; import { cn } from '../../lib/utils'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../ui/tooltip'; import type { LLMProvider } from '@dexto/core'; import { PROVIDER_LOGOS, needsDarkModeInversion, PROVIDER_PRICING_URLS, formatPricingLines, } from './constants'; import { CapabilityIcons } from './CapabilityIcons'; type Props = { providerId: LLMProvider; provider: ProviderCatalog; models: ModelInfo[]; favorites: string[]; currentModel?: { provider: string; model: string; displayName?: string }; onToggleFavorite: (providerId: LLMProvider, modelName: string) => void; onUse: (providerId: LLMProvider, model: ModelInfo) => void; defaultExpanded?: boolean; }; export function ProviderSection({ providerId, provider, models, favorites, currentModel, onToggleFavorite, onUse, defaultExpanded = true, }: Props) { const [isExpanded, setIsExpanded] = useState(defaultExpanded); if (models.length === 0) return null; const isCurrentModel = (modelName: string) => currentModel?.provider === providerId && currentModel?.model === modelName; const isFavorite = (modelName: string) => favorites.includes(`${providerId}|${modelName}`); const hasActiveModel = models.some((m) => isCurrentModel(m.name)); return (
{/* Provider Header */} {/* Models List */} {isExpanded && (
{models.map((model) => { const displayName = model.displayName || model.name; const isActive = isCurrentModel(model.name); const favorite = isFavorite(model.name); const hasApiKey = provider.hasApiKey; // Build description lines for tooltip const priceLines = formatPricingLines(model.pricing || undefined); const descriptionLines = [ model.maxInputTokens && `Max tokens: ${model.maxInputTokens.toLocaleString()}`, Array.isArray(model.supportedFileTypes) && model.supportedFileTypes.length > 0 && `Supports: ${model.supportedFileTypes.join(', ')}`, model.default && 'Default model', !hasApiKey && 'API key required', ...priceLines, ].filter(Boolean) as string[]; return (
onUse(providerId, model)} onKeyDown={(e) => { const target = e.target as HTMLElement | null; if (target && target.closest('button')) return; const isEnter = e.key === 'Enter'; const isSpace = e.key === ' ' || e.key === 'Spacebar' || e.code === 'Space'; if (isSpace) e.preventDefault(); if (isEnter || isSpace) { onUse(providerId, model); } }} className={cn( 'group/card relative flex items-center gap-3 px-4 py-3 rounded-xl border transition-all duration-150 cursor-pointer outline-none', 'hover:bg-accent/50 hover:border-accent-foreground/20 hover:shadow-sm', 'focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/50', isActive ? 'bg-primary/5 border-primary/30 shadow-sm ring-1 ring-primary/20' : 'border-border/40 bg-card/30', !hasApiKey && 'opacity-60' )} role="button" tabIndex={0} > {/* Model Name */}
{displayName}
{/* Capability Icons */} {/* Favorite Star */} {/* Active Indicator */} {isActive && (
)}
{descriptionLines.map((line, idx) => (
{line}
))}
); })}
)}
); }