"use client"; import { useState, useEffect, useRef } from "react"; import { ChevronDown, Cpu, CircuitBoard, Search, Filter, Zap, HardDrive, Clock, TrendingUp, X, Download, Trash2, Cloud, CheckCircle, } from "lucide-react"; import type { ModelInfo } from "@/lib/models"; interface ModelDropdownProps { models: ModelInfo[]; selectedModel: string | null; onSelectModel: (modelId: string) => void; onDeleteModel?: (modelId: string) => void; onPullModel?: (modelName: string) => void; disabled?: boolean; } export function ModelDropdown({ models, selectedModel, onSelectModel, onDeleteModel, onPullModel, disabled = false, }: ModelDropdownProps) { const [isOpen, setIsOpen] = useState(false); const [searchTerm, setSearchTerm] = useState(""); const [filterDevice, setFilterDevice] = useState<"all" | "CPU" | "GPU">( "all" ); const [showRemote, setShowRemote] = useState(false); const [remoteModels, setRemoteModels] = useState([]); const [isSearchingRemote, setIsSearchingRemote] = useState(false); const [showDeleteConfirm, setShowDeleteConfirm] = useState( null ); const dropdownRef = useRef(null); // Buscar modelos remotos const searchRemoteModels = async (query: string) => { setIsSearchingRemote(true); try { const response = await fetch( `/api/ollama/search?q=${encodeURIComponent(query)}` ); const data = await response.json(); if (data.success) { setRemoteModels(data.models); } } catch (error) { console.error("Erro ao buscar modelos remotos:", error); } finally { setIsSearchingRemote(false); } }; // Deletar modelo const handleDeleteModel = async (modelId: string) => { try { const response = await fetch("/api/ollama/delete", { method: "DELETE", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ modelName: modelId }), }); if (response.ok) { onDeleteModel?.(modelId); setShowDeleteConfirm(null); } } catch (error) { console.error("Erro ao deletar modelo:", error); } }; // Baixar modelo remoto const handlePullModel = (modelName: string) => { onPullModel?.(modelName); }; // Filtrar modelos const filteredModels = models.filter((model) => { const matchesSearch = model.name .toLowerCase() .includes(searchTerm.toLowerCase()); const matchesDevice = filterDevice === "all" || model.device === filterDevice; return matchesSearch && matchesDevice; }); // Categorizar modelos por performance const categorizeModel = (model: ModelInfo) => { if (model.sizeGB <= 3) return "compact"; if (model.sizeGB <= 7) return "balanced"; return "powerful"; }; const getPerformanceColor = (model: ModelInfo) => { const category = categorizeModel(model); switch (category) { case "compact": return "text-green-600 bg-green-50 border-green-200"; case "balanced": return "text-blue-600 bg-blue-50 border-blue-200"; case "powerful": return "text-purple-600 bg-purple-50 border-purple-200"; default: return "text-gray-600 bg-gray-50 border-gray-200"; } }; const getPerformanceIcon = (model: ModelInfo) => { const category = categorizeModel(model); switch (category) { case "compact": return ; case "balanced": return ; case "powerful": return ; default: return ; } }; useEffect(() => { function handleClickOutside(event: MouseEvent) { if ( dropdownRef.current && !dropdownRef.current.contains(event.target as Node) ) { setIsOpen(false); setSearchTerm(""); setFilterDevice("all"); setShowRemote(false); setRemoteModels([]); setShowDeleteConfirm(null); } } document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); }, []); // Buscar modelos remotos quando necessário useEffect(() => { if (showRemote && searchTerm.length > 2) { const timeoutId = setTimeout(() => { searchRemoteModels(searchTerm); }, 500); return () => clearTimeout(timeoutId); } }, [showRemote, searchTerm]); const selectedModelData = models.find((m) => m.id === selectedModel); return (
{isOpen && (
{/* Header com busca e filtros */}
setSearchTerm(e.target.value)} className="w-full rounded-md border border-[var(--border)] bg-[var(--background)] pl-8 pr-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-[var(--accent)]" /> {searchTerm && ( )}
{/* Instalar por referência */} {onPullModel && ( )}
{/* Toggle para busca remota */}
{showRemote ? `${remoteModels.length} remote` : `${filteredModels.length} local`}{" "} model {(showRemote ? remoteModels.length : filteredModels.length) !== 1 ? "s" : ""}{" "} found
{/* Lista de modelos com scroll customizado */}
{showRemote ? ( // Lista de modelos remotos <> {isSearchingRemote ? (
Searching remote models...
) : remoteModels.length === 0 ? (
No remote models found
) : (
{remoteModels.map((model) => (
{model.name} Remote {model.installed && ( Installed )}
Available for download
))}
)} ) : ( // Lista de modelos locais <> {filteredModels.length === 0 ? (
No models found
) : (
{filteredModels.map((model) => (
{model.name} {getPerformanceIcon(model)} {categorizeModel(model)}
{model.sizeGB} GB {model.quantization} {model.device === "GPU" ? ( ) : ( )} {model.device}
{/* Informações técnicas e ações */}
{model.estCpuUsagePct && (
{model.estCpuUsagePct}% CPU
)} {model.estVramGB && model.estVramGB > 0 && (
{model.estVramGB} GB VRAM
)}
{onDeleteModel && ( )}
{/* Confirmação de exclusão */} {showDeleteConfirm === model.id && (
Are you sure you want to delete this model?
)}
))}
)} )}
)}
); }