import { useState, useRef, useEffect, useMemo } from 'react'; import { Input } from '@/components/ui/input'; import { useOpenRouterModels, formatContextLength, formatModelPricing, OpenRouterModel } from '@/hooks/useOpenRouterModels'; import { ChevronDown, Loader2, Search, Sparkles } from 'lucide-react'; import { cn } from '@/lib/utils'; interface ModelComboboxProps { value: string; onChange: (value: string) => void; placeholder?: string; className?: string; disabled?: boolean; } export function ModelCombobox({ value, onChange, placeholder = 'Select or type model ID...', className, disabled = false, }: ModelComboboxProps) { const { models, loading } = useOpenRouterModels(); const [isOpen, setIsOpen] = useState(false); const [search, setSearch] = useState(''); const containerRef = useRef(null); const inputRef = useRef(null); // Filter models based on search const filteredModels = useMemo(() => { const query = (search || value).toLowerCase().trim(); if (!query) { // Show featured models when no search return models.slice(0, 15); } return models .filter(m => m.id.toLowerCase().includes(query) || m.name.toLowerCase().includes(query) ) .slice(0, 20); }, [models, search, value]); // Find current model info const currentModel = useMemo(() => { return models.find(m => m.id === value); }, [models, value]); // Close on click outside useEffect(() => { function handleClickOutside(event: MouseEvent) { if (containerRef.current && !containerRef.current.contains(event.target as Node)) { setIsOpen(false); setSearch(''); } } document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); }, []); // Handle keyboard navigation const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Escape') { setIsOpen(false); setSearch(''); } else if (e.key === 'ArrowDown' && !isOpen) { setIsOpen(true); } }; const handleSelect = (model: OpenRouterModel) => { onChange(model.id); setIsOpen(false); setSearch(''); }; const handleInputChange = (e: React.ChangeEvent) => { const newValue = e.target.value; setSearch(newValue); onChange(newValue); if (!isOpen && newValue) { setIsOpen(true); } }; const handleInputFocus = () => { setIsOpen(true); }; return (
{/* Input with dropdown trigger */}
{/* Current model info */} {currentModel && !isOpen && (

{currentModel.name} - {formatContextLength(currentModel.context_length)} context

)} {/* Dropdown */} {isOpen && (
{loading ? (
Loading models...
) : filteredModels.length === 0 ? (

No models found

Type a custom model ID or refine your search

) : (
{filteredModels.map((model, index) => { const isFeatured = index < 8 && !search && !value; return ( ); })}
)}
)}
); }