Compare commits

...

7 Commits

Author SHA1 Message Date
Krzysztof Czerwinski
5ee23cc8cf Fix add_store_agent_to_library 2025-01-11 15:58:52 +01:00
Krzysztof Czerwinski
887ef58fa1 Merge branch 'dev' into kpczerwinski/open-2298-redirect-to-builder-after-adding-agent-to-library-in-the 2025-01-11 14:08:41 +01:00
Krzysztof Czerwinski
fb773fbb83 Lint 2025-01-09 16:39:38 +01:00
Krzysztof Czerwinski
266cac057b Update 2025-01-08 18:29:07 +01:00
Krzysztof Czerwinski
56affddad7 Fix agent check 2025-01-08 17:26:14 +01:00
Krzysztof Czerwinski
725b38425a Remove unused files 2025-01-08 16:25:33 +01:00
Krzysztof Czerwinski
250ffdfaed Redirect to Builder after adding to library 2025-01-08 16:25:25 +01:00
6 changed files with 217 additions and 170 deletions

View File

@@ -99,6 +99,65 @@ async def create_library_agent(
) from e
async def get_library_agent(
store_listing_version_id: str, user_id: str
) -> backend.data.graph.Graph | None:
"""
Get user agent from the store listing version
"""
logger.debug(
f"Getting agent by store listing version {store_listing_version_id} for user {user_id}"
)
try:
# Get store listing version to find agent
store_listing_version = (
await prisma.models.StoreListingVersion.prisma().find_unique(
where={"id": store_listing_version_id}, include={"Agent": True}
)
)
if not store_listing_version or not store_listing_version.Agent:
logger.warning(
f"Store listing version not found: {store_listing_version_id}"
)
raise backend.server.v2.store.exceptions.AgentNotFoundError(
f"Store listing version {store_listing_version_id} not found"
)
agent = store_listing_version.Agent
# Check if user already has this agent
existing_user_agent = await prisma.models.AgentGraph.prisma().find_first(
where={
"userId": user_id,
"id": agent.id,
"version": agent.version,
}
)
if existing_user_agent:
logger.debug(f"User {user_id} has agent {agent.id} in their library")
return backend.data.graph.Graph(
id=agent.id,
version=agent.version,
is_active=agent.isActive,
name=agent.name or "",
description=agent.description or "",
)
logger.debug(f"User {user_id} does not have agent {agent.id} in their library")
return None
except backend.server.v2.store.exceptions.AgentNotFoundError:
raise
except prisma.errors.PrismaError as e:
logger.error(f"Database error checking library agent: {str(e)}")
raise backend.server.v2.store.exceptions.DatabaseError(
"Failed to check library agent"
) from e
async def update_agent_version_in_library(
user_id: str, agent_id: str, agent_version: int
) -> None:
@@ -158,7 +217,7 @@ async def update_library_agent(
async def add_store_agent_to_library(
store_listing_version_id: str, user_id: str
) -> None:
) -> backend.data.graph.Graph | None:
"""
Finds the agent from the store listing version and adds it to the user's library (LibraryAgent table)
if they don't already have it
@@ -206,10 +265,16 @@ async def add_store_agent_to_library(
logger.debug(
f"User {user_id} already has agent {agent.id} in their library"
)
return
return backend.data.graph.Graph(
id=agent.id,
version=agent.version,
is_active=agent.isActive,
name=agent.name or "",
description=agent.description or "",
)
# Create LibraryAgent entry
await prisma.models.LibraryAgent.prisma().create(
library_agent = await prisma.models.LibraryAgent.prisma().create(
data=prisma.types.LibraryAgentCreateInput(
userId=user_id,
agentId=agent.id,
@@ -218,6 +283,13 @@ async def add_store_agent_to_library(
)
)
logger.debug(f"Added agent {agent.id} to library for user {user_id}")
return backend.data.graph.Graph(
id=library_agent.agentId,
version=library_agent.agentVersion,
is_active=agent.isActive,
name=agent.name or "",
description=agent.description or "",
)
except backend.server.v2.store.exceptions.AgentNotFoundError:
raise

View File

@@ -6,6 +6,8 @@ import autogpt_libs.auth.middleware
import autogpt_libs.utils.cache
import fastapi
import backend.data
import backend.data.graph
import backend.server.v2.library.db
import backend.server.v2.library.model
import backend.server.v2.store.exceptions
@@ -38,6 +40,43 @@ async def get_library_agents(
)
@router.get(
"/agents/{store_listing_version_id}",
tags=["library", "private"],
dependencies=[fastapi.Depends(autogpt_libs.auth.middleware.auth_middleware)],
)
async def get_library_agent(
store_listing_version_id: str,
user_id: typing.Annotated[
str, fastapi.Depends(autogpt_libs.auth.depends.get_user_id)
],
) -> backend.data.graph.Graph | None:
"""
Get an agent from the user's library by store listing version ID.
Args:
store_listing_version_id (str): ID of the store listing version to get
user_id (str): ID of the authenticated user
Returns:
backend.data.graph.Graph: Agent from the user's library
None: If the agent is not found in the user's library
Raises:
HTTPException: If there is an error getting the agent from the library
"""
try:
agent = await backend.server.v2.library.db.get_library_agent(
store_listing_version_id, user_id
)
return agent
except Exception:
logger.exception("Exception occurred whilst getting library agent")
raise fastapi.HTTPException(
status_code=500, detail="Failed to get library agent"
)
@router.post(
"/agents/{store_listing_version_id}",
tags=["library", "private"],
@@ -49,7 +88,7 @@ async def add_agent_to_library(
user_id: typing.Annotated[
str, fastapi.Depends(autogpt_libs.auth.depends.get_user_id)
],
) -> fastapi.Response:
) -> backend.data.graph.Graph | None:
"""
Add an agent from the store to the user's library.
@@ -58,17 +97,17 @@ async def add_agent_to_library(
user_id (str): ID of the authenticated user
Returns:
fastapi.Response: 201 status code on success
backend.data.graph.Graph: Agent added to the user's library
None: On failure
Raises:
HTTPException: If there is an error adding the agent to the library
"""
try:
# Use the database function to add the agent to the library
await backend.server.v2.library.db.add_store_agent_to_library(
return await backend.server.v2.library.db.add_store_agent_to_library(
store_listing_version_id, user_id
)
return fastapi.Response(status_code=201)
except backend.server.v2.store.exceptions.AgentNotFoundError:
raise fastapi.HTTPException(

View File

@@ -3,13 +3,14 @@
import * as React from "react";
import { IconPlay, StarRatingIcons } from "@/components/ui/icons";
import { Separator } from "@/components/ui/separator";
import BackendAPI from "@/lib/autogpt-server-api";
import BackendAPI, { GraphMeta } from "@/lib/autogpt-server-api";
import { useRouter } from "next/navigation";
import Link from "next/link";
import { useToast } from "@/components/ui/use-toast";
import useSupabase from "@/hooks/useSupabase";
import { DownloadIcon, LoaderIcon } from "lucide-react";
import { DownloadIcon, LoaderIcon, CheckIcon } from "lucide-react";
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
interface AgentInfoProps {
name: string;
creator: string;
@@ -36,61 +37,88 @@ export const AgentInfo: React.FC<AgentInfoProps> = ({
storeListingVersionId,
}) => {
const router = useRouter();
const api = React.useMemo(() => new BackendAPI(), []);
const api = useBackendAPI();
const { user } = useSupabase();
const { toast } = useToast();
const [userAgent, setUserAgent] = React.useState<GraphMeta | null>(null);
// Either downloading or adding to library
const [processing, setProcessing] = React.useState(false);
const [downloading, setDownloading] = React.useState(false);
React.useEffect(() => {
const fetchAgent = async () => {
try {
const agent = await api.getUserLibraryAgent(storeListingVersionId);
setUserAgent(agent);
} catch (error) {
console.error("Failed to fetch library agent:", error);
}
};
fetchAgent();
}, [api, storeListingVersionId]);
const handleAddToLibrary = React.useCallback(async () => {
if (!user || userAgent) {
return;
}
toast({
title: "Adding to Library",
description: "Adding agent to library and opening builder...",
duration: 2000,
});
setProcessing(true);
const handleAddToLibrary = async () => {
try {
await api.addAgentToLibrary(storeListingVersionId);
const agent = await api.addAgentToLibrary(storeListingVersionId);
if (!agent) {
throw new Error();
}
console.log("Agent added to library successfully");
router.push("/monitoring");
router.push(`/build?flowID=${agent.id}`);
} catch (error) {
console.error("Failed to add agent to library:", error);
}
};
setProcessing(false);
}, [api, router, storeListingVersionId, toast, user, userAgent]);
const handleDownloadToLibrary = async () => {
const downloadAgent = async (): Promise<void> => {
setDownloading(true);
try {
const file = await api.downloadStoreAgent(storeListingVersionId);
const handleDownloadToLibrary = React.useCallback(async () => {
setProcessing(true);
try {
const file = await api.downloadStoreAgent(storeListingVersionId);
// Similar to Marketplace v1
const jsonData = JSON.stringify(file, null, 2);
// Create a Blob from the file content
const blob = new Blob([jsonData], { type: "application/json" });
// Similar to Marketplace v1
const jsonData = JSON.stringify(file, null, 2);
// Create a Blob from the file content
const blob = new Blob([jsonData], { type: "application/json" });
// Create a temporary URL for the Blob
const url = window.URL.createObjectURL(blob);
// Create a temporary URL for the Blob
const url = window.URL.createObjectURL(blob);
// Create a temporary anchor element
const a = document.createElement("a");
a.href = url;
a.download = `agent_${storeListingVersionId}.json`; // Set the filename
// Create a temporary anchor element
const a = document.createElement("a");
a.href = url;
a.download = `agent_${storeListingVersionId}.json`; // Set the filename
// Append the anchor to the body, click it, and remove it
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
// Append the anchor to the body, click it, and remove it
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
// Revoke the temporary URL
window.URL.revokeObjectURL(url);
// Revoke the temporary URL
window.URL.revokeObjectURL(url);
toast({
title: "Download Complete",
description: "Your agent has been successfully downloaded.",
});
} catch (error) {
console.error(`Error downloading agent:`, error);
throw error;
}
};
await downloadAgent();
setDownloading(false);
};
toast({
title: "Download Complete",
description: "Your agent has been successfully downloaded.",
duration: 2000,
});
} catch (error) {
console.error(`Error downloading agent:`, error);
throw error;
}
setProcessing(false);
}, [api, storeListingVersionId, toast]);
return (
<div className="w-full max-w-[396px] px-4 sm:px-6 lg:w-[396px] lg:px-0">
@@ -123,29 +151,40 @@ export const AgentInfo: React.FC<AgentInfoProps> = ({
<button
onClick={handleAddToLibrary}
className="inline-flex w-full items-center justify-center gap-2 rounded-[38px] bg-violet-600 px-4 py-3 transition-colors hover:bg-violet-700 sm:w-auto sm:gap-2.5 sm:px-5 sm:py-3.5 lg:px-6 lg:py-4"
disabled={processing || userAgent !== null}
>
<IconPlay className="h-5 w-5 text-white sm:h-5 sm:w-5 lg:h-6 lg:w-6" />
{processing ? (
<LoaderIcon className="h-5 w-5 animate-spin text-white sm:h-5 sm:w-5 lg:h-6 lg:w-6" />
) : userAgent ? (
<CheckIcon className="h-5 w-5 text-white sm:h-5 sm:w-5 lg:h-6 lg:w-6" />
) : (
<IconPlay className="h-5 w-5 text-white sm:h-5 sm:w-5 lg:h-6 lg:w-6" />
)}
<span className="font-poppins text-base font-medium text-neutral-50 sm:text-lg">
Add To Library
{processing
? "Adding to Library..."
: userAgent
? "Already in Library"
: "Add Agent to Library"}
</span>
</button>
) : (
<button
onClick={handleDownloadToLibrary}
className={`inline-flex w-full items-center justify-center gap-2 rounded-[38px] px-4 py-3 transition-colors sm:w-auto sm:gap-2.5 sm:px-5 sm:py-3.5 lg:px-6 lg:py-4 ${
downloading
processing
? "bg-neutral-400"
: "bg-violet-600 hover:bg-violet-700"
}`}
disabled={downloading}
disabled={processing}
>
{downloading ? (
{processing ? (
<LoaderIcon className="h-5 w-5 animate-spin text-white sm:h-5 sm:w-5 lg:h-6 lg:w-6" />
) : (
<DownloadIcon className="h-5 w-5 text-white sm:h-5 sm:w-5 lg:h-6 lg:w-6" />
)}
<span className="font-poppins text-base font-medium text-neutral-50 sm:text-lg">
{downloading ? "Downloading..." : "Download Agent as File"}
{processing ? "Downloading..." : "Download Agent as File"}
</span>
</button>
)}

View File

@@ -1,96 +0,0 @@
// "use client";
// import Link from "next/link";
// import { ArrowLeft, Download, Calendar, Tag } from "lucide-react";
// import { Button } from "@/components/ui/button";
// import BackendAPI, { GraphCreatable } from "@/lib/autogpt-server-api";
// import "@xyflow/react/dist/style.css";
// import { useToast } from "../ui/use-toast";
// function AgentDetailContent({ agent }: { agent: GraphCreatable }) {
// const { toast } = useToast();
// // const downloadAgent = async (id: string): Promise<void> => {
// // const api = new MarketplaceAPI();
// // try {
// // const file = await api.downloadAgentFile(id);
// // console.debug(`Agent file downloaded:`, file);
// // // Create a Blob from the file content
// // const blob = new Blob([file], { type: "application/json" });
// // // Create a temporary URL for the Blob
// // const url = window.URL.createObjectURL(blob);
// // // Create a temporary anchor element
// // const a = document.createElement("a");
// // a.href = url;
// // a.download = `agent_${id}.json`; // Set the filename
// // // Append the anchor to the body, click it, and remove it
// // document.body.appendChild(a);
// // a.click();
// // document.body.removeChild(a);
// // // Revoke the temporary URL
// // window.URL.revokeObjectURL(url);
// // } catch (error) {
// // console.error(`Error downloading agent:`, error);
// // throw error;
// // }
// // };
// return (
// <div className="mx-auto max-w-7xl px-4 py-4 sm:px-6 lg:px-8">
// <div className="mb-4 flex items-center justify-between">
// <Link
// href="/marketplace"
// className="inline-flex items-center text-indigo-600 hover:text-indigo-500"
// >
// <ArrowLeft className="mr-2" size={20} />
// Back to Marketplace
// </Link>
// <div className="flex space-x-4">
// <Button
// onClick={() => downloadAgent(agent.id)}
// className="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
// >
// <Download className="mr-2" size={16} />
// Download Agent
// </Button>
// </div>
// </div>
// <div className="overflow-hidden bg-white shadow sm:rounded-lg">
// <div className="px-4 py-5 sm:px-6">
// <h1 className="text-3xl font-bold text-gray-900">{agent.name}</h1>
// <p className="mt-1 max-w-2xl text-sm text-gray-500">
// {agent.description}
// </p>
// </div>
// <div className="border-t border-gray-300 px-4 py-5 sm:p-0">
// <dl className="sm:divide-y sm:divide-gray-200">
// <div className="py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
// <dt className="flex items-center text-sm font-medium text-gray-500">
// <Calendar className="mr-2" size={16} />
// Last Updated
// </dt>
// <dd className="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
// {new Date(agent.updatedAt).toLocaleDateString()}
// </dd>
// </div>
// <div className="py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
// <dt className="flex items-center text-sm font-medium text-gray-500">
// <Tag className="mr-2" size={16} />
// Categories
// </dt>
// <dd className="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
// {agent.categories.join(", ")}
// </dd>
// </div>
// </dl>
// </div>
// </div>
// </div>
// );
// }
// export default AgentDetailContent;

View File

@@ -1,18 +0,0 @@
// "use server";
// import * as Sentry from "@sentry/nextjs";
// import MarketplaceAPI, { AnalyticsEvent } from "@/lib/marketplace-api";
// import { checkAuth } from "@/lib/supabase/server";
// export async function makeAnalyticsEvent(event: AnalyticsEvent) {
// return await Sentry.withServerActionInstrumentation(
// "makeAnalyticsEvent",
// {},
// async () => {
// await checkAuth();
// const apiUrl = process.env.AGPT_SERVER_API_URL;
// const api = new MarketplaceAPI();
// await api.makeAnalyticsEvent(event);
// },
// );
// }

View File

@@ -400,8 +400,19 @@ export default class BackendAPI {
return this._get("/library/agents");
}
async addAgentToLibrary(storeListingVersionId: string): Promise<void> {
await this._request("POST", `/library/agents/${storeListingVersionId}`);
getUserLibraryAgent(
storeListingVersionId: string,
): Promise<GraphMeta | null> {
return this._get(`/library/agents/${storeListingVersionId}`);
}
async addAgentToLibrary(
storeListingVersionId: string,
): Promise<GraphMeta | null> {
return await this._request(
"POST",
`/library/agents/${storeListingVersionId}`,
);
}
///////////////////////////////////////////