"use client"; import React, { useState, useRef, useEffect, useCallback } from "react"; import { Search, List, Loader2, Check } from "lucide-react"; import { Input } from "@/components/ui/input"; export interface ModelOption { id: string; name?: string; owned_by?: string; context_length?: number; pricing?: { prompt?: string; completion?: string }; } interface ModelComboboxProps { value: string; onChange: (value: string) => void; models: ModelOption[]; isLoading: boolean; onFetchModels: () => void; providerLabel: string; placeholder?: string; } export function ModelCombobox({ value, onChange, models, isLoading, onFetchModels, providerLabel, placeholder = "nom-du-modele", }: ModelComboboxProps) { const [isOpen, setIsOpen] = useState(false); const [filter, setFilter] = useState(""); const containerRef = useRef(null); const listRef = useRef(null); const [highlightIndex, setHighlightIndex] = useState(-1); const filtered = React.useMemo(() => { if (!filter.trim()) return models; const q = filter.toLowerCase(); return models.filter( (m) => m.id.toLowerCase().includes(q) || (m.name && m.name.toLowerCase().includes(q)) ); }, [models, filter]); useEffect(() => { function handleClick(e: MouseEvent) { if (containerRef.current && !containerRef.current.contains(e.target as Node)) { setIsOpen(false); } } document.addEventListener("mousedown", handleClick); return () => document.removeEventListener("mousedown", handleClick); }, []); useEffect(() => { setHighlightIndex(-1); }, [filter]); useEffect(() => { if (highlightIndex >= 0 && listRef.current) { (listRef.current.children[highlightIndex] as HTMLElement)?.scrollIntoView({ block: "nearest" }); } }, [highlightIndex]); const handleBrowse = useCallback(() => { if (!isOpen) onFetchModels(); setIsOpen(!isOpen); }, [isOpen, onFetchModels]); const handleSelect = useCallback( (model: ModelOption) => { onChange(model.id); setFilter(""); setIsOpen(false); }, [onChange] ); const handleKeyDown = useCallback( (e: React.KeyboardEvent) => { if (!isOpen || filtered.length === 0) return; if (e.key === "ArrowDown") { e.preventDefault(); setHighlightIndex((p) => Math.min(p + 1, filtered.length - 1)); } else if (e.key === "ArrowUp") { e.preventDefault(); setHighlightIndex((p) => Math.max(p - 1, 0)); } else if (e.key === "Enter" && highlightIndex >= 0) { e.preventDefault(); handleSelect(filtered[highlightIndex]); } else if (e.key === "Escape") { setIsOpen(false); } }, [isOpen, filtered, highlightIndex, handleSelect] ); return (
onChange(e.target.value)} onFocus={() => { if (models.length > 0) setIsOpen(true); }} onKeyDown={handleKeyDown} leftIcon={} loading={isLoading} />
{isOpen && (
{/* Filter */}
setFilter(e.target.value)} leftIcon={} className="h-8 text-xs" />
{/* List */}
    {filtered.length === 0 ? (
  • {models.length === 0 ? "Aucun modele recupere" : "Aucun modele ne correspond"}
  • ) : ( filtered.map((model, i) => (
  • handleSelect(model)} onMouseEnter={() => setHighlightIndex(i)} className={`flex items-center justify-between px-3 py-2 text-xs cursor-pointer transition-colors ${ i === highlightIndex ? "bg-primary/10 text-foreground" : "text-muted-foreground hover:bg-surface-elevated" } ${value === model.id ? "bg-primary/5" : ""}`} >
    {model.id}
    {model.name && model.name !== model.id && (
    {model.name}
    )} {model.context_length && ( ctx: {(model.context_length / 1000).toFixed(0)}k )}
    {value === model.id && }
  • )) )}
{models.length > 0 && (
{filtered.length} modele{filtered.length !== 1 ? "s" : ""} {filter ? ` sur ${models.length}` : ""} — nom libre possible
)}
)}
); }