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

fix(store) : Download agent from store if user is not logged in #9121

Open
wants to merge 4 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
46 changes: 46 additions & 0 deletions autogpt_platform/backend/backend/server/v2/store/db.py
Original file line number Diff line number Diff line change
@@ -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__)

Expand Down Expand Up @@ -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
49 changes: 49 additions & 0 deletions autogpt_platform/backend/backend/server/v2/store/routes.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import json
import logging
import tempfile
import typing
import urllib.parse

import autogpt_libs.auth.depends
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
Expand Down Expand Up @@ -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"
)
88 changes: 78 additions & 10 deletions autogpt_platform/frontend/src/components/agptui/AgentInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -32,8 +36,11 @@ export const AgentInfo: React.FC<AgentInfoProps> = ({
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 {
Expand All @@ -45,6 +52,46 @@ export const AgentInfo: React.FC<AgentInfoProps> = ({
}
};

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

// 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 (
<div className="w-full max-w-[396px] px-4 sm:px-6 lg:w-[396px] lg:px-0">
{/* Title */}
Expand Down Expand Up @@ -72,15 +119,36 @@ export const AgentInfo: React.FC<AgentInfoProps> = ({

{/* Run Agent Button */}
<div className="mb-4 w-full lg:mb-[60px]">
<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"
>
<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
</span>
</button>
{user ? (
<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"
>
<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
</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
? "bg-neutral-400"
: "bg-violet-600 hover:bg-violet-700"
}`}
disabled={downloading}
>
{downloading ? (
<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"}
</span>
</button>
)}
</div>

{/* Rating and Runs */}
Expand Down
13 changes: 12 additions & 1 deletion autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ export default class BackendAPI {
"/store/submissions/generate_image?agent_id=" + agent_id,
);
}
c;

deleteStoreSubmission(submission_id: string): Promise<boolean> {
return this._request("DELETE", `/store/submissions/${submission_id}`);
}
Expand Down Expand Up @@ -348,6 +348,17 @@ export default class BackendAPI {
return this._get("/store/myagents", params);
}

downloadStoreAgent(
storeListingVersionId: string,
version?: number,
): Promise<BlobPart> {
const url = version
? `/store/download/agents/${storeListingVersionId}?version=${version}`
: `/store/download/agents/${storeListingVersionId}`;

return this._get(url);
}

/////////////////////////////////////////
/////////// V2 LIBRARY API //////////////
/////////////////////////////////////////
Expand Down
Loading