"use client"; import { useState, useEffect, useCallback, useMemo } from "react"; import { X, Search, Filter, Package, Star, Download, Eye } from "lucide-react"; import { MCPProvider } from "@/lib/types/mcp"; import { MCPCard } from "./MCPCard"; import { MCPCategoryFilter } from "./MCPCategoryFilter"; import { MCPSearchBar } from "./MCPSearchBar"; interface MCPMarketplaceModalProps { isOpen: boolean; onClose: () => void; } export function MCPMarketplaceModal({ isOpen, onClose, }: MCPMarketplaceModalProps) { const [mcps, setMcps] = useState([]); const [loading, setLoading] = useState(false); const [selectedCategory, setSelectedCategory] = useState("all"); const [searchTerm, setSearchTerm] = useState(""); const [debouncedSearchTerm, setDebouncedSearchTerm] = useState(""); const [sortBy, setSortBy] = useState< "rating" | "downloads" | "name" | "recent" | "total_ratings" | "updated_at" >("rating"); const [sortOrder, setSortOrder] = useState<"asc" | "desc">("desc"); const [selectedMCP, setSelectedMCP] = useState(null); const [syncStatus, setSyncStatus] = useState<{ isSyncing: boolean; lastSync: number | null; totalCached: number; cacheValid: boolean; } | null>(null); const [categories, setCategories] = useState<{ primary: Array<{ category: string; count: number }>; others: Array<{ category: string; count: number }>; }>({ primary: [], others: [] }); const fetchMCPs = useCallback(async () => { console.log("Fetching MCPs with params:", { selectedCategory, debouncedSearchTerm, sortBy, sortOrder, }); setLoading(true); try { const params = new URLSearchParams(); if (selectedCategory !== "all") params.append("category", selectedCategory); if (debouncedSearchTerm) params.append("search", debouncedSearchTerm); params.append("sort", sortBy); params.append("order", sortOrder); params.append("limit", "2000"); // Aumentado para 2000 const response = await fetch(`/api/mcp/list?${params}`); const data = await response.json(); if (data.success) { setMcps(data.mcps); } } catch (error) { console.error("Error fetching MCPs:", error); } finally { setLoading(false); } }, [selectedCategory, debouncedSearchTerm, sortBy, sortOrder]); const fetchCategories = useCallback(async () => { try { const response = await fetch("/api/mcp/categories"); const data = await response.json(); if (data.success) { setCategories(data.categories); console.log("📊 Categories loaded:", data.categories); } else { console.error("Failed to fetch categories:", data.error); } } catch (error) { console.error("Error fetching categories:", error); } }, []); const handleInstall = async (mcpId: string) => { try { const response = await fetch("/api/mcp/install", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ mcpId }), }); const data = await response.json(); if (data.success) { // Atualizar estado local setMcps((prev) => prev.map((mcp) => mcp.id === mcpId ? { ...mcp, installed: true } : mcp ) ); } } catch (error) { console.error("Error installing MCP:", error); } }; const handleUninstall = async (mcpId: string) => { try { const response = await fetch("/api/mcp/uninstall", { method: "DELETE", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ mcpId }), }); const data = await response.json(); if (data.success) { // Atualizar estado local setMcps((prev) => prev.map((mcp) => mcp.id === mcpId ? { ...mcp, installed: false } : mcp ) ); } } catch (error) { console.error("Error uninstalling MCP:", error); } }; const handleViewDetails = (mcp: MCPProvider) => { setSelectedMCP(mcp); }; // Carregar MCPs e categorias quando o modal abrir useEffect(() => { if (isOpen) { fetchMCPs(); fetchCategories(); } }, [isOpen]); // Removido fetchMCPs das dependências // Debounce para searchTerm useEffect(() => { const timer = setTimeout(() => { setDebouncedSearchTerm(searchTerm); }, 500); return () => clearTimeout(timer); }, [searchTerm]); // Fetch sync status when modal opens useEffect(() => { if (isOpen) { fetch("/api/mcp/sync") .then((r) => r.json()) .then((data) => { setSyncStatus({ isSyncing: data.isSyncing, lastSync: data.lastSync, totalCached: data.totalCached, cacheValid: data.cacheValid, }); }) .catch(console.error); } }, [isOpen]); // Recarregar MCPs quando filtros mudarem (só se modal estiver aberto) useEffect(() => { if (isOpen) { fetchMCPs(); } }, [selectedCategory, debouncedSearchTerm, sortBy, sortOrder, isOpen]); if (!isOpen) return null; return (
{/* Header */}

MCP Marketplace

{mcps.length} MCPs available
{/* Sync Status Banner */} {syncStatus?.isSyncing && (
Sincronizando marketplace... {syncStatus.totalCached} servidores carregados
)} {syncStatus?.lastSync && !syncStatus.isSyncing && (
Cache local ativo • {syncStatus.totalCached} servidores • Última atualização: {new Date(syncStatus.lastSync).toLocaleString()}
)} {/* Content */}
{/* Sidebar */}
({ id: cat.category.toLowerCase(), name: cat.category, icon: cat.category, count: cat.count, }))} otherCategories={categories.others.map((cat) => ({ id: cat.category.toLowerCase(), name: cat.category, icon: cat.category, count: cat.count, }))} selectedCategory={selectedCategory} onSelectCategory={setSelectedCategory} />
{/* Main Content */}
{/* Search and Filters */}
{ // O useEffect já cuida de recarregar quando searchTerm muda }} />
{/* MCP Grid */}
{loading ? (
) : (
{mcps.map((mcp) => ( handleInstall(mcp.id)} onUninstall={() => handleUninstall(mcp.id)} onViewDetails={() => handleViewDetails(mcp)} /> ))}
)} {!loading && mcps.length === 0 && (

No MCPs found

Try adjusting your search or filters

)}
); }