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(platform): Redirect to builder after saving agent to library and disable save button for owned agents #9225

Open
wants to merge 7 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
78 changes: 75 additions & 3 deletions autogpt_platform/backend/backend/server/v2/library/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
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
Expand Down Expand Up @@ -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"],
Expand All @@ -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.

Expand All @@ -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(
Expand Down
143 changes: 91 additions & 52 deletions autogpt_platform/frontend/src/components/agptui/AgentInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
}
};

const handleDownloadToLibrary = async () => {
const downloadAgent = async (): Promise<void> => {
setDownloading(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" });

// Create a temporary URL for the Blob
const url = window.URL.createObjectURL(blob);
setProcessing(false);
}, [api, router, storeListingVersionId, toast, user, userAgent]);

// 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);
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" });

// 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

// 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);

toast({
title: "Download Complete",
description: "Your agent has been successfully downloaded.",
duration: 2000,
});
} catch (error) {
console.error(`Error downloading agent:`, error);
throw error;
}

// 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);
};
setProcessing(false);
}, [api, storeListingVersionId, toast]);

return (
<div className="w-full max-w-[396px] px-4 sm:px-6 lg:w-[396px] lg:px-0">
Expand Down Expand Up @@ -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>
)}
Expand Down
Loading
Loading