Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(rnd) Agent Marketplace MVP #7657

Merged
merged 55 commits into from
Aug 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
d1187b5
Added listing, sorting, filtering and ordering of agents
Swiftyos Jul 31, 2024
b1c3a5f
Merge branch 'master' into swiftyos/open-1601-paginated-listing-of-st…
Swiftyos Jul 31, 2024
8c06c3a
feat(market): general upkeep for vscode and small docs
ntindle Jul 31, 2024
b49c04a
feat(market): most of search
ntindle Jul 31, 2024
8e7ecc2
fix(market): hinting on the sort was weird + linting
ntindle Jul 31, 2024
d490863
feat(market): migrations and schema updates
ntindle Jul 31, 2024
a0e4735
lint(market): autolint
ntindle Jul 31, 2024
f024a8e
feat(market): better search
ntindle Jul 31, 2024
2ac5868
feat(market): file download
ntindle Jul 31, 2024
217b691
feat(market): analytics of downloads
ntindle Jul 31, 2024
4e16366
Added tracking of views
Swiftyos Aug 1, 2024
738ba79
changed all imports to be fully qualified
Swiftyos Aug 1, 2024
aa539b8
Upgrade sentry sdk
Swiftyos Aug 1, 2024
49a554a
Added an admin endpoint to submit new agents
Swiftyos Aug 1, 2024
21601f2
fixes
Swiftyos Aug 1, 2024
1b4750e
Added endpoint that just tracks download
Swiftyos Aug 1, 2024
0c093db
Starting adding the marketplace page
Swiftyos Aug 1, 2024
55344ef
Marketplace client
Swiftyos Aug 1, 2024
e566491
Create template of the marketplace page
Swiftyos Aug 1, 2024
ed6a8e5
Updated client
Swiftyos Aug 1, 2024
4346286
fix(market): debug port
ntindle Aug 2, 2024
6e5948e
feat(market): agents by downloads
ntindle Aug 2, 2024
f5a3162
fix(market, builder): hook up frontend and backend
ntindle Aug 2, 2024
797d7fb
feat(builder, market): build a "better" market page that loads data
ntindle Aug 2, 2024
18c8886
feat(builder): updated search (working) and page (kinda working)
ntindle Aug 2, 2024
2550c9b
feat(builder): add a feature agents ui (not backed yet)
ntindle Aug 2, 2024
b56d067
feat(builder): improve detail page content
ntindle Aug 2, 2024
ee57568
Added run script
Swiftyos Aug 2, 2024
051b007
Added pre populate database command
Swiftyos Aug 2, 2024
98714a9
Add AnalyticsTracker on create agent
Swiftyos Aug 2, 2024
b29a02c
Add download counts for top agents
Swiftyos Aug 2, 2024
5c31ae4
Add hb page prometheus metrics
Swiftyos Aug 2, 2024
5865700
Added featured agents funcitonality
Swiftyos Aug 2, 2024
5a21305
renamed endpoint to health
Swiftyos Aug 2, 2024
3895bb0
Adding download flow
Swiftyos Aug 2, 2024
54209f6
normalised api routes
Swiftyos Aug 2, 2024
26a0571
update readme
Swiftyos Aug 2, 2024
8d343d6
feat(market) : default featured
ntindle Aug 2, 2024
3fa9149
Merge branch 'master' into swiftyos/open-1601-paginated-listing-of-st…
Swiftyos Aug 5, 2024
1c53212
formatting
Swiftyos Aug 5, 2024
27aec44
revert changes to autogpt and forge
Swiftyos Aug 5, 2024
6aa1d8a
Updated Readme
Swiftyos Aug 5, 2024
6d12509
Eerror when creating an agent from a template installed from (#7697)
Swiftyos Aug 5, 2024
a4af92a
Add dockerfile
Swiftyos Aug 5, 2024
1df4e62
z level fix
Swiftyos Aug 5, 2024
7b67b91
Updated env vars
Swiftyos Aug 5, 2024
c69e014
updated populate url
Swiftyos Aug 5, 2024
3525076
Merge branch 'master' into swiftyos/open-1601-paginated-listing-of-st…
Swiftyos Aug 5, 2024
25376f7
formatting
Swiftyos Aug 5, 2024
83bef7d
fixed linting error
Swiftyos Aug 5, 2024
8ad253b
Set defaults
Swiftyos Aug 5, 2024
49b8e75
Allow only next.js dev server
Swiftyos Aug 5, 2024
492fd71
fixed url
Swiftyos Aug 5, 2024
08009ad
Merge branch 'master' into swiftyos/open-1601-paginated-listing-of-st…
Swiftyos Aug 5, 2024
54ae131
removed graph reassignment as due to change in master
Swiftyos Aug 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .vscode/all-projects.code-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,17 @@
"name": "autogpt_builder",
"path": "../rnd/autogpt_builder"
},
{
"name": "market",
"path": "../rnd/market"
},
{
"name": "[root]",
"path": ".."
}
],
"settings": {
"python.analysis.typeCheckingMode": "basic",
"python.analysis.typeCheckingMode": "basic"
},
"extensions": {
"recommendations": [
Expand Down
3 changes: 2 additions & 1 deletion rnd/autogpt_builder/.env.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
AGPT_SERVER_URL=http://localhost:8000/api
NEXT_PUBLIC_AGPT_SERVER_URL=http://localhost:8000/api
NEXT_PUBLIC_AGPT_MARKETPLACE_URL=http://localhost:8001/api/v1/market

## Supabase credentials
## YOU ONLY NEED THEM IF YOU WANT TO USE SUPABASE USER AUTHENTICATION
Expand Down
4 changes: 3 additions & 1 deletion rnd/autogpt_builder/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ dotenv.config();
/** @type {import('next').NextConfig} */
const nextConfig = {
env: {
AGPT_SERVER_URL: process.env.AGPT_SERVER_URL,
NEXT_PUBLIC_AGPT_SERVER_URL: process.env.NEXT_PUBLIC_AGPT_SERVER_URL,
NEXT_PUBLIC_AGPT_MARKETPLACE_URL:
process.env.NEXT_PUBLIC_AGPT_MARKETPLACE_URL,
},
async redirects() {
return [
Expand Down
41 changes: 41 additions & 0 deletions rnd/autogpt_builder/src/app/marketplace/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Suspense } from "react";
import { notFound } from "next/navigation";
import MarketplaceAPI from "@/lib/marketplace-api";
import { AgentDetailResponse } from "@/lib/marketplace-api";
import AgentDetailContent from "@/components/AgentDetailContent";

async function getAgentDetails(id: string): Promise<AgentDetailResponse> {
const apiUrl =
process.env.NEXT_PUBLIC_AGPT_MARKETPLACE_URL ||
"http://localhost:8001/api/v1/market";
const api = new MarketplaceAPI(apiUrl);
try {
console.log(`Fetching agent details for id: ${id}`);
const agent = await api.getAgentDetails(id);
console.log(`Agent details fetched:`, agent);
return agent;
} catch (error) {
console.error(`Error fetching agent details:`, error);
throw error;
}
}

export default async function AgentDetailPage({
params,
}: {
params: { id: string };
}) {
let agent: AgentDetailResponse;

try {
agent = await getAgentDetails(params.id);
} catch (error) {
return notFound();
}

return (
<Suspense fallback={<div>Loading...</div>}>
<AgentDetailContent agent={agent} />
</Suspense>
);
}
293 changes: 293 additions & 0 deletions rnd/autogpt_builder/src/app/marketplace/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,293 @@
"use client";
import React, { 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,
AgentWithRank,
} from "@/lib/marketplace-api";
import { ChevronLeft, ChevronRight, Search, Star } from "lucide-react";

// Utility Functions
function debounce<T extends (...args: any[]) => any>(
func: T,
wait: number,
): (...args: Parameters<T>) => void {
let timeout: NodeJS.Timeout | null = null;
return (...args: Parameters<T>) => {
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => func(...args), wait);
};
}

// Types
type Agent = AgentResponse | AgentWithRank;

// Components
const HeroSection: React.FC = () => (
<div className="relative bg-indigo-600 py-6">
<div className="absolute inset-0 z-0">
<img
className="w-full h-full object-cover opacity-20"
src="https://images.unsplash.com/photo-1562408590-e32931084e23?auto=format&fit=crop&w=2070&q=80"
alt="Marketplace background"
/>
<div
className="absolute inset-0 bg-indigo-600 mix-blend-multiply"
aria-hidden="true"
></div>
</div>
<div className="relative max-w-7xl mx-auto py-4 px-4 sm:px-6 lg:px-8">
<h1 className="text-2xl font-extrabold tracking-tight text-white sm:text-3xl lg:text-4xl">
AutoGPT Marketplace
</h1>
<p className="mt-2 max-w-3xl text-sm sm:text-base text-indigo-100">
Discover and share proven AI Agents to supercharge your business.
</p>
</div>
</div>
);

const SearchInput: React.FC<{
value: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
}> = ({ value, onChange }) => (
<div className="mb-8 relative">
<Input
placeholder="Search agents..."
type="text"
className="w-full pl-10 pr-4 py-2 rounded-full border-gray-300 focus:border-indigo-500 focus:ring-indigo-500"
value={value}
onChange={onChange}
/>
<Search
className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"
size={20}
/>
</div>
);

const AgentCard: React.FC<{ agent: Agent; featured?: boolean }> = ({
agent,
featured = false,
}) => {
const router = useRouter();

const handleClick = () => {
router.push(`/marketplace/${agent.id}`);
};

return (
<div
className={`flex flex-col justify-between p-6 cursor-pointer hover:bg-gray-50 transition-colors duration-200 rounded-lg border ${featured ? "border-indigo-500 shadow-md" : "border-gray-200"}`}
onClick={handleClick}
>
<div>
<div className="flex items-center justify-between mb-2">
<h3 className="text-lg font-semibold text-gray-900 truncate">
{agent.name}
</h3>
{featured && <Star className="text-indigo-500" size={20} />}
</div>
<p className="text-sm text-gray-500 line-clamp-2 mb-4">
{agent.description}
</p>
<div className="text-xs text-gray-400 mb-2">
Categories: {agent.categories.join(", ")}
</div>
</div>
<div className="flex justify-between items-end">
<div className="text-xs text-gray-400">
Updated {new Date(agent.updatedAt).toLocaleDateString()}
</div>
<div className="text-xs text-gray-400">Downloads {agent.downloads}</div>
{"rank" in agent && (
<div className="text-xs text-indigo-600">
Rank: {agent.rank.toFixed(2)}
</div>
)}
</div>
</div>
);
};

const AgentGrid: React.FC<{
agents: Agent[];
title: string;
featured?: boolean;
}> = ({ agents, title, featured = false }) => (
<div className="mb-12">
<h2 className="text-2xl font-bold text-gray-900 mb-4">{title}</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{agents.map((agent) => (
<AgentCard agent={agent} key={agent.id} featured={featured} />
))}
</div>
</div>
);

const Pagination: React.FC<{
page: number;
totalPages: number;
onPrevPage: () => void;
onNextPage: () => void;
}> = ({ page, totalPages, onPrevPage, onNextPage }) => (
<div className="flex justify-between items-center mt-8">
<Button
onClick={onPrevPage}
disabled={page === 1}
className="flex items-center space-x-2 px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50"
>
<ChevronLeft size={16} />
<span>Previous</span>
</Button>
<span className="text-sm text-gray-700">
Page {page} of {totalPages}
</span>
<Button
onClick={onNextPage}
disabled={page === totalPages}
className="flex items-center space-x-2 px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50"
>
<span>Next</span>
<ChevronRight size={16} />
</Button>
</div>
);

// Main Component
const Marketplace: React.FC = () => {
const apiUrl =
process.env.NEXT_PUBLIC_AGPT_MARKETPLACE_URL ||
"http://localhost:8001/api/v1/market";
const api = useMemo(() => new MarketplaceAPI(apiUrl), [apiUrl]);

const [searchValue, setSearchValue] = useState("");
const [searchResults, setSearchResults] = useState<Agent[]>([]);
const [featuredAgents, setFeaturedAgents] = useState<Agent[]>([]);
const [topAgents, setTopAgents] = useState<Agent[]>([]);
const [page, setPage] = useState(1);
const [totalPages, setTotalPages] = useState(1);
const [isLoading, setIsLoading] = useState(false);

const fetchTopAgents = useCallback(
async (currentPage: number) => {
setIsLoading(true);
try {
const response = await api.getTopDownloadedAgents(currentPage, 9);
setTopAgents(response.agents);
setTotalPages(response.total_pages);
} catch (error) {
console.error("Error fetching top agents:", error);
} finally {
setIsLoading(false);
}
},
[api],
);

const fetchFeaturedAgents = useCallback(async () => {
try {
const featured = await api.getFeaturedAgents();
setFeaturedAgents(featured.agents);
} catch (error) {
console.error("Error fetching featured agents:", error);
}
}, [api]);

const searchAgents = useCallback(
async (searchTerm: string) => {
setIsLoading(true);
try {
const response = await api.searchAgents(searchTerm, 1, 30);
const filteredAgents = response.filter((agent) => agent.rank > 0);
setSearchResults(filteredAgents);
} catch (error) {
console.error("Error searching agents:", error);
} finally {
setIsLoading(false);
}
},
[api],
);

const debouncedSearch = useMemo(
() => debounce(searchAgents, 300),
[searchAgents],
);

useEffect(() => {
if (searchValue) {
debouncedSearch(searchValue);
} else {
fetchTopAgents(page);
}
}, [searchValue, page, debouncedSearch, fetchTopAgents]);

useEffect(() => {
fetchFeaturedAgents();
}, [fetchFeaturedAgents]);

const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearchValue(e.target.value);
setPage(1);
};

const handleNextPage = () => {
if (page < totalPages) {
setPage(page + 1);
}
};

const handlePrevPage = () => {
if (page > 1) {
setPage(page - 1);
}
};

return (
<div className="bg-gray-50 min-h-screen">
<HeroSection />
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<SearchInput value={searchValue} onChange={handleInputChange} />
{isLoading ? (
<div className="text-center py-12">
<div className="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900"></div>
<p className="mt-2 text-gray-600">Loading agents...</p>
</div>
) : searchValue ? (
searchResults.length > 0 ? (
<AgentGrid agents={searchResults} title="Search Results" />
) : (
<div className="text-center py-12">
<p className="text-gray-600">
No agents found matching your search criteria.
</p>
</div>
)
) : (
<>
{featuredAgents.length > 0 && (
<AgentGrid
agents={featuredAgents}
title="Featured Agents"
featured={true}
/>
)}
<AgentGrid agents={topAgents} title="Top Downloaded Agents" />
<Pagination
page={page}
totalPages={totalPages}
onPrevPage={handlePrevPage}
onNextPage={handleNextPage}
/>
</>
)}
</div>
</div>
);
};

export default Marketplace;
Loading
Loading