diff --git a/rnd/autogpt_builder/src/app/marketplace/[id]/page.tsx b/rnd/autogpt_builder/src/app/marketplace/[id]/page.tsx index 095af0fa9e7e..5bdc84a738d1 100644 --- a/rnd/autogpt_builder/src/app/marketplace/[id]/page.tsx +++ b/rnd/autogpt_builder/src/app/marketplace/[id]/page.tsx @@ -1,41 +1,66 @@ -import { Suspense, useMemo } from 'react'; +import { Suspense } from 'react'; import { notFound } from 'next/navigation'; import Link from 'next/link'; import MarketplaceAPI from "@/lib/marketplace-api"; import { AgentDetailResponse } from "@/lib/marketplace-api"; +import { ArrowLeft, Download, Calendar, Tag } from 'lucide-react'; +import { Button } from "@/components/ui/button"; async function getAgentDetails(id: string): Promise { const apiUrl = process.env.AGPT_MARKETPLACE_URL; const api = new MarketplaceAPI(apiUrl); try { - console.log(`Fetching agent details for id: ${id}`); // Add logging + console.log(`Fetching agent details for id: ${id}`); const agent = await api.getAgentDetails(id); - console.log(`Agent details fetched:`, agent); // Add logging + console.log(`Agent details fetched:`, agent); return agent; } catch (error) { - console.error(`Error fetching agent details:`, error); // Add error logging + console.error(`Error fetching agent details:`, error); return notFound(); } } function AgentDetailContent({ agent }: { agent: AgentDetailResponse }) { return ( -
-
-

{agent.name}

- - Back - -
-
-

{agent.description}

+
+ + + Back to Marketplace + +
+
+

{agent.name}

+

{agent.description}

+
+
+
+
+
+ + Last Updated +
+
+ {new Date(agent.updatedAt).toLocaleDateString()} +
+
+
+
+ + Categories +
+
+ {agent.categories.join(', ')} +
+
+
+
-
- - Download +
+ +
@@ -43,7 +68,7 @@ function AgentDetailContent({ agent }: { agent: AgentDetailResponse }) { } export default async function AgentDetailPage({ params }: { params: { id: string } }) { - console.log(`Rendering AgentDetailPage for id: ${params.id}`); // Add logging + console.log(`Rendering AgentDetailPage for id: ${params.id}`); let agent: AgentDetailResponse | null = null; let error: Error | null = null; @@ -56,7 +81,16 @@ export default async function AgentDetailPage({ params }: { params: { id: string } if (error) { - return
Error: {error.message}
; + return ( +
+

Error

+

{error.message}

+ + + Back to Marketplace + +
+ ); } if (!agent) { @@ -64,7 +98,12 @@ export default async function AgentDetailPage({ params }: { params: { id: string } return ( - Loading...
}> + +
+

Loading agent details...

+
+ }> ); diff --git a/rnd/autogpt_builder/src/app/marketplace/page.tsx b/rnd/autogpt_builder/src/app/marketplace/page.tsx index 37f24d6eac72..a3555082d3cc 100644 --- a/rnd/autogpt_builder/src/app/marketplace/page.tsx +++ b/rnd/autogpt_builder/src/app/marketplace/page.tsx @@ -1,12 +1,21 @@ "use client"; -import { useEffect, useMemo, useState } from "react"; +import { useEffect, useMemo, useState, useCallback } from "react"; import { useRouter } from 'next/navigation'; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; -import MarketplaceAPI, { AgentResponse, AgentListResponse } from "@/lib/marketplace-api"; +import MarketplaceAPI, { AgentResponse, AgentListResponse, AgentWithRank } from "@/lib/marketplace-api"; +import { ChevronLeft, ChevronRight, Search } from 'lucide-react'; + +function debounce any>(func: T, wait: number): (...args: Parameters) => void { + let timeout: NodeJS.Timeout | null = null; + return (...args: Parameters) => { + if (timeout) clearTimeout(timeout); + timeout = setTimeout(() => func(...args), wait); + }; +} interface AgentRowProps { - agent: AgentResponse; + agent: AgentResponse | AgentWithRank; } const AgentRow = ({ agent }: AgentRowProps) => { @@ -17,24 +26,29 @@ const AgentRow = ({ agent }: AgentRowProps) => { }; return ( -
  • -
    - -
    -

    {agent.name}

    -

    {agent.description}

    +
  • +
    +
    + {agent.name.charAt(0)} +
    +
    +

    {agent.name}

    +

    {agent.description}

    -
    -
    -

    {agent.categories.join(', ')}

    -

    - Last updated -

    +
    +
    {agent.categories.join(', ')}
    +
    + Updated {new Date(agent.updatedAt).toLocaleDateString()}
    - + {'rank' in agent && ( +
    + Rank: {agent.rank.toFixed(2)} +
    + )}
  • ); @@ -45,37 +59,44 @@ const Marketplace = () => { const api = useMemo(() => new MarketplaceAPI(apiUrl), [apiUrl]); const [searchValue, setSearchValue] = useState(""); - const [agents, setAgents] = useState([]); + const [agents, setAgents] = useState<(AgentResponse | AgentWithRank)[]>([]); const [page, setPage] = useState(1); const [totalPages, setTotalPages] = useState(1); const [isLoading, setIsLoading] = useState(false); - const fetchAgents = async (searchTerm: string, currentPage: number) => { + const fetchAgents = useCallback(async (searchTerm: string, currentPage: number) => { setIsLoading(true); try { - let response: AgentListResponse; + let response: AgentListResponse | AgentWithRank[]; if (searchTerm) { - response = await api.listAgents({ page: currentPage, page_size: 10, keyword: searchTerm }); + response = await api.searchAgents(searchTerm, currentPage, 10); + const filteredAgents = (response as AgentWithRank[]).filter(agent => agent.rank > 0); + setAgents(filteredAgents); + setTotalPages(Math.ceil(filteredAgents.length / 10)); } else { response = await api.getTopDownloadedAgents(currentPage, 10); + setAgents(response.agents); + setTotalPages(response.total_pages); } - setAgents(response.agents); - setTotalPages(response.total_pages); } catch (error) { console.error("Error fetching agents:", error); } finally { - console.log("Finished fetching agents"); setIsLoading(false); } - }; + }, [api]); + + const debouncedFetchAgents = useMemo( + () => debounce(fetchAgents, 300), + [fetchAgents] + ); useEffect(() => { - fetchAgents(searchValue, page); - }, [searchValue, page, api]); + debouncedFetchAgents(searchValue, page); + }, [searchValue, page, debouncedFetchAgents]); - const handleSearch = (e: React.ChangeEvent) => { + const handleInputChange = (e: React.ChangeEvent) => { setSearchValue(e.target.value); - setPage(1); + setPage(1); // Reset to first page on new search }; const handleNextPage = () => { @@ -91,49 +112,77 @@ const Marketplace = () => { }; return ( -
    -
    -
    +
    -
    -
    +
    +
    +
    {isLoading ? ( -
    Loading...
    - ) : ( +
    +
    +

    Loading agents...

    +
    + ) : agents.length > 0 ? ( <> -
      +

      + {searchValue ? "Search Results" : "Top Downloaded Agents"} +

      +
        {agents.map((agent) => ( ))}
      -
      - - Page {page} of {totalPages} - +
      + + + Page {page} of {totalPages} + +
      + ) : ( +
      +

      No agents found matching your search criteria.

      +
      )} -
    +
    ); }; diff --git a/rnd/autogpt_builder/src/lib/marketplace-api/client.ts b/rnd/autogpt_builder/src/lib/marketplace-api/client.ts index 75b44406a6d0..185a19837765 100644 --- a/rnd/autogpt_builder/src/lib/marketplace-api/client.ts +++ b/rnd/autogpt_builder/src/lib/marketplace-api/client.ts @@ -32,6 +32,33 @@ export default class MarketplaceAPI { ); } + async searchAgents( + query: string, + page: number = 1, + pageSize: number = 10, + categories?: string[], + descriptionThreshold: number = 60, + sortBy: string = "rank", + sortOrder: "asc" | "desc" = "desc" + ): Promise { + const queryParams = new URLSearchParams({ + query, + page: page.toString(), + page_size: pageSize.toString(), + description_threshold: descriptionThreshold.toString(), + sort_by: sortBy, + sort_order: sortOrder, + }); + + if (categories && categories.length > 0) { + categories.forEach((category) => + queryParams.append("categories", category) + ); + } + + return this._get(`/search/search?${queryParams.toString()}`); + } + async getAgentDetails( id: string, version?: number diff --git a/rnd/autogpt_builder/src/lib/marketplace-api/types.ts b/rnd/autogpt_builder/src/lib/marketplace-api/types.ts index f00560702eb1..8ccf2bc1443c 100644 --- a/rnd/autogpt_builder/src/lib/marketplace-api/types.ts +++ b/rnd/autogpt_builder/src/lib/marketplace-api/types.ts @@ -1,52 +1,56 @@ export type ListAgentsParams = { - page?: number; - page_size?: number; - name?: string; - keyword?: string; - category?: string; - description?: string; - description_threshold?: number; - sort_by?: string; - sort_order?: 'asc' | 'desc'; + page?: number; + page_size?: number; + name?: string; + keyword?: string; + category?: string; + description?: string; + description_threshold?: number; + sort_by?: string; + sort_order?: "asc" | "desc"; }; export type AddAgentRequest = { - graph: { - name: string; - description: string; - [key: string]: any; - }; - author: string; - keywords: string[]; - categories: string[]; + graph: { + name: string; + description: string; + [key: string]: any; + }; + author: string; + keywords: string[]; + categories: string[]; }; export type Agent = { - id: string; - name: string; - description: string; - author: string; - keywords: string[]; - categories: string[]; - version: number; - createdAt: string; // ISO8601 datetime string - updatedAt: string; // ISO8601 datetime string + id: string; + name: string; + description: string; + author: string; + keywords: string[]; + categories: string[]; + version: number; + createdAt: string; // ISO8601 datetime string + updatedAt: string; // ISO8601 datetime string }; export type AgentList = { - agents: Agent[]; - total_count: number; - page: number; - page_size: number; - total_pages: number; + agents: Agent[]; + total_count: number; + page: number; + page_size: number; + total_pages: number; }; export type AgentDetail = Agent & { - graph: Record; + graph: Record; +}; + +export type AgentWithRank = Agent & { + rank: number; }; export type AgentListResponse = AgentList; export type AgentDetailResponse = AgentDetail; -export type AgentResponse = Agent; \ No newline at end of file +export type AgentResponse = Agent;