diff --git a/autogpt_platform/backend/backend/server/v2/store/db.py b/autogpt_platform/backend/backend/server/v2/store/db.py index f3536326c28a..1ad1cff67edb 100644 --- a/autogpt_platform/backend/backend/server/v2/store/db.py +++ b/autogpt_platform/backend/backend/server/v2/store/db.py @@ -1,14 +1,18 @@ import logging import random from datetime import datetime +from typing import Optional +import fastapi import prisma.enums import prisma.errors import prisma.models import prisma.types +import backend.data.graph import backend.server.v2.store.exceptions import backend.server.v2.store.model +from backend.data.graph import GraphModel logger = logging.getLogger(__name__) @@ -776,3 +780,45 @@ async def get_my_agents( raise backend.server.v2.store.exceptions.DatabaseError( "Failed to fetch my agents" ) from e + + +async def get_agent( + store_listing_version_id: str, version_id: Optional[int] +) -> GraphModel: + """Get agent using the version ID and store listing version ID.""" + try: + 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: + raise fastapi.HTTPException( + status_code=404, + detail=f"Store listing version {store_listing_version_id} not found", + ) + + agent = store_listing_version.Agent + + graph = await backend.data.graph.get_graph( + agent.id, agent.version, template=True + ) + + if not graph: + raise fastapi.HTTPException( + status_code=404, detail=f"Agent {agent.id} not found" + ) + + graph.version = 1 + graph.is_template = False + graph.is_active = True + delattr(graph, "user_id") + + return graph + + except Exception as e: + logger.error(f"Error getting agent: {str(e)}") + raise backend.server.v2.store.exceptions.DatabaseError( + "Failed to fetch agent" + ) from e diff --git a/autogpt_platform/backend/backend/server/v2/store/routes.py b/autogpt_platform/backend/backend/server/v2/store/routes.py index 0ef5815afede..8011970355b9 100644 --- a/autogpt_platform/backend/backend/server/v2/store/routes.py +++ b/autogpt_platform/backend/backend/server/v2/store/routes.py @@ -1,4 +1,6 @@ +import json import logging +import tempfile import typing import urllib.parse @@ -6,6 +8,7 @@ import autogpt_libs.auth.middleware import fastapi import fastapi.responses +from fastapi.encoders import jsonable_encoder import backend.data.graph import backend.server.v2.store.db @@ -508,3 +511,49 @@ async def generate_image( raise fastapi.HTTPException( status_code=500, detail=f"Failed to generate image: {str(e)}" ) + + +@router.get( + "/download/agents/{store_listing_version_id}", + tags=["store", "public"], +) +async def download_agent_file( + store_listing_version_id: str = fastapi.Path( + ..., description="The ID of the agent to download" + ), + version: typing.Optional[int] = fastapi.Query( + None, description="Specific version of the agent" + ), +) -> fastapi.responses.FileResponse: + """ + Download the agent file by streaming its content. + + Args: + agent_id (str): The ID of the agent to download. + version (Optional[int]): Specific version of the agent to download. + + Returns: + StreamingResponse: A streaming response containing the agent's graph data. + + Raises: + HTTPException: If the agent is not found or an unexpected error occurs. + """ + + graph_data = await backend.server.v2.store.db.get_agent( + store_listing_version_id=store_listing_version_id, version_id=version + ) + + graph_date_dict = jsonable_encoder(graph_data) + + file_name = f"agent_{store_listing_version_id}_v{version or 'latest'}.json" + + # Sending graph as a stream (similar to marketplace v1) + with tempfile.NamedTemporaryFile( + mode="w", suffix=".json", delete=False + ) as tmp_file: + tmp_file.write(json.dumps(graph_date_dict)) + tmp_file.flush() + + return fastapi.responses.FileResponse( + tmp_file.name, filename=file_name, media_type="application/json" + ) diff --git a/autogpt_platform/frontend/src/components/agptui/AgentInfo.tsx b/autogpt_platform/frontend/src/components/agptui/AgentInfo.tsx index fe12e75b4979..03f7d141d7a8 100644 --- a/autogpt_platform/frontend/src/components/agptui/AgentInfo.tsx +++ b/autogpt_platform/frontend/src/components/agptui/AgentInfo.tsx @@ -6,6 +6,10 @@ import { Separator } from "@/components/ui/separator"; import BackendAPI 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"; interface AgentInfoProps { name: string; creator: string; @@ -32,8 +36,11 @@ export const AgentInfo: React.FC = ({ storeListingVersionId, }) => { const router = useRouter(); - const api = React.useMemo(() => new BackendAPI(), []); + const { user } = useSupabase(); + const { toast } = useToast(); + + const [downloading, setDownloading] = React.useState(false); const handleAddToLibrary = async () => { try { @@ -45,6 +52,46 @@ export const AgentInfo: React.FC = ({ } }; + const handleDownloadToLibrary = async () => { + const downloadAgent = async (): Promise => { + 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); + + // 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.", + }); + } catch (error) { + console.error(`Error downloading agent:`, error); + throw error; + } + }; + await downloadAgent(); + setDownloading(false); + }; + return (
{/* Title */} @@ -72,15 +119,36 @@ export const AgentInfo: React.FC = ({ {/* Run Agent Button */}
- + {user ? ( + + ) : ( + + )}
{/* Rating and Runs */} diff --git a/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts b/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts index fcfbaa7014d6..1f74e67f636a 100644 --- a/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts +++ b/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts @@ -311,7 +311,7 @@ export default class BackendAPI { "/store/submissions/generate_image?agent_id=" + agent_id, ); } - c; + deleteStoreSubmission(submission_id: string): Promise { return this._request("DELETE", `/store/submissions/${submission_id}`); } @@ -348,6 +348,17 @@ export default class BackendAPI { return this._get("/store/myagents", params); } + downloadStoreAgent( + storeListingVersionId: string, + version?: number, + ): Promise { + const url = version + ? `/store/download/agents/${storeListingVersionId}?version=${version}` + : `/store/download/agents/${storeListingVersionId}`; + + return this._get(url); + } + ///////////////////////////////////////// /////////// V2 LIBRARY API ////////////// /////////////////////////////////////////