import { useEffect, useCallback } from 'react' import { X } from 'lucide-react' import { ModelChooserModalProps } from '../types' import { useModelData } from '../hooks/useModelData' import { useFiltering } from '../hooks/useFiltering' import { FilterBar } from './FilterBar' import { ModelTable } from './ModelTable' import { LoadingState, ErrorState } from './LoadingState' export function ModelChooserModal({ isOpen, onClose, selectedModel, onModelChange, onModelSelect, apiEndpoint, fallbackModels, theme = 'light', maxHeight = '80vh', maxWidth, className, categorizeByType = false }: ModelChooserModalProps) { const { models, freeModels, paidModels, loading, error, refresh } = useModelData(apiEndpoint, fallbackModels) const { filteredModels, filterState, sortConfig, updateFilter, clearFilters, setSort, availableProviders, filterStats } = useFiltering(models) // Handle escape key useEffect(() => { const handleEscape = (e: KeyboardEvent) => { if (e.key === 'Escape') { onClose() } } if (isOpen) { document.addEventListener('keydown', handleEscape) // Prevent body scroll when modal is open document.body.style.overflow = 'hidden' } return () => { document.removeEventListener('keydown', handleEscape) document.body.style.overflow = '' } }, [isOpen, onClose]) const handleModelSelect = useCallback((modelId: string) => { // CRITICAL: Pass exact model ID without any modification onModelChange(modelId) // Also call the enhanced callback with full model info if provided if (onModelSelect) { const model = models.find(m => m.id === modelId) if (model) { onModelSelect(model) } } }, [onModelChange, onModelSelect, models]) const handleSelectAndClose = useCallback(() => { if (selectedModel) { onClose() } }, [selectedModel, onClose]) if (!isOpen) return null const renderCategorizedModels = () => { if (!categorizeByType) { return ( ) } // Categorize filtered models const categorizedFree = filteredModels.filter(m => m.costTier === 'free') const categorizedPaid = filteredModels.filter(m => m.costTier !== 'free') return (
{categorizedFree.length > 0 && (

Free Models ({categorizedFree.length})

)} {categorizedPaid.length > 0 && (

Paid Models ({categorizedPaid.length})

)}
) } return (
{/* Backdrop */}
{/* Modal */}
e.stopPropagation()} style={{ maxHeight, ...(maxWidth && { maxWidth, width: maxWidth }) }} > {/* Header */}

Choose OpenRouter LLM Model {!loading && !error && models.length > 0 && ( ({models.length} models{freeModels.length > 0 && `: ${freeModels.length} free, ${paidModels.length} paid`}) )}

{/* Content */}
{loading ? ( ) : error ? ( ) : ( <> {renderCategorizedModels()} )}
{/* Reasoning Detection Warning */} {filteredModels.some(model => model.reasoning) && (
⚠️
Reasoning Detection Notice: Reasoning capabilities are detected using pattern matching for known models (o1, DeepSeek R1, Gemini Thinking, etc.). This may not detect all reasoning models or may change as new models are released. OpenRouter doesn't provide an official reasoning capability field.
)} {/* Footer */}
model.reasoning) ? 'none' : '1px solid var(--or-border)', backgroundColor: 'rgba(0, 0, 0, 0.02)', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
{selectedModel && (() => { const model = models.find(m => m.id === selectedModel) return (
Selected: {selectedModel}
{model && (
{model.name} • {model.provider} {model.reasoning && ( 🧠 Reasoning )} {model.multimodal && ( 👁 Vision )} {model.streamCancel && ( ⏹️ Stream Cancel )}
)}
) })()}
) }