Add Marketplace Agents to builder

Adds functionality to add Marketplace agents to the user's library and then to builder.
Includes a loading indicator while the agent is being added.
Refactors agent-to-block conversion into a utility function.
This commit is contained in:
Abhimanyu Yadav
2025-06-02 12:32:13 +05:30
parent 8088d294f4
commit 39d03f2090
4 changed files with 92 additions and 37 deletions

View File

@@ -1,7 +1,7 @@
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import { cn } from "@/lib/utils";
import { ExternalLink, Plus } from "lucide-react";
import { ExternalLink, Loader2, Plus } from "lucide-react";
import Image from "next/image";
import React, { ButtonHTMLAttributes } from "react";
import { highlightText } from "./IntegrationBlock";
@@ -14,6 +14,7 @@ interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
image_url?: string;
highlightedText?: string;
slug: string;
loading: boolean;
}
interface MarketplaceAgentBlockComponent extends React.FC<Props> {
@@ -26,6 +27,7 @@ const MarketplaceAgentBlock: MarketplaceAgentBlockComponent = ({
creator_name,
number_of_runs,
className,
loading,
highlightedText,
slug,
...rest
@@ -90,10 +92,14 @@ const MarketplaceAgentBlock: MarketplaceAgentBlockComponent = ({
</div>
<div
className={cn(
"flex h-7 w-7 items-center justify-center rounded-[0.5rem] bg-zinc-700 group-disabled:bg-zinc-400",
"flex h-7 min-w-7 items-center justify-center rounded-[0.5rem] bg-zinc-700 group-disabled:bg-zinc-400",
)}
>
<Plus className="h-5 w-5 text-zinc-50" strokeWidth={2} />
{!loading ? (
<Plus className="h-5 w-5 text-zinc-50" strokeWidth={2} />
) : (
<Loader2 className="h-5 w-5 animate-spin" />
)}
</div>
</Button>
);

View File

@@ -1,7 +1,10 @@
import React from "react";
import React, { useState } from "react";
import MarketplaceAgentBlock from "../MarketplaceAgentBlock";
import { usePagination } from "@/hooks/usePagination";
import ErrorState from "../ErrorState";
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
import { convertLibraryAgentIntoBlock } from "@/lib/utils";
import { useBlockMenuContext } from "../block-menu-provider";
const MarketplaceAgentsContent: React.FC = () => {
const {
@@ -16,6 +19,40 @@ const MarketplaceAgentsContent: React.FC = () => {
request: { apiType: "store-agents" },
pageSize: 10,
});
const api = useBackendAPI();
const { addNode } = useBlockMenuContext();
const [loadingSlug, setLoadingSlug] = useState<string | null>(null);
const handleAddStoreAgent = async ({
creator_name,
slug,
}: {
creator_name: string;
slug: string;
}) => {
try {
setLoadingSlug(slug);
const details = await api.getStoreAgent(creator_name, slug);
if (!details.active_version_id) {
console.error(
"Cannot add store agent to library: active version ID is missing or undefined",
);
return;
}
const libraryAgent = await api.addMarketplaceAgentToLibrary(
details.active_version_id,
);
const block = convertLibraryAgentIntoBlock(libraryAgent);
addNode(block);
} catch (error) {
console.error("Failed to add store agent:", error);
} finally {
setLoadingSlug(null);
}
};
if (loading) {
return (
@@ -58,6 +95,13 @@ const MarketplaceAgentsContent: React.FC = () => {
image_url={agent.agent_image}
creator_name={agent.creator}
number_of_runs={agent.runs}
loading={loadingSlug === agent.slug}
onClick={() =>
handleAddStoreAgent({
creator_name: agent.creator,
slug: agent.slug,
})
}
/>
))}
{loadingMore && hasMore && (

View File

@@ -2,13 +2,8 @@ import React from "react";
import UGCAgentBlock from "../UGCAgentBlock";
import { usePagination } from "@/hooks/usePagination";
import ErrorState from "../ErrorState";
import {
Block,
BlockUIType,
LibraryAgent,
SpecialBlockID,
} from "@/lib/autogpt-server-api";
import { useBlockMenuContext } from "../block-menu-provider";
import { convertLibraryAgentIntoBlock } from "@/lib/utils";
const MyAgentsContent: React.FC = () => {
const {
@@ -25,31 +20,6 @@ const MyAgentsContent: React.FC = () => {
});
const { addNode } = useBlockMenuContext();
const handleAddAgent = (agent: LibraryAgent) => {
const block = {
id: SpecialBlockID.AGENT,
name: agent.name,
description:
`Ver.${agent.graph_version}` +
(agent.description ? ` | ${agent.description}` : ""),
categories: [{ category: "AGENT", description: "" }],
inputSchema: agent.input_schema,
outputSchema: agent.output_schema,
staticOutput: false,
uiType: BlockUIType.AGENT,
uiKey: agent.id,
costs: [],
hardcodedValues: {
graph_id: agent.graph_id,
graph_version: agent.graph_version,
input_schema: agent.input_schema,
output_schema: agent.output_schema,
},
} as Block;
addNode(block);
};
if (loading) {
return (
<div
@@ -90,7 +60,10 @@ const MyAgentsContent: React.FC = () => {
edited_time={agent.updated_at}
version={agent.graph_version}
image_url={agent.image_url}
onClick={() => handleAddAgent(agent)}
onClick={() => {
const block = convertLibraryAgentIntoBlock(agent);
addNode(block);
}}
/>
))}
{loadingMore && hasMore && (

View File

@@ -1,7 +1,14 @@
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
import { Category, Graph } from "@/lib/autogpt-server-api/types";
import {
Block,
BlockUIType,
Category,
Graph,
LibraryAgent,
SpecialBlockID,
} from "@/lib/autogpt-server-api/types";
import { NodeDimension } from "@/components/Flow";
export function cn(...inputs: ClassValue[]) {
@@ -396,3 +403,28 @@ export function getValue(key: string, value: any) {
export function isEmptyOrWhitespace(str: string | undefined | null): boolean {
return !str || str.trim().length === 0;
}
export const convertLibraryAgentIntoBlock = (agent: LibraryAgent) => {
const block = {
id: SpecialBlockID.AGENT,
name: agent.name,
description:
`Ver.${agent.graph_version}` +
(agent.description ? ` | ${agent.description}` : ""),
categories: [{ category: "AGENT", description: "" }],
inputSchema: agent.input_schema,
outputSchema: agent.output_schema,
staticOutput: false,
uiType: BlockUIType.AGENT,
uiKey: agent.id,
costs: [],
hardcodedValues: {
graph_id: agent.graph_id,
graph_version: agent.graph_version,
input_schema: agent.input_schema,
output_schema: agent.output_schema,
},
} as Block;
return block;
};