import React from 'react'; import { Star, HelpCircle } from 'lucide-react'; import { cn } from '../../lib/utils'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../ui/tooltip'; import type { LLMProvider } from '@dexto/core'; import { PROVIDER_LOGOS, needsDarkModeInversion, formatPricingLines } from './constants'; import { CapabilityIcons } from './CapabilityIcons'; import type { ModelInfo, ProviderCatalog } from './types'; interface CompactModelCardProps { provider: LLMProvider; model: ModelInfo; providerInfo: ProviderCatalog; isFavorite: boolean; isActive: boolean; onClick: () => void; onToggleFavorite: () => void; } // Provider display name mapping const PROVIDER_DISPLAY_NAMES: Record = { anthropic: 'Claude', google: 'Gemini', openai: 'GPT', groq: 'Groq', xai: 'Grok', cohere: 'Cohere', 'openai-compatible': 'Custom', dexto: 'Dexto', }; // Providers that have multi-vendor models (don't strip provider prefixes from display name) const MULTI_VENDOR_PROVIDERS = new Set([ 'openrouter', 'dexto', 'openai-compatible', 'litellm', 'glama', 'bedrock', 'vertex', ]); export function CompactModelCard({ provider, model, providerInfo, isFavorite, isActive, onClick, onToggleFavorite, }: CompactModelCardProps) { const displayName = model.displayName || model.name; const hasApiKey = providerInfo.hasApiKey; const providerName = PROVIDER_DISPLAY_NAMES[provider] || provider; // Build description for tooltip const priceLines = formatPricingLines(model.pricing || undefined); const descriptionLines = [ `Provider: ${providerInfo.name}`, model.maxInputTokens && `Max tokens: ${model.maxInputTokens.toLocaleString()}`, Array.isArray(model.supportedFileTypes) && model.supportedFileTypes.length > 0 && `Supports: ${model.supportedFileTypes.join(', ')}`, !hasApiKey && 'API key required', ...priceLines, ].filter(Boolean) as string[]; return (
{ const target = event.target as HTMLElement | null; if (target && target.closest('button')) return; const isEnter = event.key === 'Enter'; const isSpace = event.key === ' ' || event.key === 'Spacebar' || event.code === 'Space'; if (!isEnter && !isSpace) return; if (isSpace) event.preventDefault(); onClick(); }} className={cn( 'relative flex items-center gap-2.5 px-3 py-2 rounded-xl border transition-all duration-150 cursor-pointer group whitespace-nowrap', 'hover:bg-accent/50 hover:border-primary/30', isActive ? 'bg-primary/10 border-primary/40 shadow-sm' : 'border-border/40 bg-card/60', !hasApiKey && 'opacity-70' )} role="button" tabIndex={0} > {/* Provider Logo */}
{PROVIDER_LOGOS[provider] ? ( {`${provider} ) : ( )}
{/* Model Name */}
{providerName} {MULTI_VENDOR_PROVIDERS.has(provider) ? displayName : displayName.replace( new RegExp(`^${providerName}\\s*`, 'i'), '' )}
{/* Capability Icons */} {/* Favorite Star */}
{descriptionLines.map((line, idx) => (
{line}
))}
); }