"use client"; import { Bot, Check, ChevronDown, Search } from "lucide-react"; import { useRouter } from "next/navigation"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { useAnonymousMode } from "@/contexts/anonymous-mode"; import type { AnonModel } from "@/contracts/types/anonymous-chat.types"; import { anonymousChatApiService } from "@/lib/apis/anonymous-chat-api.service"; import { getProviderIcon } from "@/lib/provider-icons"; import { cn } from "@/lib/utils"; export function FreeModelSelector({ className }: { className?: string }) { const router = useRouter(); const anonMode = useAnonymousMode(); const currentSlug = anonMode.isAnonymous ? anonMode.modelSlug : ""; const [open, setOpen] = useState(false); const [models, setModels] = useState([]); const [searchQuery, setSearchQuery] = useState(""); const [focusedIndex, setFocusedIndex] = useState(-1); const searchInputRef = useRef(null); useEffect(() => { anonymousChatApiService.getModels().then(setModels).catch(console.error); }, []); const handleOpenChange = useCallback((next: boolean) => { if (next) { setSearchQuery(""); setFocusedIndex(-1); requestAnimationFrame(() => searchInputRef.current?.focus()); } setOpen(next); }, []); const currentModel = useMemo( () => models.find((m) => m.seo_slug === currentSlug) ?? null, [models, currentSlug] ); const sortedModels = useMemo( () => [...models].sort((a, b) => Number(a.is_premium) - Number(b.is_premium)), [models] ); const filteredModels = useMemo(() => { if (!searchQuery.trim()) return sortedModels; const q = searchQuery.toLowerCase(); return sortedModels.filter( (m) => m.name.toLowerCase().includes(q) || m.model_name.toLowerCase().includes(q) || m.provider.toLowerCase().includes(q) ); }, [sortedModels, searchQuery]); const handleSelect = useCallback( (model: AnonModel) => { setOpen(false); if (model.seo_slug === currentSlug) return; if (anonMode.isAnonymous) { anonMode.setModelSlug(model.seo_slug ?? ""); anonMode.resetChat(); } router.replace(`/free/${model.seo_slug}`); }, [currentSlug, anonMode, router] ); const handleKeyDown = useCallback( (e: React.KeyboardEvent) => { const count = filteredModels.length; if (count === 0) return; switch (e.key) { case "ArrowDown": e.preventDefault(); setFocusedIndex((p) => (p < count - 1 ? p + 1 : 0)); break; case "ArrowUp": e.preventDefault(); setFocusedIndex((p) => (p > 0 ? p - 1 : count - 1)); break; case "Enter": e.preventDefault(); if (focusedIndex >= 0 && focusedIndex < count) { handleSelect(filteredModels[focusedIndex]); } break; } }, [filteredModels, focusedIndex, handleSelect] ); return ( e.preventDefault()} >
setSearchQuery(e.target.value)} onKeyDown={handleKeyDown} className="w-full pl-8 pr-3 py-2.5 text-sm bg-transparent focus:outline-none placeholder:text-muted-foreground" />
{filteredModels.length === 0 ? (

No models found

) : ( filteredModels.map((model, index) => { const isSelected = model.seo_slug === currentSlug; const isFocused = focusedIndex === index; return (
handleSelect(model)} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); handleSelect(model); } }} onMouseEnter={() => setFocusedIndex(index)} className={cn( "group flex items-center gap-2.5 px-3 py-2 rounded-xl cursor-pointer", "transition-all duration-150 mx-2", "hover:bg-accent/40", isSelected && "bg-primary/6 dark:bg-primary/8", isFocused && "bg-accent/50" )} >
{getProviderIcon(model.provider, { className: "size-5" })}
{model.name} {model.is_premium ? ( Premium ) : ( Free )}
{model.model_name}
{isSelected && }
); }) )}
); }