Compare commits
11 Commits
master
...
kpczerwins
Author | SHA1 | Date |
---|---|---|
![]() |
f92dcaaf1b | |
![]() |
f5e7a1d4a7 | |
![]() |
fd6f28fa57 | |
![]() |
4b17cc9963 | |
![]() |
00bb7c67b3 | |
![]() |
786e890cb5 | |
![]() |
f4f5c9af80 | |
![]() |
04f325cc0f | |
![]() |
720841b1df | |
![]() |
67c9b757da | |
![]() |
593ad0222e |
|
@ -136,6 +136,7 @@ async def create_graph_execution(
|
||||||
graph_version: int,
|
graph_version: int,
|
||||||
nodes_input: list[tuple[str, BlockInput]],
|
nodes_input: list[tuple[str, BlockInput]],
|
||||||
user_id: str,
|
user_id: str,
|
||||||
|
preset_id: str | None = None,
|
||||||
) -> tuple[str, list[ExecutionResult]]:
|
) -> tuple[str, list[ExecutionResult]]:
|
||||||
"""
|
"""
|
||||||
Create a new AgentGraphExecution record.
|
Create a new AgentGraphExecution record.
|
||||||
|
@ -163,6 +164,7 @@ async def create_graph_execution(
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"userId": user_id,
|
"userId": user_id,
|
||||||
|
"agentPresetId": preset_id,
|
||||||
},
|
},
|
||||||
include=GRAPH_EXECUTION_INCLUDE,
|
include=GRAPH_EXECUTION_INCLUDE,
|
||||||
)
|
)
|
||||||
|
|
|
@ -6,7 +6,13 @@ from datetime import datetime, timezone
|
||||||
from typing import Any, Literal, Optional, Type
|
from typing import Any, Literal, Optional, Type
|
||||||
|
|
||||||
import prisma
|
import prisma
|
||||||
from prisma.models import AgentGraph, AgentGraphExecution, AgentNode, AgentNodeLink
|
from prisma.models import (
|
||||||
|
AgentGraph,
|
||||||
|
AgentGraphExecution,
|
||||||
|
AgentNode,
|
||||||
|
AgentNodeLink,
|
||||||
|
StoreListingVersion,
|
||||||
|
)
|
||||||
from prisma.types import AgentGraphWhereInput
|
from prisma.types import AgentGraphWhereInput
|
||||||
from pydantic.fields import computed_field
|
from pydantic.fields import computed_field
|
||||||
|
|
||||||
|
@ -529,7 +535,6 @@ async def get_execution(user_id: str, execution_id: str) -> GraphExecution | Non
|
||||||
async def get_graph(
|
async def get_graph(
|
||||||
graph_id: str,
|
graph_id: str,
|
||||||
version: int | None = None,
|
version: int | None = None,
|
||||||
template: bool = False,
|
|
||||||
user_id: str | None = None,
|
user_id: str | None = None,
|
||||||
for_export: bool = False,
|
for_export: bool = False,
|
||||||
) -> GraphModel | None:
|
) -> GraphModel | None:
|
||||||
|
@ -543,21 +548,36 @@ async def get_graph(
|
||||||
where_clause: AgentGraphWhereInput = {
|
where_clause: AgentGraphWhereInput = {
|
||||||
"id": graph_id,
|
"id": graph_id,
|
||||||
}
|
}
|
||||||
|
|
||||||
if version is not None:
|
if version is not None:
|
||||||
where_clause["version"] = version
|
where_clause["version"] = version
|
||||||
elif not template:
|
else:
|
||||||
where_clause["isActive"] = True
|
where_clause["isActive"] = True
|
||||||
|
|
||||||
# TODO: Fix hack workaround to get adding store agents to work
|
|
||||||
if user_id is not None and not template:
|
|
||||||
where_clause["userId"] = user_id
|
|
||||||
|
|
||||||
graph = await AgentGraph.prisma().find_first(
|
graph = await AgentGraph.prisma().find_first(
|
||||||
where=where_clause,
|
where=where_clause,
|
||||||
include=AGENT_GRAPH_INCLUDE,
|
include=AGENT_GRAPH_INCLUDE,
|
||||||
order={"version": "desc"},
|
order={"version": "desc"},
|
||||||
)
|
)
|
||||||
return GraphModel.from_db(graph, for_export) if graph else None
|
|
||||||
|
# The Graph has to be owned by the user or a store listing.
|
||||||
|
if (
|
||||||
|
graph is None
|
||||||
|
or graph.userId != user_id
|
||||||
|
and not (
|
||||||
|
await StoreListingVersion.prisma().find_first(
|
||||||
|
where=prisma.types.StoreListingVersionWhereInput(
|
||||||
|
agentId=graph_id,
|
||||||
|
agentVersion=version or graph.version,
|
||||||
|
isDeleted=False,
|
||||||
|
StoreListing={"is": {"isApproved": True}},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
):
|
||||||
|
return None
|
||||||
|
|
||||||
|
return GraphModel.from_db(graph, for_export)
|
||||||
|
|
||||||
|
|
||||||
async def set_graph_active_version(graph_id: str, version: int, user_id: str) -> None:
|
async def set_graph_active_version(graph_id: str, version: int, user_id: str) -> None:
|
||||||
|
@ -611,9 +631,7 @@ async def create_graph(graph: Graph, user_id: str) -> GraphModel:
|
||||||
async with transaction() as tx:
|
async with transaction() as tx:
|
||||||
await __create_graph(tx, graph, user_id)
|
await __create_graph(tx, graph, user_id)
|
||||||
|
|
||||||
if created_graph := await get_graph(
|
if created_graph := await get_graph(graph.id, graph.version, user_id=user_id):
|
||||||
graph.id, graph.version, graph.is_template, user_id=user_id
|
|
||||||
):
|
|
||||||
return created_graph
|
return created_graph
|
||||||
|
|
||||||
raise ValueError(f"Created graph {graph.id} v{graph.version} is not in DB")
|
raise ValueError(f"Created graph {graph.id} v{graph.version} is not in DB")
|
||||||
|
|
|
@ -780,7 +780,8 @@ class ExecutionManager(AppService):
|
||||||
graph_id: str,
|
graph_id: str,
|
||||||
data: BlockInput,
|
data: BlockInput,
|
||||||
user_id: str,
|
user_id: str,
|
||||||
graph_version: int | None = None,
|
graph_version: int,
|
||||||
|
preset_id: str | None = None,
|
||||||
) -> GraphExecutionEntry:
|
) -> GraphExecutionEntry:
|
||||||
graph: GraphModel | None = self.db_client.get_graph(
|
graph: GraphModel | None = self.db_client.get_graph(
|
||||||
graph_id=graph_id, user_id=user_id, version=graph_version
|
graph_id=graph_id, user_id=user_id, version=graph_version
|
||||||
|
@ -829,6 +830,7 @@ class ExecutionManager(AppService):
|
||||||
graph_version=graph.version,
|
graph_version=graph.version,
|
||||||
nodes_input=nodes_input,
|
nodes_input=nodes_input,
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
|
preset_id=preset_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
starting_node_execs = []
|
starting_node_execs = []
|
||||||
|
|
|
@ -63,7 +63,10 @@ def execute_graph(**kwargs):
|
||||||
try:
|
try:
|
||||||
log(f"Executing recurring job for graph #{args.graph_id}")
|
log(f"Executing recurring job for graph #{args.graph_id}")
|
||||||
get_execution_client().add_execution(
|
get_execution_client().add_execution(
|
||||||
args.graph_id, args.input_data, args.user_id
|
graph_id=args.graph_id,
|
||||||
|
data=args.input_data,
|
||||||
|
user_id=args.user_id,
|
||||||
|
graph_version=args.graph_version,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(f"Error executing graph {args.graph_id}: {e}")
|
logger.exception(f"Error executing graph {args.graph_id}: {e}")
|
||||||
|
|
|
@ -320,7 +320,8 @@ async def webhook_ingress_generic(
|
||||||
continue
|
continue
|
||||||
logger.debug(f"Executing graph #{node.graph_id} node #{node.id}")
|
logger.debug(f"Executing graph #{node.graph_id} node #{node.id}")
|
||||||
executor.add_execution(
|
executor.add_execution(
|
||||||
node.graph_id,
|
graph_id=node.graph_id,
|
||||||
|
graph_version=node.graph_version,
|
||||||
data={f"webhook_{webhook_id}_payload": payload},
|
data={f"webhook_{webhook_id}_payload": payload},
|
||||||
user_id=webhook.user_id,
|
user_id=webhook.user_id,
|
||||||
)
|
)
|
||||||
|
|
|
@ -56,3 +56,18 @@ class SetGraphActiveVersion(pydantic.BaseModel):
|
||||||
|
|
||||||
class UpdatePermissionsRequest(pydantic.BaseModel):
|
class UpdatePermissionsRequest(pydantic.BaseModel):
|
||||||
permissions: List[APIKeyPermission]
|
permissions: List[APIKeyPermission]
|
||||||
|
|
||||||
|
|
||||||
|
class Pagination(pydantic.BaseModel):
|
||||||
|
total_items: int = pydantic.Field(
|
||||||
|
description="Total number of items.", examples=[42]
|
||||||
|
)
|
||||||
|
total_pages: int = pydantic.Field(
|
||||||
|
description="Total number of pages.", examples=[2]
|
||||||
|
)
|
||||||
|
current_page: int = pydantic.Field(
|
||||||
|
description="Current_page page number.", examples=[1]
|
||||||
|
)
|
||||||
|
page_size: int = pydantic.Field(
|
||||||
|
description="Number of items per page.", examples=[25]
|
||||||
|
)
|
||||||
|
|
|
@ -2,6 +2,7 @@ import contextlib
|
||||||
import logging
|
import logging
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
|
import autogpt_libs.auth.models
|
||||||
import fastapi
|
import fastapi
|
||||||
import fastapi.responses
|
import fastapi.responses
|
||||||
import starlette.middleware.cors
|
import starlette.middleware.cors
|
||||||
|
@ -16,7 +17,9 @@ import backend.data.db
|
||||||
import backend.data.graph
|
import backend.data.graph
|
||||||
import backend.data.user
|
import backend.data.user
|
||||||
import backend.server.routers.v1
|
import backend.server.routers.v1
|
||||||
|
import backend.server.v2.library.model
|
||||||
import backend.server.v2.library.routes
|
import backend.server.v2.library.routes
|
||||||
|
import backend.server.v2.store.model
|
||||||
import backend.server.v2.store.routes
|
import backend.server.v2.store.routes
|
||||||
import backend.util.service
|
import backend.util.service
|
||||||
import backend.util.settings
|
import backend.util.settings
|
||||||
|
@ -117,9 +120,24 @@ class AgentServer(backend.util.service.AppProcess):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def test_execute_graph(
|
async def test_execute_graph(
|
||||||
graph_id: str, node_input: dict[typing.Any, typing.Any], user_id: str
|
graph_id: str,
|
||||||
|
graph_version: int,
|
||||||
|
node_input: dict[typing.Any, typing.Any],
|
||||||
|
user_id: str,
|
||||||
):
|
):
|
||||||
return backend.server.routers.v1.execute_graph(graph_id, node_input, user_id)
|
return backend.server.routers.v1.execute_graph(
|
||||||
|
graph_id, graph_version, node_input, user_id
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def test_get_graph(
|
||||||
|
graph_id: str,
|
||||||
|
graph_version: int,
|
||||||
|
user_id: str,
|
||||||
|
):
|
||||||
|
return await backend.server.routers.v1.get_graph(
|
||||||
|
graph_id, user_id, graph_version
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def test_create_graph(
|
async def test_create_graph(
|
||||||
|
@ -149,5 +167,71 @@ class AgentServer(backend.util.service.AppProcess):
|
||||||
async def test_delete_graph(graph_id: str, user_id: str):
|
async def test_delete_graph(graph_id: str, user_id: str):
|
||||||
return await backend.server.routers.v1.delete_graph(graph_id, user_id)
|
return await backend.server.routers.v1.delete_graph(graph_id, user_id)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def test_get_presets(user_id: str, page: int = 1, page_size: int = 10):
|
||||||
|
return await backend.server.v2.library.routes.presets.get_presets(
|
||||||
|
user_id=user_id, page=page, page_size=page_size
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def test_get_preset(preset_id: str, user_id: str):
|
||||||
|
return await backend.server.v2.library.routes.presets.get_preset(
|
||||||
|
preset_id=preset_id, user_id=user_id
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def test_create_preset(
|
||||||
|
preset: backend.server.v2.library.model.CreateLibraryAgentPresetRequest,
|
||||||
|
user_id: str,
|
||||||
|
):
|
||||||
|
return await backend.server.v2.library.routes.presets.create_preset(
|
||||||
|
preset=preset, user_id=user_id
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def test_update_preset(
|
||||||
|
preset_id: str,
|
||||||
|
preset: backend.server.v2.library.model.CreateLibraryAgentPresetRequest,
|
||||||
|
user_id: str,
|
||||||
|
):
|
||||||
|
return await backend.server.v2.library.routes.presets.update_preset(
|
||||||
|
preset_id=preset_id, preset=preset, user_id=user_id
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def test_delete_preset(preset_id: str, user_id: str):
|
||||||
|
return await backend.server.v2.library.routes.presets.delete_preset(
|
||||||
|
preset_id=preset_id, user_id=user_id
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def test_execute_preset(
|
||||||
|
graph_id: str,
|
||||||
|
graph_version: int,
|
||||||
|
preset_id: str,
|
||||||
|
node_input: dict[typing.Any, typing.Any],
|
||||||
|
user_id: str,
|
||||||
|
):
|
||||||
|
return await backend.server.v2.library.routes.presets.execute_preset(
|
||||||
|
graph_id=graph_id,
|
||||||
|
graph_version=graph_version,
|
||||||
|
preset_id=preset_id,
|
||||||
|
node_input=node_input,
|
||||||
|
user_id=user_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def test_create_store_listing(
|
||||||
|
request: backend.server.v2.store.model.StoreSubmissionRequest, user_id: str
|
||||||
|
):
|
||||||
|
return await backend.server.v2.store.routes.create_submission(request, user_id)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def test_review_store_listing(
|
||||||
|
request: backend.server.v2.store.model.ReviewSubmissionRequest,
|
||||||
|
user: autogpt_libs.auth.models.User,
|
||||||
|
):
|
||||||
|
return await backend.server.v2.store.routes.review_submission(request, user)
|
||||||
|
|
||||||
def set_test_dependency_overrides(self, overrides: dict):
|
def set_test_dependency_overrides(self, overrides: dict):
|
||||||
app.dependency_overrides.update(overrides)
|
app.dependency_overrides.update(overrides)
|
||||||
|
|
|
@ -13,6 +13,7 @@ from typing_extensions import Optional, TypedDict
|
||||||
import backend.data.block
|
import backend.data.block
|
||||||
import backend.server.integrations.router
|
import backend.server.integrations.router
|
||||||
import backend.server.routers.analytics
|
import backend.server.routers.analytics
|
||||||
|
import backend.server.v2.library.db
|
||||||
from backend.data import execution as execution_db
|
from backend.data import execution as execution_db
|
||||||
from backend.data import graph as graph_db
|
from backend.data import graph as graph_db
|
||||||
from backend.data.api_key import (
|
from backend.data.api_key import (
|
||||||
|
@ -180,11 +181,6 @@ async def get_graph(
|
||||||
tags=["graphs"],
|
tags=["graphs"],
|
||||||
dependencies=[Depends(auth_middleware)],
|
dependencies=[Depends(auth_middleware)],
|
||||||
)
|
)
|
||||||
@v1_router.get(
|
|
||||||
path="/templates/{graph_id}/versions",
|
|
||||||
tags=["templates", "graphs"],
|
|
||||||
dependencies=[Depends(auth_middleware)],
|
|
||||||
)
|
|
||||||
async def get_graph_all_versions(
|
async def get_graph_all_versions(
|
||||||
graph_id: str, user_id: Annotated[str, Depends(get_user_id)]
|
graph_id: str, user_id: Annotated[str, Depends(get_user_id)]
|
||||||
) -> Sequence[graph_db.GraphModel]:
|
) -> Sequence[graph_db.GraphModel]:
|
||||||
|
@ -200,12 +196,11 @@ async def get_graph_all_versions(
|
||||||
async def create_new_graph(
|
async def create_new_graph(
|
||||||
create_graph: CreateGraph, user_id: Annotated[str, Depends(get_user_id)]
|
create_graph: CreateGraph, user_id: Annotated[str, Depends(get_user_id)]
|
||||||
) -> graph_db.GraphModel:
|
) -> graph_db.GraphModel:
|
||||||
return await do_create_graph(create_graph, is_template=False, user_id=user_id)
|
return await do_create_graph(create_graph, user_id=user_id)
|
||||||
|
|
||||||
|
|
||||||
async def do_create_graph(
|
async def do_create_graph(
|
||||||
create_graph: CreateGraph,
|
create_graph: CreateGraph,
|
||||||
is_template: bool,
|
|
||||||
# user_id doesn't have to be annotated like on other endpoints,
|
# user_id doesn't have to be annotated like on other endpoints,
|
||||||
# because create_graph isn't used directly as an endpoint
|
# because create_graph isn't used directly as an endpoint
|
||||||
user_id: str,
|
user_id: str,
|
||||||
|
@ -217,7 +212,6 @@ async def do_create_graph(
|
||||||
graph = await graph_db.get_graph(
|
graph = await graph_db.get_graph(
|
||||||
create_graph.template_id,
|
create_graph.template_id,
|
||||||
create_graph.template_version,
|
create_graph.template_version,
|
||||||
template=True,
|
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
)
|
)
|
||||||
if not graph:
|
if not graph:
|
||||||
|
@ -225,13 +219,18 @@ async def do_create_graph(
|
||||||
400, detail=f"Template #{create_graph.template_id} not found"
|
400, detail=f"Template #{create_graph.template_id} not found"
|
||||||
)
|
)
|
||||||
graph.version = 1
|
graph.version = 1
|
||||||
|
|
||||||
|
# Create a library agent for the new graph
|
||||||
|
await backend.server.v2.library.db.create_library_agent(
|
||||||
|
graph.id,
|
||||||
|
graph.version,
|
||||||
|
user_id,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=400, detail="Either graph or template_id must be provided."
|
status_code=400, detail="Either graph or template_id must be provided."
|
||||||
)
|
)
|
||||||
|
|
||||||
graph.is_template = is_template
|
|
||||||
graph.is_active = not is_template
|
|
||||||
graph.reassign_ids(user_id=user_id, reassign_graph_id=True)
|
graph.reassign_ids(user_id=user_id, reassign_graph_id=True)
|
||||||
|
|
||||||
graph = await graph_db.create_graph(graph, user_id=user_id)
|
graph = await graph_db.create_graph(graph, user_id=user_id)
|
||||||
|
@ -261,11 +260,6 @@ async def delete_graph(
|
||||||
@v1_router.put(
|
@v1_router.put(
|
||||||
path="/graphs/{graph_id}", tags=["graphs"], dependencies=[Depends(auth_middleware)]
|
path="/graphs/{graph_id}", tags=["graphs"], dependencies=[Depends(auth_middleware)]
|
||||||
)
|
)
|
||||||
@v1_router.put(
|
|
||||||
path="/templates/{graph_id}",
|
|
||||||
tags=["templates", "graphs"],
|
|
||||||
dependencies=[Depends(auth_middleware)],
|
|
||||||
)
|
|
||||||
async def update_graph(
|
async def update_graph(
|
||||||
graph_id: str,
|
graph_id: str,
|
||||||
graph: graph_db.Graph,
|
graph: graph_db.Graph,
|
||||||
|
@ -298,6 +292,11 @@ async def update_graph(
|
||||||
|
|
||||||
if new_graph_version.is_active:
|
if new_graph_version.is_active:
|
||||||
|
|
||||||
|
# Keep the library agent up to date with the new active version
|
||||||
|
await backend.server.v2.library.db.update_agent_version_in_library(
|
||||||
|
user_id, graph.id, graph.version
|
||||||
|
)
|
||||||
|
|
||||||
def get_credentials(credentials_id: str) -> "Credentials | None":
|
def get_credentials(credentials_id: str) -> "Credentials | None":
|
||||||
return integration_creds_manager.get(user_id, credentials_id)
|
return integration_creds_manager.get(user_id, credentials_id)
|
||||||
|
|
||||||
|
@ -353,6 +352,12 @@ async def set_graph_active_version(
|
||||||
version=new_active_version,
|
version=new_active_version,
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Keep the library agent up to date with the new active version
|
||||||
|
await backend.server.v2.library.db.update_agent_version_in_library(
|
||||||
|
user_id, new_active_graph.id, new_active_graph.version
|
||||||
|
)
|
||||||
|
|
||||||
if current_active_graph and current_active_graph.version != new_active_version:
|
if current_active_graph and current_active_graph.version != new_active_version:
|
||||||
# Handle deactivation of the previously active version
|
# Handle deactivation of the previously active version
|
||||||
await on_graph_deactivate(
|
await on_graph_deactivate(
|
||||||
|
@ -368,12 +373,13 @@ async def set_graph_active_version(
|
||||||
)
|
)
|
||||||
def execute_graph(
|
def execute_graph(
|
||||||
graph_id: str,
|
graph_id: str,
|
||||||
|
graph_version: int,
|
||||||
node_input: dict[Any, Any],
|
node_input: dict[Any, Any],
|
||||||
user_id: Annotated[str, Depends(get_user_id)],
|
user_id: Annotated[str, Depends(get_user_id)],
|
||||||
) -> dict[str, Any]: # FIXME: add proper return type
|
) -> dict[str, Any]: # FIXME: add proper return type
|
||||||
try:
|
try:
|
||||||
graph_exec = execution_manager_client().add_execution(
|
graph_exec = execution_manager_client().add_execution(
|
||||||
graph_id, node_input, user_id=user_id
|
graph_id, node_input, user_id=user_id, graph_version=graph_version
|
||||||
)
|
)
|
||||||
return {"id": graph_exec.graph_exec_id}
|
return {"id": graph_exec.graph_exec_id}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -428,47 +434,6 @@ async def get_graph_run_node_execution_results(
|
||||||
return await execution_db.get_execution_results(graph_exec_id)
|
return await execution_db.get_execution_results(graph_exec_id)
|
||||||
|
|
||||||
|
|
||||||
########################################################
|
|
||||||
##################### Templates ########################
|
|
||||||
########################################################
|
|
||||||
|
|
||||||
|
|
||||||
@v1_router.get(
|
|
||||||
path="/templates",
|
|
||||||
tags=["graphs", "templates"],
|
|
||||||
dependencies=[Depends(auth_middleware)],
|
|
||||||
)
|
|
||||||
async def get_templates(
|
|
||||||
user_id: Annotated[str, Depends(get_user_id)]
|
|
||||||
) -> Sequence[graph_db.GraphModel]:
|
|
||||||
return await graph_db.get_graphs(filter_by="template", user_id=user_id)
|
|
||||||
|
|
||||||
|
|
||||||
@v1_router.get(
|
|
||||||
path="/templates/{graph_id}",
|
|
||||||
tags=["templates", "graphs"],
|
|
||||||
dependencies=[Depends(auth_middleware)],
|
|
||||||
)
|
|
||||||
async def get_template(
|
|
||||||
graph_id: str, version: int | None = None
|
|
||||||
) -> graph_db.GraphModel:
|
|
||||||
graph = await graph_db.get_graph(graph_id, version, template=True)
|
|
||||||
if not graph:
|
|
||||||
raise HTTPException(status_code=404, detail=f"Template #{graph_id} not found.")
|
|
||||||
return graph
|
|
||||||
|
|
||||||
|
|
||||||
@v1_router.post(
|
|
||||||
path="/templates",
|
|
||||||
tags=["templates", "graphs"],
|
|
||||||
dependencies=[Depends(auth_middleware)],
|
|
||||||
)
|
|
||||||
async def create_new_template(
|
|
||||||
create_graph: CreateGraph, user_id: Annotated[str, Depends(get_user_id)]
|
|
||||||
) -> graph_db.GraphModel:
|
|
||||||
return await do_create_graph(create_graph, is_template=True, user_id=user_id)
|
|
||||||
|
|
||||||
|
|
||||||
########################################################
|
########################################################
|
||||||
##################### Schedules ########################
|
##################### Schedules ########################
|
||||||
########################################################
|
########################################################
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
from typing import List
|
|
||||||
|
|
||||||
import prisma.errors
|
import prisma.errors
|
||||||
import prisma.models
|
import prisma.models
|
||||||
|
@ -7,6 +7,7 @@ import prisma.types
|
||||||
|
|
||||||
import backend.data.graph
|
import backend.data.graph
|
||||||
import backend.data.includes
|
import backend.data.includes
|
||||||
|
import backend.server.model
|
||||||
import backend.server.v2.library.model
|
import backend.server.v2.library.model
|
||||||
import backend.server.v2.store.exceptions
|
import backend.server.v2.store.exceptions
|
||||||
|
|
||||||
|
@ -14,90 +15,152 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
async def get_library_agents(
|
async def get_library_agents(
|
||||||
user_id: str,
|
user_id: str, search_query: str | None = None
|
||||||
) -> List[backend.server.v2.library.model.LibraryAgent]:
|
) -> list[backend.server.v2.library.model.LibraryAgent]:
|
||||||
"""
|
logger.debug(
|
||||||
Returns all agents (AgentGraph) that belong to the user and all agents in their library (UserAgent table)
|
f"Fetching library agents for user_id={user_id} search_query={search_query}"
|
||||||
"""
|
)
|
||||||
logger.debug(f"Getting library agents for user {user_id}")
|
|
||||||
|
|
||||||
try:
|
if search_query and len(search_query.strip()) > 100:
|
||||||
# Get agents created by user with nodes and links
|
logger.warning(f"Search query too long: {search_query}")
|
||||||
user_created = await prisma.models.AgentGraph.prisma().find_many(
|
raise backend.server.v2.store.exceptions.DatabaseError(
|
||||||
where=prisma.types.AgentGraphWhereInput(userId=user_id, isActive=True),
|
"Search query is too long."
|
||||||
include=backend.data.includes.AGENT_GRAPH_INCLUDE,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get agents in user's library with nodes and links
|
where_clause = prisma.types.LibraryAgentWhereInput(
|
||||||
library_agents = await prisma.models.UserAgent.prisma().find_many(
|
userId=user_id,
|
||||||
where=prisma.types.UserAgentWhereInput(
|
isDeleted=False,
|
||||||
userId=user_id, isDeleted=False, isArchived=False
|
isArchived=False,
|
||||||
),
|
)
|
||||||
include={
|
|
||||||
|
if search_query:
|
||||||
|
where_clause["OR"] = [
|
||||||
|
{
|
||||||
"Agent": {
|
"Agent": {
|
||||||
"include": {
|
"is": {"name": {"contains": search_query, "mode": "insensitive"}}
|
||||||
"AgentNodes": {
|
}
|
||||||
"include": {
|
},
|
||||||
"Input": True,
|
{
|
||||||
"Output": True,
|
"Agent": {
|
||||||
"Webhook": True,
|
"is": {
|
||||||
"AgentBlock": True,
|
"description": {"contains": search_query, "mode": "insensitive"}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
try:
|
||||||
|
library_agents = await prisma.models.LibraryAgent.prisma().find_many(
|
||||||
|
where=where_clause,
|
||||||
|
include={
|
||||||
|
"Agent": {
|
||||||
|
"include": {
|
||||||
|
"AgentNodes": {"include": {"Input": True, "Output": True}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
order=[{"updatedAt": "desc"}],
|
||||||
|
)
|
||||||
|
logger.debug(f"Retrieved {len(library_agents)} agents for user_id={user_id}.")
|
||||||
|
return [
|
||||||
|
backend.server.v2.library.model.LibraryAgent.from_db(agent)
|
||||||
|
for agent in library_agents
|
||||||
|
]
|
||||||
|
except prisma.errors.PrismaError as e:
|
||||||
|
logger.error(f"Database error fetching library agents: {e}")
|
||||||
|
raise backend.server.v2.store.exceptions.DatabaseError(
|
||||||
|
"Unable to fetch library agents."
|
||||||
)
|
)
|
||||||
|
|
||||||
# Convert to Graph models first
|
|
||||||
graphs = []
|
|
||||||
|
|
||||||
# Add user created agents
|
async def create_library_agent(
|
||||||
for agent in user_created:
|
agent_id: str, agent_version: int, user_id: str
|
||||||
try:
|
) -> prisma.models.LibraryAgent:
|
||||||
graphs.append(backend.data.graph.GraphModel.from_db(agent))
|
"""
|
||||||
except Exception as e:
|
Adds an agent to the user's library (LibraryAgent table)
|
||||||
logger.error(f"Error processing user created agent {agent.id}: {e}")
|
"""
|
||||||
continue
|
|
||||||
|
|
||||||
# Add library agents
|
try:
|
||||||
for agent in library_agents:
|
|
||||||
if agent.Agent:
|
|
||||||
try:
|
|
||||||
graphs.append(backend.data.graph.GraphModel.from_db(agent.Agent))
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error processing library agent {agent.agentId}: {e}")
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Convert Graph models to LibraryAgent models
|
library_agent = await prisma.models.LibraryAgent.prisma().create(
|
||||||
result = []
|
data=prisma.types.LibraryAgentCreateInput(
|
||||||
for graph in graphs:
|
userId=user_id,
|
||||||
result.append(
|
agentId=agent_id,
|
||||||
backend.server.v2.library.model.LibraryAgent(
|
agentVersion=agent_version,
|
||||||
id=graph.id,
|
isCreatedByUser=False,
|
||||||
version=graph.version,
|
useGraphIsActiveVersion=True,
|
||||||
is_active=graph.is_active,
|
|
||||||
name=graph.name,
|
|
||||||
description=graph.description,
|
|
||||||
isCreatedByUser=any(a.id == graph.id for a in user_created),
|
|
||||||
input_schema=graph.input_schema,
|
|
||||||
output_schema=graph.output_schema,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
)
|
||||||
logger.debug(f"Found {len(result)} library agents")
|
return library_agent
|
||||||
return result
|
|
||||||
|
|
||||||
except prisma.errors.PrismaError as e:
|
except prisma.errors.PrismaError as e:
|
||||||
logger.error(f"Database error getting library agents: {str(e)}")
|
logger.error(f"Database error creating agent to library: {str(e)}")
|
||||||
raise backend.server.v2.store.exceptions.DatabaseError(
|
raise backend.server.v2.store.exceptions.DatabaseError(
|
||||||
"Failed to fetch library agents"
|
"Failed to create agent to library"
|
||||||
) from e
|
) from e
|
||||||
|
|
||||||
|
|
||||||
async def add_agent_to_library(store_listing_version_id: str, user_id: str) -> None:
|
async def update_agent_version_in_library(
|
||||||
|
user_id: str, agent_id: str, agent_version: int
|
||||||
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Finds the agent from the store listing version and adds it to the user's library (UserAgent table)
|
Updates the agent version in the library
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
await prisma.models.LibraryAgent.prisma().update(
|
||||||
|
where={
|
||||||
|
"userId": user_id,
|
||||||
|
"agentId": agent_id,
|
||||||
|
"useGraphIsActiveVersion": True,
|
||||||
|
},
|
||||||
|
data=prisma.types.LibraryAgentUpdateInput(
|
||||||
|
Agent=prisma.types.AgentGraphUpdateOneWithoutRelationsInput(
|
||||||
|
connect=prisma.types.AgentGraphWhereUniqueInput(
|
||||||
|
id=agent_id,
|
||||||
|
version=agent_version,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
except prisma.errors.PrismaError as e:
|
||||||
|
logger.error(f"Database error updating agent version in library: {str(e)}")
|
||||||
|
raise backend.server.v2.store.exceptions.DatabaseError(
|
||||||
|
"Failed to update agent version in library"
|
||||||
|
) from e
|
||||||
|
|
||||||
|
|
||||||
|
async def update_library_agent(
|
||||||
|
library_agent_id: str,
|
||||||
|
user_id: str,
|
||||||
|
auto_update_version: bool = False,
|
||||||
|
is_favorite: bool = False,
|
||||||
|
is_archived: bool = False,
|
||||||
|
is_deleted: bool = False,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Updates the library agent with the given fields
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
await prisma.models.LibraryAgent.prisma().update(
|
||||||
|
where={"id": library_agent_id, "userId": user_id},
|
||||||
|
data=prisma.types.LibraryAgentUpdateInput(
|
||||||
|
useGraphIsActiveVersion=auto_update_version,
|
||||||
|
isFavorite=is_favorite,
|
||||||
|
isArchived=is_archived,
|
||||||
|
isDeleted=is_deleted,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
except prisma.errors.PrismaError as e:
|
||||||
|
logger.error(f"Database error updating library agent: {str(e)}")
|
||||||
|
raise backend.server.v2.store.exceptions.DatabaseError(
|
||||||
|
"Failed to update library agent"
|
||||||
|
) from e
|
||||||
|
|
||||||
|
|
||||||
|
async def add_store_agent_to_library(
|
||||||
|
store_listing_version_id: str, user_id: str
|
||||||
|
) -> 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
|
if they don't already have it
|
||||||
"""
|
"""
|
||||||
logger.debug(
|
logger.debug(
|
||||||
|
@ -131,7 +194,7 @@ async def add_agent_to_library(store_listing_version_id: str, user_id: str) -> N
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check if user already has this agent
|
# Check if user already has this agent
|
||||||
existing_user_agent = await prisma.models.UserAgent.prisma().find_first(
|
existing_user_agent = await prisma.models.LibraryAgent.prisma().find_first(
|
||||||
where={
|
where={
|
||||||
"userId": user_id,
|
"userId": user_id,
|
||||||
"agentId": agent.id,
|
"agentId": agent.id,
|
||||||
|
@ -145,9 +208,9 @@ async def add_agent_to_library(store_listing_version_id: str, user_id: str) -> N
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Create UserAgent entry
|
# Create LibraryAgent entry
|
||||||
await prisma.models.UserAgent.prisma().create(
|
await prisma.models.LibraryAgent.prisma().create(
|
||||||
data=prisma.types.UserAgentCreateInput(
|
data=prisma.types.LibraryAgentCreateInput(
|
||||||
userId=user_id,
|
userId=user_id,
|
||||||
agentId=agent.id,
|
agentId=agent.id,
|
||||||
agentVersion=agent.version,
|
agentVersion=agent.version,
|
||||||
|
@ -163,3 +226,116 @@ async def add_agent_to_library(store_listing_version_id: str, user_id: str) -> N
|
||||||
raise backend.server.v2.store.exceptions.DatabaseError(
|
raise backend.server.v2.store.exceptions.DatabaseError(
|
||||||
"Failed to add agent to library"
|
"Failed to add agent to library"
|
||||||
) from e
|
) from e
|
||||||
|
|
||||||
|
|
||||||
|
##############################################
|
||||||
|
########### Presets DB Functions #############
|
||||||
|
##############################################
|
||||||
|
|
||||||
|
|
||||||
|
async def get_presets(
|
||||||
|
user_id: str, page: int, page_size: int
|
||||||
|
) -> backend.server.v2.library.model.LibraryAgentPresetResponse:
|
||||||
|
|
||||||
|
try:
|
||||||
|
presets = await prisma.models.AgentPreset.prisma().find_many(
|
||||||
|
where={"userId": user_id},
|
||||||
|
skip=page * page_size,
|
||||||
|
take=page_size,
|
||||||
|
)
|
||||||
|
|
||||||
|
total_items = await prisma.models.AgentPreset.prisma().count(
|
||||||
|
where={"userId": user_id},
|
||||||
|
)
|
||||||
|
total_pages = (total_items + page_size - 1) // page_size
|
||||||
|
|
||||||
|
presets = [
|
||||||
|
backend.server.v2.library.model.LibraryAgentPreset.from_db(preset)
|
||||||
|
for preset in presets
|
||||||
|
]
|
||||||
|
|
||||||
|
return backend.server.v2.library.model.LibraryAgentPresetResponse(
|
||||||
|
presets=presets,
|
||||||
|
pagination=backend.server.model.Pagination(
|
||||||
|
total_items=total_items,
|
||||||
|
total_pages=total_pages,
|
||||||
|
current_page=page,
|
||||||
|
page_size=page_size,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
except prisma.errors.PrismaError as e:
|
||||||
|
logger.error(f"Database error getting presets: {str(e)}")
|
||||||
|
raise backend.server.v2.store.exceptions.DatabaseError(
|
||||||
|
"Failed to fetch presets"
|
||||||
|
) from e
|
||||||
|
|
||||||
|
|
||||||
|
async def get_preset(
|
||||||
|
user_id: str, preset_id: str
|
||||||
|
) -> backend.server.v2.library.model.LibraryAgentPreset | None:
|
||||||
|
try:
|
||||||
|
preset = await prisma.models.AgentPreset.prisma().find_unique(
|
||||||
|
where={"id": preset_id, "userId": user_id}, include={"InputPresets": True}
|
||||||
|
)
|
||||||
|
if not preset:
|
||||||
|
return None
|
||||||
|
return backend.server.v2.library.model.LibraryAgentPreset.from_db(preset)
|
||||||
|
except prisma.errors.PrismaError as e:
|
||||||
|
logger.error(f"Database error getting preset: {str(e)}")
|
||||||
|
raise backend.server.v2.store.exceptions.DatabaseError(
|
||||||
|
"Failed to fetch preset"
|
||||||
|
) from e
|
||||||
|
|
||||||
|
|
||||||
|
async def create_or_update_preset(
|
||||||
|
user_id: str,
|
||||||
|
preset: backend.server.v2.library.model.CreateLibraryAgentPresetRequest,
|
||||||
|
preset_id: str | None = None,
|
||||||
|
) -> backend.server.v2.library.model.LibraryAgentPreset:
|
||||||
|
try:
|
||||||
|
new_preset = await prisma.models.AgentPreset.prisma().upsert(
|
||||||
|
where={
|
||||||
|
"id": preset_id if preset_id else "",
|
||||||
|
},
|
||||||
|
data={
|
||||||
|
"create": {
|
||||||
|
"userId": user_id,
|
||||||
|
"name": preset.name,
|
||||||
|
"description": preset.description,
|
||||||
|
"agentId": preset.agent_id,
|
||||||
|
"agentVersion": preset.agent_version,
|
||||||
|
"isActive": preset.is_active,
|
||||||
|
"InputPresets": {
|
||||||
|
"create": [
|
||||||
|
{"name": name, "data": json.dumps(data)}
|
||||||
|
for name, data in preset.inputs.items()
|
||||||
|
]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"update": {
|
||||||
|
"name": preset.name,
|
||||||
|
"description": preset.description,
|
||||||
|
"isActive": preset.is_active,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return backend.server.v2.library.model.LibraryAgentPreset.from_db(new_preset)
|
||||||
|
except prisma.errors.PrismaError as e:
|
||||||
|
logger.error(f"Database error creating preset: {str(e)}")
|
||||||
|
raise backend.server.v2.store.exceptions.DatabaseError(
|
||||||
|
"Failed to create preset"
|
||||||
|
) from e
|
||||||
|
|
||||||
|
|
||||||
|
async def delete_preset(user_id: str, preset_id: str) -> None:
|
||||||
|
try:
|
||||||
|
await prisma.models.AgentPreset.prisma().update(
|
||||||
|
where={"id": preset_id, "userId": user_id},
|
||||||
|
data={"isDeleted": True},
|
||||||
|
)
|
||||||
|
except prisma.errors.PrismaError as e:
|
||||||
|
logger.error(f"Database error deleting preset: {str(e)}")
|
||||||
|
raise backend.server.v2.store.exceptions.DatabaseError(
|
||||||
|
"Failed to delete preset"
|
||||||
|
) from e
|
||||||
|
|
|
@ -37,7 +37,7 @@ async def test_get_library_agents(mocker):
|
||||||
]
|
]
|
||||||
|
|
||||||
mock_library_agents = [
|
mock_library_agents = [
|
||||||
prisma.models.UserAgent(
|
prisma.models.LibraryAgent(
|
||||||
id="ua1",
|
id="ua1",
|
||||||
userId="test-user",
|
userId="test-user",
|
||||||
agentId="agent2",
|
agentId="agent2",
|
||||||
|
@ -48,6 +48,7 @@ async def test_get_library_agents(mocker):
|
||||||
createdAt=datetime.now(),
|
createdAt=datetime.now(),
|
||||||
updatedAt=datetime.now(),
|
updatedAt=datetime.now(),
|
||||||
isFavorite=False,
|
isFavorite=False,
|
||||||
|
useGraphIsActiveVersion=True,
|
||||||
Agent=prisma.models.AgentGraph(
|
Agent=prisma.models.AgentGraph(
|
||||||
id="agent2",
|
id="agent2",
|
||||||
version=1,
|
version=1,
|
||||||
|
@ -67,8 +68,8 @@ async def test_get_library_agents(mocker):
|
||||||
return_value=mock_user_created
|
return_value=mock_user_created
|
||||||
)
|
)
|
||||||
|
|
||||||
mock_user_agent = mocker.patch("prisma.models.UserAgent.prisma")
|
mock_library_agent = mocker.patch("prisma.models.LibraryAgent.prisma")
|
||||||
mock_user_agent.return_value.find_many = mocker.AsyncMock(
|
mock_library_agent.return_value.find_many = mocker.AsyncMock(
|
||||||
return_value=mock_library_agents
|
return_value=mock_library_agents
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -76,40 +77,16 @@ async def test_get_library_agents(mocker):
|
||||||
result = await db.get_library_agents("test-user")
|
result = await db.get_library_agents("test-user")
|
||||||
|
|
||||||
# Verify results
|
# Verify results
|
||||||
assert len(result) == 2
|
assert len(result) == 1
|
||||||
assert result[0].id == "agent1"
|
assert result[0].id == "ua1"
|
||||||
assert result[0].name == "Test Agent 1"
|
assert result[0].name == "Test Agent 2"
|
||||||
assert result[0].description == "Test Description 1"
|
assert result[0].description == "Test Description 2"
|
||||||
assert result[0].isCreatedByUser is True
|
assert result[0].is_created_by_user is False
|
||||||
assert result[1].id == "agent2"
|
assert result[0].is_latest_version is True
|
||||||
assert result[1].name == "Test Agent 2"
|
assert result[0].is_favorite is False
|
||||||
assert result[1].description == "Test Description 2"
|
assert result[0].agent_id == "agent2"
|
||||||
assert result[1].isCreatedByUser is False
|
assert result[0].agent_version == 1
|
||||||
|
assert result[0].preset_id is None
|
||||||
# Verify mocks called correctly
|
|
||||||
mock_agent_graph.return_value.find_many.assert_called_once_with(
|
|
||||||
where=prisma.types.AgentGraphWhereInput(userId="test-user", isActive=True),
|
|
||||||
include=backend.data.includes.AGENT_GRAPH_INCLUDE,
|
|
||||||
)
|
|
||||||
mock_user_agent.return_value.find_many.assert_called_once_with(
|
|
||||||
where=prisma.types.UserAgentWhereInput(
|
|
||||||
userId="test-user", isDeleted=False, isArchived=False
|
|
||||||
),
|
|
||||||
include={
|
|
||||||
"Agent": {
|
|
||||||
"include": {
|
|
||||||
"AgentNodes": {
|
|
||||||
"include": {
|
|
||||||
"Input": True,
|
|
||||||
"Output": True,
|
|
||||||
"Webhook": True,
|
|
||||||
"AgentBlock": True,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
@ -152,26 +129,26 @@ async def test_add_agent_to_library(mocker):
|
||||||
return_value=mock_store_listing
|
return_value=mock_store_listing
|
||||||
)
|
)
|
||||||
|
|
||||||
mock_user_agent = mocker.patch("prisma.models.UserAgent.prisma")
|
mock_library_agent = mocker.patch("prisma.models.LibraryAgent.prisma")
|
||||||
mock_user_agent.return_value.find_first = mocker.AsyncMock(return_value=None)
|
mock_library_agent.return_value.find_first = mocker.AsyncMock(return_value=None)
|
||||||
mock_user_agent.return_value.create = mocker.AsyncMock()
|
mock_library_agent.return_value.create = mocker.AsyncMock()
|
||||||
|
|
||||||
# Call function
|
# Call function
|
||||||
await db.add_agent_to_library("version123", "test-user")
|
await db.add_store_agent_to_library("version123", "test-user")
|
||||||
|
|
||||||
# Verify mocks called correctly
|
# Verify mocks called correctly
|
||||||
mock_store_listing_version.return_value.find_unique.assert_called_once_with(
|
mock_store_listing_version.return_value.find_unique.assert_called_once_with(
|
||||||
where={"id": "version123"}, include={"Agent": True}
|
where={"id": "version123"}, include={"Agent": True}
|
||||||
)
|
)
|
||||||
mock_user_agent.return_value.find_first.assert_called_once_with(
|
mock_library_agent.return_value.find_first.assert_called_once_with(
|
||||||
where={
|
where={
|
||||||
"userId": "test-user",
|
"userId": "test-user",
|
||||||
"agentId": "agent1",
|
"agentId": "agent1",
|
||||||
"agentVersion": 1,
|
"agentVersion": 1,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
mock_user_agent.return_value.create.assert_called_once_with(
|
mock_library_agent.return_value.create.assert_called_once_with(
|
||||||
data=prisma.types.UserAgentCreateInput(
|
data=prisma.types.LibraryAgentCreateInput(
|
||||||
userId="test-user", agentId="agent1", agentVersion=1, isCreatedByUser=False
|
userId="test-user", agentId="agent1", agentVersion=1, isCreatedByUser=False
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -189,7 +166,7 @@ async def test_add_agent_to_library_not_found(mocker):
|
||||||
|
|
||||||
# Call function and verify exception
|
# Call function and verify exception
|
||||||
with pytest.raises(backend.server.v2.store.exceptions.AgentNotFoundError):
|
with pytest.raises(backend.server.v2.store.exceptions.AgentNotFoundError):
|
||||||
await db.add_agent_to_library("version123", "test-user")
|
await db.add_store_agent_to_library("version123", "test-user")
|
||||||
|
|
||||||
# Verify mock called correctly
|
# Verify mock called correctly
|
||||||
mock_store_listing_version.return_value.find_unique.assert_called_once_with(
|
mock_store_listing_version.return_value.find_unique.assert_called_once_with(
|
||||||
|
|
|
@ -1,16 +1,112 @@
|
||||||
|
import datetime
|
||||||
|
import json
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
|
import prisma.models
|
||||||
import pydantic
|
import pydantic
|
||||||
|
|
||||||
|
import backend.data.block
|
||||||
|
import backend.data.graph
|
||||||
|
import backend.server.model
|
||||||
|
|
||||||
|
|
||||||
class LibraryAgent(pydantic.BaseModel):
|
class LibraryAgent(pydantic.BaseModel):
|
||||||
id: str # Changed from agent_id to match GraphMeta
|
id: str # Changed from agent_id to match GraphMeta
|
||||||
version: int # Changed from agent_version to match GraphMeta
|
|
||||||
is_active: bool # Added to match GraphMeta
|
agent_id: str
|
||||||
|
agent_version: int # Changed from agent_version to match GraphMeta
|
||||||
|
|
||||||
|
preset_id: str | None
|
||||||
|
|
||||||
|
updated_at: datetime.datetime
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
description: str
|
description: str
|
||||||
|
|
||||||
isCreatedByUser: bool
|
|
||||||
# Made input_schema and output_schema match GraphMeta's type
|
# Made input_schema and output_schema match GraphMeta's type
|
||||||
input_schema: dict[str, typing.Any] # Should be BlockIOObjectSubSchema in frontend
|
input_schema: dict[str, typing.Any] # Should be BlockIOObjectSubSchema in frontend
|
||||||
output_schema: dict[str, typing.Any] # Should be BlockIOObjectSubSchema in frontend
|
output_schema: dict[str, typing.Any] # Should be BlockIOObjectSubSchema in frontend
|
||||||
|
|
||||||
|
is_favorite: bool
|
||||||
|
is_created_by_user: bool
|
||||||
|
|
||||||
|
is_latest_version: bool
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_db(agent: prisma.models.LibraryAgent):
|
||||||
|
if not agent.Agent:
|
||||||
|
raise ValueError("AgentGraph is required")
|
||||||
|
|
||||||
|
graph = backend.data.graph.GraphModel.from_db(agent.Agent)
|
||||||
|
|
||||||
|
agent_updated_at = agent.Agent.updatedAt
|
||||||
|
lib_agent_updated_at = agent.updatedAt
|
||||||
|
|
||||||
|
# Take the latest updated_at timestamp either when the graph was updated or the library agent was updated
|
||||||
|
updated_at = (
|
||||||
|
max(agent_updated_at, lib_agent_updated_at)
|
||||||
|
if agent_updated_at
|
||||||
|
else lib_agent_updated_at
|
||||||
|
)
|
||||||
|
|
||||||
|
return LibraryAgent(
|
||||||
|
id=agent.id,
|
||||||
|
agent_id=agent.agentId,
|
||||||
|
agent_version=agent.agentVersion,
|
||||||
|
updated_at=updated_at,
|
||||||
|
name=graph.name,
|
||||||
|
description=graph.description,
|
||||||
|
input_schema=graph.input_schema,
|
||||||
|
output_schema=graph.output_schema,
|
||||||
|
is_favorite=agent.isFavorite,
|
||||||
|
is_created_by_user=agent.isCreatedByUser,
|
||||||
|
is_latest_version=graph.is_active,
|
||||||
|
preset_id=agent.AgentPreset.id if agent.AgentPreset else None,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class LibraryAgentPreset(pydantic.BaseModel):
|
||||||
|
id: str
|
||||||
|
updated_at: datetime.datetime
|
||||||
|
|
||||||
|
agent_id: str
|
||||||
|
agent_version: int
|
||||||
|
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
|
||||||
|
is_active: bool
|
||||||
|
|
||||||
|
inputs: dict[str, typing.Union[backend.data.block.BlockInput, typing.Any]]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_db(preset: prisma.models.AgentPreset):
|
||||||
|
input_data = {}
|
||||||
|
|
||||||
|
for data in preset.InputPresets or []:
|
||||||
|
input_data[data.name] = json.loads(data.data)
|
||||||
|
|
||||||
|
return LibraryAgentPreset(
|
||||||
|
id=preset.id,
|
||||||
|
updated_at=preset.updatedAt,
|
||||||
|
agent_id=preset.agentId,
|
||||||
|
agent_version=preset.agentVersion,
|
||||||
|
name=preset.name,
|
||||||
|
description=preset.description,
|
||||||
|
is_active=preset.isActive,
|
||||||
|
inputs=input_data,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class LibraryAgentPresetResponse(pydantic.BaseModel):
|
||||||
|
presets: list[LibraryAgentPreset]
|
||||||
|
pagination: backend.server.model.Pagination
|
||||||
|
|
||||||
|
|
||||||
|
class CreateLibraryAgentPresetRequest(pydantic.BaseModel):
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
inputs: dict[str, typing.Union[backend.data.block.BlockInput, typing.Any]]
|
||||||
|
agent_id: str
|
||||||
|
agent_version: int
|
||||||
|
is_active: bool
|
||||||
|
|
|
@ -1,23 +1,35 @@
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
import prisma.models
|
||||||
|
|
||||||
|
import backend.data.block
|
||||||
|
import backend.server.model
|
||||||
import backend.server.v2.library.model
|
import backend.server.v2.library.model
|
||||||
|
|
||||||
|
|
||||||
def test_library_agent():
|
def test_library_agent():
|
||||||
agent = backend.server.v2.library.model.LibraryAgent(
|
agent = backend.server.v2.library.model.LibraryAgent(
|
||||||
id="test-agent-123",
|
id="test-agent-123",
|
||||||
version=1,
|
agent_id="agent-123",
|
||||||
is_active=True,
|
agent_version=1,
|
||||||
|
preset_id=None,
|
||||||
|
updated_at=datetime.datetime.now(),
|
||||||
name="Test Agent",
|
name="Test Agent",
|
||||||
description="Test description",
|
description="Test description",
|
||||||
isCreatedByUser=False,
|
|
||||||
input_schema={"type": "object", "properties": {}},
|
input_schema={"type": "object", "properties": {}},
|
||||||
output_schema={"type": "object", "properties": {}},
|
output_schema={"type": "object", "properties": {}},
|
||||||
|
is_favorite=False,
|
||||||
|
is_created_by_user=False,
|
||||||
|
is_latest_version=True,
|
||||||
)
|
)
|
||||||
assert agent.id == "test-agent-123"
|
assert agent.id == "test-agent-123"
|
||||||
assert agent.version == 1
|
assert agent.agent_id == "agent-123"
|
||||||
assert agent.is_active is True
|
assert agent.agent_version == 1
|
||||||
assert agent.name == "Test Agent"
|
assert agent.name == "Test Agent"
|
||||||
assert agent.description == "Test description"
|
assert agent.description == "Test description"
|
||||||
assert agent.isCreatedByUser is False
|
assert agent.is_favorite is False
|
||||||
|
assert agent.is_created_by_user is False
|
||||||
|
assert agent.is_latest_version is True
|
||||||
assert agent.input_schema == {"type": "object", "properties": {}}
|
assert agent.input_schema == {"type": "object", "properties": {}}
|
||||||
assert agent.output_schema == {"type": "object", "properties": {}}
|
assert agent.output_schema == {"type": "object", "properties": {}}
|
||||||
|
|
||||||
|
@ -25,19 +37,148 @@ def test_library_agent():
|
||||||
def test_library_agent_with_user_created():
|
def test_library_agent_with_user_created():
|
||||||
agent = backend.server.v2.library.model.LibraryAgent(
|
agent = backend.server.v2.library.model.LibraryAgent(
|
||||||
id="user-agent-456",
|
id="user-agent-456",
|
||||||
version=2,
|
agent_id="agent-456",
|
||||||
is_active=True,
|
agent_version=2,
|
||||||
|
preset_id=None,
|
||||||
|
updated_at=datetime.datetime.now(),
|
||||||
name="User Created Agent",
|
name="User Created Agent",
|
||||||
description="An agent created by the user",
|
description="An agent created by the user",
|
||||||
isCreatedByUser=True,
|
|
||||||
input_schema={"type": "object", "properties": {}},
|
input_schema={"type": "object", "properties": {}},
|
||||||
output_schema={"type": "object", "properties": {}},
|
output_schema={"type": "object", "properties": {}},
|
||||||
|
is_favorite=False,
|
||||||
|
is_created_by_user=True,
|
||||||
|
is_latest_version=True,
|
||||||
)
|
)
|
||||||
assert agent.id == "user-agent-456"
|
assert agent.id == "user-agent-456"
|
||||||
assert agent.version == 2
|
assert agent.agent_id == "agent-456"
|
||||||
assert agent.is_active is True
|
assert agent.agent_version == 2
|
||||||
assert agent.name == "User Created Agent"
|
assert agent.name == "User Created Agent"
|
||||||
assert agent.description == "An agent created by the user"
|
assert agent.description == "An agent created by the user"
|
||||||
assert agent.isCreatedByUser is True
|
assert agent.is_favorite is False
|
||||||
|
assert agent.is_created_by_user is True
|
||||||
|
assert agent.is_latest_version is True
|
||||||
assert agent.input_schema == {"type": "object", "properties": {}}
|
assert agent.input_schema == {"type": "object", "properties": {}}
|
||||||
assert agent.output_schema == {"type": "object", "properties": {}}
|
assert agent.output_schema == {"type": "object", "properties": {}}
|
||||||
|
|
||||||
|
|
||||||
|
def test_library_agent_preset():
|
||||||
|
preset = backend.server.v2.library.model.LibraryAgentPreset(
|
||||||
|
id="preset-123",
|
||||||
|
name="Test Preset",
|
||||||
|
description="Test preset description",
|
||||||
|
agent_id="test-agent-123",
|
||||||
|
agent_version=1,
|
||||||
|
is_active=True,
|
||||||
|
inputs={
|
||||||
|
"input1": backend.data.block.BlockInput(
|
||||||
|
name="input1",
|
||||||
|
data={"type": "string", "value": "test value"},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
updated_at=datetime.datetime.now(),
|
||||||
|
)
|
||||||
|
assert preset.id == "preset-123"
|
||||||
|
assert preset.name == "Test Preset"
|
||||||
|
assert preset.description == "Test preset description"
|
||||||
|
assert preset.agent_id == "test-agent-123"
|
||||||
|
assert preset.agent_version == 1
|
||||||
|
assert preset.is_active is True
|
||||||
|
assert preset.inputs == {
|
||||||
|
"input1": backend.data.block.BlockInput(
|
||||||
|
name="input1", data={"type": "string", "value": "test value"}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_library_agent_preset_response():
|
||||||
|
preset = backend.server.v2.library.model.LibraryAgentPreset(
|
||||||
|
id="preset-123",
|
||||||
|
name="Test Preset",
|
||||||
|
description="Test preset description",
|
||||||
|
agent_id="test-agent-123",
|
||||||
|
agent_version=1,
|
||||||
|
is_active=True,
|
||||||
|
inputs={
|
||||||
|
"input1": backend.data.block.BlockInput(
|
||||||
|
name="input1",
|
||||||
|
data={"type": "string", "value": "test value"},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
updated_at=datetime.datetime.now(),
|
||||||
|
)
|
||||||
|
|
||||||
|
pagination = backend.server.model.Pagination(
|
||||||
|
total_items=1, total_pages=1, current_page=1, page_size=10
|
||||||
|
)
|
||||||
|
|
||||||
|
response = backend.server.v2.library.model.LibraryAgentPresetResponse(
|
||||||
|
presets=[preset], pagination=pagination
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(response.presets) == 1
|
||||||
|
assert response.presets[0].id == "preset-123"
|
||||||
|
assert response.pagination.total_items == 1
|
||||||
|
assert response.pagination.total_pages == 1
|
||||||
|
assert response.pagination.current_page == 1
|
||||||
|
assert response.pagination.page_size == 10
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_library_agent_preset_request():
|
||||||
|
request = backend.server.v2.library.model.CreateLibraryAgentPresetRequest(
|
||||||
|
name="New Preset",
|
||||||
|
description="New preset description",
|
||||||
|
agent_id="agent-123",
|
||||||
|
agent_version=1,
|
||||||
|
is_active=True,
|
||||||
|
inputs={
|
||||||
|
"input1": backend.data.block.BlockInput(
|
||||||
|
name="input1",
|
||||||
|
data={"type": "string", "value": "test value"},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert request.name == "New Preset"
|
||||||
|
assert request.description == "New preset description"
|
||||||
|
assert request.agent_id == "agent-123"
|
||||||
|
assert request.agent_version == 1
|
||||||
|
assert request.is_active is True
|
||||||
|
assert request.inputs == {
|
||||||
|
"input1": backend.data.block.BlockInput(
|
||||||
|
name="input1", data={"type": "string", "value": "test value"}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_library_agent_from_db():
|
||||||
|
# Create mock DB agent
|
||||||
|
db_agent = prisma.models.AgentPreset(
|
||||||
|
id="test-agent-123",
|
||||||
|
createdAt=datetime.datetime.now(),
|
||||||
|
updatedAt=datetime.datetime.now(),
|
||||||
|
agentId="agent-123",
|
||||||
|
agentVersion=1,
|
||||||
|
name="Test Agent",
|
||||||
|
description="Test agent description",
|
||||||
|
isActive=True,
|
||||||
|
userId="test-user-123",
|
||||||
|
isDeleted=False,
|
||||||
|
InputPresets=[
|
||||||
|
prisma.models.AgentNodeExecutionInputOutput(
|
||||||
|
id="input-123",
|
||||||
|
time=datetime.datetime.now(),
|
||||||
|
name="input1",
|
||||||
|
data='{"type": "string", "value": "test value"}',
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Convert to LibraryAgentPreset
|
||||||
|
agent = backend.server.v2.library.model.LibraryAgentPreset.from_db(db_agent)
|
||||||
|
|
||||||
|
assert agent.id == "test-agent-123"
|
||||||
|
assert agent.agent_version == 1
|
||||||
|
assert agent.is_active is True
|
||||||
|
assert agent.name == "Test Agent"
|
||||||
|
assert agent.description == "Test agent description"
|
||||||
|
assert agent.inputs == {"input1": {"type": "string", "value": "test value"}}
|
||||||
|
|
|
@ -1,123 +0,0 @@
|
||||||
import logging
|
|
||||||
import typing
|
|
||||||
|
|
||||||
import autogpt_libs.auth.depends
|
|
||||||
import autogpt_libs.auth.middleware
|
|
||||||
import fastapi
|
|
||||||
import prisma
|
|
||||||
|
|
||||||
import backend.data.graph
|
|
||||||
import backend.integrations.creds_manager
|
|
||||||
import backend.integrations.webhooks.graph_lifecycle_hooks
|
|
||||||
import backend.server.v2.library.db
|
|
||||||
import backend.server.v2.library.model
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
router = fastapi.APIRouter()
|
|
||||||
integration_creds_manager = (
|
|
||||||
backend.integrations.creds_manager.IntegrationCredentialsManager()
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
|
||||||
"/agents",
|
|
||||||
tags=["library", "private"],
|
|
||||||
dependencies=[fastapi.Depends(autogpt_libs.auth.middleware.auth_middleware)],
|
|
||||||
)
|
|
||||||
async def get_library_agents(
|
|
||||||
user_id: typing.Annotated[
|
|
||||||
str, fastapi.Depends(autogpt_libs.auth.depends.get_user_id)
|
|
||||||
]
|
|
||||||
) -> typing.Sequence[backend.server.v2.library.model.LibraryAgent]:
|
|
||||||
"""
|
|
||||||
Get all agents in the user's library, including both created and saved agents.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
agents = await backend.server.v2.library.db.get_library_agents(user_id)
|
|
||||||
return agents
|
|
||||||
except Exception:
|
|
||||||
logger.exception("Exception occurred whilst getting library agents")
|
|
||||||
raise fastapi.HTTPException(
|
|
||||||
status_code=500, detail="Failed to get library agents"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@router.post(
|
|
||||||
"/agents/{store_listing_version_id}",
|
|
||||||
tags=["library", "private"],
|
|
||||||
dependencies=[fastapi.Depends(autogpt_libs.auth.middleware.auth_middleware)],
|
|
||||||
status_code=201,
|
|
||||||
)
|
|
||||||
async def add_agent_to_library(
|
|
||||||
store_listing_version_id: str,
|
|
||||||
user_id: typing.Annotated[
|
|
||||||
str, fastapi.Depends(autogpt_libs.auth.depends.get_user_id)
|
|
||||||
],
|
|
||||||
) -> fastapi.Response:
|
|
||||||
"""
|
|
||||||
Add an agent from the store to the user's library.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
store_listing_version_id (str): ID of the store listing version to add
|
|
||||||
user_id (str): ID of the authenticated user
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
fastapi.Response: 201 status code on success
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
HTTPException: If there is an error adding the agent to the library
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
# Get the graph from the store listing
|
|
||||||
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
|
|
||||||
|
|
||||||
if agent.userId == user_id:
|
|
||||||
raise fastapi.HTTPException(
|
|
||||||
status_code=400, detail="Cannot add own agent to library"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create a new graph from the template
|
|
||||||
graph = await backend.data.graph.get_graph(
|
|
||||||
agent.id, agent.version, template=True, user_id=user_id
|
|
||||||
)
|
|
||||||
|
|
||||||
if not graph:
|
|
||||||
raise fastapi.HTTPException(
|
|
||||||
status_code=404, detail=f"Agent {agent.id} not found"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create a deep copy with new IDs
|
|
||||||
graph.version = 1
|
|
||||||
graph.is_template = False
|
|
||||||
graph.is_active = True
|
|
||||||
graph.reassign_ids(user_id=user_id, reassign_graph_id=True)
|
|
||||||
|
|
||||||
# Save the new graph
|
|
||||||
graph = await backend.data.graph.create_graph(graph, user_id=user_id)
|
|
||||||
graph = (
|
|
||||||
await backend.integrations.webhooks.graph_lifecycle_hooks.on_graph_activate(
|
|
||||||
graph,
|
|
||||||
get_credentials=lambda id: integration_creds_manager.get(user_id, id),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return fastapi.Response(status_code=201)
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
logger.exception("Exception occurred whilst adding agent to library")
|
|
||||||
raise fastapi.HTTPException(
|
|
||||||
status_code=500, detail="Failed to add agent to library"
|
|
||||||
)
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
import fastapi
|
||||||
|
|
||||||
|
from .agents import router as agents_router
|
||||||
|
from .presets import router as presets_router
|
||||||
|
|
||||||
|
router = fastapi.APIRouter()
|
||||||
|
|
||||||
|
router.include_router(presets_router)
|
||||||
|
router.include_router(agents_router)
|
|
@ -0,0 +1,148 @@
|
||||||
|
import logging
|
||||||
|
import typing
|
||||||
|
|
||||||
|
import autogpt_libs.auth.depends
|
||||||
|
import autogpt_libs.auth.middleware
|
||||||
|
import autogpt_libs.utils.cache
|
||||||
|
import fastapi
|
||||||
|
|
||||||
|
import backend.server.v2.library.db
|
||||||
|
import backend.server.v2.library.model
|
||||||
|
import backend.server.v2.store.exceptions
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
router = fastapi.APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
"/agents",
|
||||||
|
tags=["library", "private"],
|
||||||
|
dependencies=[fastapi.Depends(autogpt_libs.auth.middleware.auth_middleware)],
|
||||||
|
)
|
||||||
|
async def get_library_agents(
|
||||||
|
user_id: typing.Annotated[
|
||||||
|
str, fastapi.Depends(autogpt_libs.auth.depends.get_user_id)
|
||||||
|
]
|
||||||
|
) -> typing.Sequence[backend.server.v2.library.model.LibraryAgent]:
|
||||||
|
"""
|
||||||
|
Get all agents in the user's library, including both created and saved agents.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
agents = await backend.server.v2.library.db.get_library_agents(user_id)
|
||||||
|
return agents
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(f"Exception occurred whilst getting library agents: {e}")
|
||||||
|
raise fastapi.HTTPException(
|
||||||
|
status_code=500, detail="Failed to get library agents"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post(
|
||||||
|
"/agents/{store_listing_version_id}",
|
||||||
|
tags=["library", "private"],
|
||||||
|
dependencies=[fastapi.Depends(autogpt_libs.auth.middleware.auth_middleware)],
|
||||||
|
status_code=201,
|
||||||
|
)
|
||||||
|
async def add_agent_to_library(
|
||||||
|
store_listing_version_id: str,
|
||||||
|
user_id: typing.Annotated[
|
||||||
|
str, fastapi.Depends(autogpt_libs.auth.depends.get_user_id)
|
||||||
|
],
|
||||||
|
) -> fastapi.Response:
|
||||||
|
"""
|
||||||
|
Add an agent from the store to the user's library.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
store_listing_version_id (str): ID of the store listing version to add
|
||||||
|
user_id (str): ID of the authenticated user
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
fastapi.Response: 201 status code on success
|
||||||
|
|
||||||
|
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(
|
||||||
|
store_listing_version_id, user_id
|
||||||
|
)
|
||||||
|
return fastapi.Response(status_code=201)
|
||||||
|
|
||||||
|
except backend.server.v2.store.exceptions.AgentNotFoundError:
|
||||||
|
raise fastapi.HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail=f"Store listing version {store_listing_version_id} not found",
|
||||||
|
)
|
||||||
|
except backend.server.v2.store.exceptions.DatabaseError as e:
|
||||||
|
logger.exception(f"Database error occurred whilst adding agent to library: {e}")
|
||||||
|
raise fastapi.HTTPException(
|
||||||
|
status_code=500, detail="Failed to add agent to library"
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(
|
||||||
|
f"Unexpected exception occurred whilst adding agent to library: {e}"
|
||||||
|
)
|
||||||
|
raise fastapi.HTTPException(
|
||||||
|
status_code=500, detail="Failed to add agent to library"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.put(
|
||||||
|
"/agents/{library_agent_id}",
|
||||||
|
tags=["library", "private"],
|
||||||
|
dependencies=[fastapi.Depends(autogpt_libs.auth.middleware.auth_middleware)],
|
||||||
|
status_code=204,
|
||||||
|
)
|
||||||
|
async def update_library_agent(
|
||||||
|
library_agent_id: str,
|
||||||
|
user_id: typing.Annotated[
|
||||||
|
str, fastapi.Depends(autogpt_libs.auth.depends.get_user_id)
|
||||||
|
],
|
||||||
|
auto_update_version: bool = False,
|
||||||
|
is_favorite: bool = False,
|
||||||
|
is_archived: bool = False,
|
||||||
|
is_deleted: bool = False,
|
||||||
|
) -> fastapi.Response:
|
||||||
|
"""
|
||||||
|
Update the library agent with the given fields.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
library_agent_id (str): ID of the library agent to update
|
||||||
|
user_id (str): ID of the authenticated user
|
||||||
|
auto_update_version (bool): Whether to auto-update the agent version
|
||||||
|
is_favorite (bool): Whether the agent is marked as favorite
|
||||||
|
is_archived (bool): Whether the agent is archived
|
||||||
|
is_deleted (bool): Whether the agent is deleted
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
fastapi.Response: 204 status code on success
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
HTTPException: If there is an error updating the library agent
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Use the database function to update the library agent
|
||||||
|
await backend.server.v2.library.db.update_library_agent(
|
||||||
|
library_agent_id,
|
||||||
|
user_id,
|
||||||
|
auto_update_version,
|
||||||
|
is_favorite,
|
||||||
|
is_archived,
|
||||||
|
is_deleted,
|
||||||
|
)
|
||||||
|
return fastapi.Response(status_code=204)
|
||||||
|
|
||||||
|
except backend.server.v2.store.exceptions.DatabaseError as e:
|
||||||
|
logger.exception(f"Database error occurred whilst updating library agent: {e}")
|
||||||
|
raise fastapi.HTTPException(
|
||||||
|
status_code=500, detail="Failed to update library agent"
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(
|
||||||
|
f"Unexpected exception occurred whilst updating library agent: {e}"
|
||||||
|
)
|
||||||
|
raise fastapi.HTTPException(
|
||||||
|
status_code=500, detail="Failed to update library agent"
|
||||||
|
)
|
|
@ -0,0 +1,156 @@
|
||||||
|
import logging
|
||||||
|
import typing
|
||||||
|
|
||||||
|
import autogpt_libs.auth.depends
|
||||||
|
import autogpt_libs.auth.middleware
|
||||||
|
import autogpt_libs.utils.cache
|
||||||
|
import fastapi
|
||||||
|
|
||||||
|
import backend.data.graph
|
||||||
|
import backend.executor
|
||||||
|
import backend.integrations.creds_manager
|
||||||
|
import backend.integrations.webhooks.graph_lifecycle_hooks
|
||||||
|
import backend.server.v2.library.db
|
||||||
|
import backend.server.v2.library.model
|
||||||
|
import backend.util.service
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
router = fastapi.APIRouter()
|
||||||
|
integration_creds_manager = (
|
||||||
|
backend.integrations.creds_manager.IntegrationCredentialsManager()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@autogpt_libs.utils.cache.thread_cached
|
||||||
|
def execution_manager_client() -> backend.executor.ExecutionManager:
|
||||||
|
return backend.util.service.get_service_client(backend.executor.ExecutionManager)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/presets")
|
||||||
|
async def get_presets(
|
||||||
|
user_id: typing.Annotated[
|
||||||
|
str, fastapi.Depends(autogpt_libs.auth.depends.get_user_id)
|
||||||
|
],
|
||||||
|
page: int = 1,
|
||||||
|
page_size: int = 10,
|
||||||
|
) -> backend.server.v2.library.model.LibraryAgentPresetResponse:
|
||||||
|
try:
|
||||||
|
presets = await backend.server.v2.library.db.get_presets(
|
||||||
|
user_id, page, page_size
|
||||||
|
)
|
||||||
|
return presets
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(f"Exception occurred whilst getting presets: {e}")
|
||||||
|
raise fastapi.HTTPException(status_code=500, detail="Failed to get presets")
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/presets/{preset_id}")
|
||||||
|
async def get_preset(
|
||||||
|
preset_id: str,
|
||||||
|
user_id: typing.Annotated[
|
||||||
|
str, fastapi.Depends(autogpt_libs.auth.depends.get_user_id)
|
||||||
|
],
|
||||||
|
) -> backend.server.v2.library.model.LibraryAgentPreset:
|
||||||
|
try:
|
||||||
|
preset = await backend.server.v2.library.db.get_preset(user_id, preset_id)
|
||||||
|
if not preset:
|
||||||
|
raise fastapi.HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail=f"Preset {preset_id} not found",
|
||||||
|
)
|
||||||
|
return preset
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(f"Exception occurred whilst getting preset: {e}")
|
||||||
|
raise fastapi.HTTPException(status_code=500, detail="Failed to get preset")
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/presets")
|
||||||
|
async def create_preset(
|
||||||
|
preset: backend.server.v2.library.model.CreateLibraryAgentPresetRequest,
|
||||||
|
user_id: typing.Annotated[
|
||||||
|
str, fastapi.Depends(autogpt_libs.auth.depends.get_user_id)
|
||||||
|
],
|
||||||
|
) -> backend.server.v2.library.model.LibraryAgentPreset:
|
||||||
|
try:
|
||||||
|
return await backend.server.v2.library.db.create_or_update_preset(
|
||||||
|
user_id, preset
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(f"Exception occurred whilst creating preset: {e}")
|
||||||
|
raise fastapi.HTTPException(status_code=500, detail="Failed to create preset")
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/presets/{preset_id}")
|
||||||
|
async def update_preset(
|
||||||
|
preset_id: str,
|
||||||
|
preset: backend.server.v2.library.model.CreateLibraryAgentPresetRequest,
|
||||||
|
user_id: typing.Annotated[
|
||||||
|
str, fastapi.Depends(autogpt_libs.auth.depends.get_user_id)
|
||||||
|
],
|
||||||
|
) -> backend.server.v2.library.model.LibraryAgentPreset:
|
||||||
|
try:
|
||||||
|
return await backend.server.v2.library.db.create_or_update_preset(
|
||||||
|
user_id, preset, preset_id
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(f"Exception occurred whilst updating preset: {e}")
|
||||||
|
raise fastapi.HTTPException(status_code=500, detail="Failed to update preset")
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/presets/{preset_id}")
|
||||||
|
async def delete_preset(
|
||||||
|
preset_id: str,
|
||||||
|
user_id: typing.Annotated[
|
||||||
|
str, fastapi.Depends(autogpt_libs.auth.depends.get_user_id)
|
||||||
|
],
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
await backend.server.v2.library.db.delete_preset(user_id, preset_id)
|
||||||
|
return fastapi.Response(status_code=204)
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(f"Exception occurred whilst deleting preset: {e}")
|
||||||
|
raise fastapi.HTTPException(status_code=500, detail="Failed to delete preset")
|
||||||
|
|
||||||
|
|
||||||
|
@router.post(
|
||||||
|
path="/presets/{preset_id}/execute",
|
||||||
|
tags=["presets"],
|
||||||
|
dependencies=[fastapi.Depends(autogpt_libs.auth.middleware.auth_middleware)],
|
||||||
|
)
|
||||||
|
async def execute_preset(
|
||||||
|
graph_id: str,
|
||||||
|
graph_version: int,
|
||||||
|
preset_id: str,
|
||||||
|
node_input: dict[typing.Any, typing.Any],
|
||||||
|
user_id: typing.Annotated[
|
||||||
|
str, fastapi.Depends(autogpt_libs.auth.depends.get_user_id)
|
||||||
|
],
|
||||||
|
) -> dict[str, typing.Any]: # FIXME: add proper return type
|
||||||
|
try:
|
||||||
|
preset = await backend.server.v2.library.db.get_preset(user_id, preset_id)
|
||||||
|
if not preset:
|
||||||
|
raise fastapi.HTTPException(status_code=404, detail="Preset not found")
|
||||||
|
|
||||||
|
logger.info(f"Preset inputs: {preset.inputs}")
|
||||||
|
|
||||||
|
updated_node_input = node_input.copy()
|
||||||
|
# Merge in preset input values
|
||||||
|
for key, value in preset.inputs.items():
|
||||||
|
if key not in updated_node_input:
|
||||||
|
updated_node_input[key] = value
|
||||||
|
|
||||||
|
execution = execution_manager_client().add_execution(
|
||||||
|
graph_id=graph_id,
|
||||||
|
graph_version=graph_version,
|
||||||
|
data=updated_node_input,
|
||||||
|
user_id=user_id,
|
||||||
|
preset_id=preset_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(f"Execution added: {execution} with input: {updated_node_input}")
|
||||||
|
|
||||||
|
return {"id": execution.graph_exec_id}
|
||||||
|
except Exception as e:
|
||||||
|
msg = e.__str__().encode().decode("unicode_escape")
|
||||||
|
raise fastapi.HTTPException(status_code=400, detail=msg)
|
|
@ -1,3 +1,5 @@
|
||||||
|
import datetime
|
||||||
|
|
||||||
import autogpt_libs.auth.depends
|
import autogpt_libs.auth.depends
|
||||||
import autogpt_libs.auth.middleware
|
import autogpt_libs.auth.middleware
|
||||||
import fastapi
|
import fastapi
|
||||||
|
@ -35,21 +37,29 @@ def test_get_library_agents_success(mocker: pytest_mock.MockFixture):
|
||||||
mocked_value = [
|
mocked_value = [
|
||||||
backend.server.v2.library.model.LibraryAgent(
|
backend.server.v2.library.model.LibraryAgent(
|
||||||
id="test-agent-1",
|
id="test-agent-1",
|
||||||
version=1,
|
agent_id="test-agent-1",
|
||||||
is_active=True,
|
agent_version=1,
|
||||||
|
preset_id="preset-1",
|
||||||
|
updated_at=datetime.datetime(2023, 1, 1, 0, 0, 0),
|
||||||
|
is_favorite=False,
|
||||||
|
is_created_by_user=True,
|
||||||
|
is_latest_version=True,
|
||||||
name="Test Agent 1",
|
name="Test Agent 1",
|
||||||
description="Test Description 1",
|
description="Test Description 1",
|
||||||
isCreatedByUser=True,
|
|
||||||
input_schema={"type": "object", "properties": {}},
|
input_schema={"type": "object", "properties": {}},
|
||||||
output_schema={"type": "object", "properties": {}},
|
output_schema={"type": "object", "properties": {}},
|
||||||
),
|
),
|
||||||
backend.server.v2.library.model.LibraryAgent(
|
backend.server.v2.library.model.LibraryAgent(
|
||||||
id="test-agent-2",
|
id="test-agent-2",
|
||||||
version=1,
|
agent_id="test-agent-2",
|
||||||
is_active=True,
|
agent_version=1,
|
||||||
|
preset_id="preset-2",
|
||||||
|
updated_at=datetime.datetime(2023, 1, 1, 0, 0, 0),
|
||||||
|
is_favorite=False,
|
||||||
|
is_created_by_user=False,
|
||||||
|
is_latest_version=True,
|
||||||
name="Test Agent 2",
|
name="Test Agent 2",
|
||||||
description="Test Description 2",
|
description="Test Description 2",
|
||||||
isCreatedByUser=False,
|
|
||||||
input_schema={"type": "object", "properties": {}},
|
input_schema={"type": "object", "properties": {}},
|
||||||
output_schema={"type": "object", "properties": {}},
|
output_schema={"type": "object", "properties": {}},
|
||||||
),
|
),
|
||||||
|
@ -65,10 +75,10 @@ def test_get_library_agents_success(mocker: pytest_mock.MockFixture):
|
||||||
for agent in response.json()
|
for agent in response.json()
|
||||||
]
|
]
|
||||||
assert len(data) == 2
|
assert len(data) == 2
|
||||||
assert data[0].id == "test-agent-1"
|
assert data[0].agent_id == "test-agent-1"
|
||||||
assert data[0].isCreatedByUser is True
|
assert data[0].is_created_by_user is True
|
||||||
assert data[1].id == "test-agent-2"
|
assert data[1].agent_id == "test-agent-2"
|
||||||
assert data[1].isCreatedByUser is False
|
assert data[1].is_created_by_user is False
|
||||||
mock_db_call.assert_called_once_with("test-user-id")
|
mock_db_call.assert_called_once_with("test-user-id")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -325,7 +325,10 @@ async def get_store_submissions(
|
||||||
where = prisma.types.StoreSubmissionWhereInput(user_id=user_id)
|
where = prisma.types.StoreSubmissionWhereInput(user_id=user_id)
|
||||||
# Query submissions from database
|
# Query submissions from database
|
||||||
submissions = await prisma.models.StoreSubmission.prisma().find_many(
|
submissions = await prisma.models.StoreSubmission.prisma().find_many(
|
||||||
where=where, skip=skip, take=page_size, order=[{"date_submitted": "desc"}]
|
where=where,
|
||||||
|
skip=skip,
|
||||||
|
take=page_size,
|
||||||
|
order=[{"date_submitted": "desc"}],
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get total count for pagination
|
# Get total count for pagination
|
||||||
|
@ -405,9 +408,7 @@ async def delete_store_submission(
|
||||||
)
|
)
|
||||||
|
|
||||||
# Delete the submission
|
# Delete the submission
|
||||||
await prisma.models.StoreListing.prisma().delete(
|
await prisma.models.StoreListing.prisma().delete(where={"id": submission.id})
|
||||||
where=prisma.types.StoreListingWhereUniqueInput(id=submission.id)
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"Successfully deleted submission {submission_id} for user {user_id}"
|
f"Successfully deleted submission {submission_id} for user {user_id}"
|
||||||
|
@ -504,7 +505,15 @@ async def create_store_submission(
|
||||||
"subHeading": sub_heading,
|
"subHeading": sub_heading,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
|
include={"StoreListingVersions": True},
|
||||||
|
)
|
||||||
|
|
||||||
|
slv_id = (
|
||||||
|
listing.StoreListingVersions[0].id
|
||||||
|
if listing.StoreListingVersions is not None
|
||||||
|
and len(listing.StoreListingVersions) > 0
|
||||||
|
else None
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.debug(f"Created store listing for agent {agent_id}")
|
logger.debug(f"Created store listing for agent {agent_id}")
|
||||||
|
@ -521,6 +530,7 @@ async def create_store_submission(
|
||||||
status=prisma.enums.SubmissionStatus.PENDING,
|
status=prisma.enums.SubmissionStatus.PENDING,
|
||||||
runs=0,
|
runs=0,
|
||||||
rating=0.0,
|
rating=0.0,
|
||||||
|
store_listing_version_id=slv_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
except (
|
except (
|
||||||
|
@ -811,9 +821,7 @@ async def get_agent(
|
||||||
|
|
||||||
agent = store_listing_version.Agent
|
agent = store_listing_version.Agent
|
||||||
|
|
||||||
graph = await backend.data.graph.get_graph(
|
graph = await backend.data.graph.get_graph(agent.id, agent.version)
|
||||||
agent.id, agent.version, template=True
|
|
||||||
)
|
|
||||||
|
|
||||||
if not graph:
|
if not graph:
|
||||||
raise fastapi.HTTPException(
|
raise fastapi.HTTPException(
|
||||||
|
@ -832,3 +840,74 @@ async def get_agent(
|
||||||
raise backend.server.v2.store.exceptions.DatabaseError(
|
raise backend.server.v2.store.exceptions.DatabaseError(
|
||||||
"Failed to fetch agent"
|
"Failed to fetch agent"
|
||||||
) from e
|
) from e
|
||||||
|
|
||||||
|
|
||||||
|
async def review_store_submission(
|
||||||
|
store_listing_version_id: str, is_approved: bool, comments: str, reviewer_id: str
|
||||||
|
) -> prisma.models.StoreListingSubmission:
|
||||||
|
"""Review a store listing submission."""
|
||||||
|
try:
|
||||||
|
store_listing_version = (
|
||||||
|
await prisma.models.StoreListingVersion.prisma().find_unique(
|
||||||
|
where={"id": store_listing_version_id},
|
||||||
|
include={"StoreListing": True},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if not store_listing_version or not store_listing_version.StoreListing:
|
||||||
|
raise fastapi.HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail=f"Store listing version {store_listing_version_id} not found",
|
||||||
|
)
|
||||||
|
|
||||||
|
status = (
|
||||||
|
prisma.enums.SubmissionStatus.APPROVED
|
||||||
|
if is_approved
|
||||||
|
else prisma.enums.SubmissionStatus.REJECTED
|
||||||
|
)
|
||||||
|
|
||||||
|
create_data = prisma.types.StoreListingSubmissionCreateInput(
|
||||||
|
StoreListingVersion={"connect": {"id": store_listing_version_id}},
|
||||||
|
Status=status,
|
||||||
|
reviewComments=comments,
|
||||||
|
Reviewer={"connect": {"id": reviewer_id}},
|
||||||
|
StoreListing={"connect": {"id": store_listing_version.StoreListing.id}},
|
||||||
|
createdAt=datetime.now(),
|
||||||
|
updatedAt=datetime.now(),
|
||||||
|
)
|
||||||
|
|
||||||
|
update_data = prisma.types.StoreListingSubmissionUpdateInput(
|
||||||
|
Status=status,
|
||||||
|
reviewComments=comments,
|
||||||
|
Reviewer={"connect": {"id": reviewer_id}},
|
||||||
|
StoreListing={"connect": {"id": store_listing_version.StoreListing.id}},
|
||||||
|
updatedAt=datetime.now(),
|
||||||
|
)
|
||||||
|
|
||||||
|
if is_approved:
|
||||||
|
await prisma.models.StoreListing.prisma().update(
|
||||||
|
where={"id": store_listing_version.StoreListing.id},
|
||||||
|
data={"isApproved": True},
|
||||||
|
)
|
||||||
|
|
||||||
|
submission = await prisma.models.StoreListingSubmission.prisma().upsert(
|
||||||
|
where={"storeListingVersionId": store_listing_version_id},
|
||||||
|
data=prisma.types.StoreListingSubmissionUpsertInput(
|
||||||
|
create=create_data,
|
||||||
|
update=update_data,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
if not submission:
|
||||||
|
raise fastapi.HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail=f"Store listing submission {store_listing_version_id} not found",
|
||||||
|
)
|
||||||
|
|
||||||
|
return submission
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error reviewing store submission: {str(e)}")
|
||||||
|
raise backend.server.v2.store.exceptions.DatabaseError(
|
||||||
|
"Failed to review store submission"
|
||||||
|
) from e
|
||||||
|
|
|
@ -115,6 +115,7 @@ class StoreSubmission(pydantic.BaseModel):
|
||||||
status: prisma.enums.SubmissionStatus
|
status: prisma.enums.SubmissionStatus
|
||||||
runs: int
|
runs: int
|
||||||
rating: float
|
rating: float
|
||||||
|
store_listing_version_id: str | None = None
|
||||||
|
|
||||||
|
|
||||||
class StoreSubmissionsResponse(pydantic.BaseModel):
|
class StoreSubmissionsResponse(pydantic.BaseModel):
|
||||||
|
@ -151,3 +152,9 @@ class StoreReviewCreate(pydantic.BaseModel):
|
||||||
store_listing_version_id: str
|
store_listing_version_id: str
|
||||||
score: int
|
score: int
|
||||||
comments: str | None = None
|
comments: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class ReviewSubmissionRequest(pydantic.BaseModel):
|
||||||
|
store_listing_version_id: str
|
||||||
|
isApproved: bool
|
||||||
|
comments: str
|
||||||
|
|
|
@ -642,3 +642,33 @@ async def download_agent_file(
|
||||||
return fastapi.responses.FileResponse(
|
return fastapi.responses.FileResponse(
|
||||||
tmp_file.name, filename=file_name, media_type="application/json"
|
tmp_file.name, filename=file_name, media_type="application/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post(
|
||||||
|
"/submissions/review/{store_listing_version_id}",
|
||||||
|
tags=["store", "private"],
|
||||||
|
)
|
||||||
|
async def review_submission(
|
||||||
|
request: backend.server.v2.store.model.ReviewSubmissionRequest,
|
||||||
|
user: typing.Annotated[
|
||||||
|
autogpt_libs.auth.models.User,
|
||||||
|
fastapi.Depends(autogpt_libs.auth.depends.requires_admin_user),
|
||||||
|
],
|
||||||
|
):
|
||||||
|
# Proceed with the review submission logic
|
||||||
|
try:
|
||||||
|
submission = await backend.server.v2.store.db.review_store_submission(
|
||||||
|
store_listing_version_id=request.store_listing_version_id,
|
||||||
|
is_approved=request.isApproved,
|
||||||
|
comments=request.comments,
|
||||||
|
reviewer_id=user.user_id,
|
||||||
|
)
|
||||||
|
return submission
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Exception occurred whilst reviewing store submission")
|
||||||
|
return fastapi.responses.JSONResponse(
|
||||||
|
status_code=500,
|
||||||
|
content={
|
||||||
|
"detail": "An error occurred while reviewing the store submission"
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
|
@ -253,7 +253,7 @@ async def block_autogen_agent():
|
||||||
test_graph = await create_graph(create_test_graph(), user_id=test_user.id)
|
test_graph = await create_graph(create_test_graph(), user_id=test_user.id)
|
||||||
input_data = {"input": "Write me a block that writes a string into a file."}
|
input_data = {"input": "Write me a block that writes a string into a file."}
|
||||||
response = await server.agent_server.test_execute_graph(
|
response = await server.agent_server.test_execute_graph(
|
||||||
test_graph.id, input_data, test_user.id
|
test_graph.id, test_graph.version, input_data, test_user.id
|
||||||
)
|
)
|
||||||
print(response)
|
print(response)
|
||||||
result = await wait_execution(
|
result = await wait_execution(
|
||||||
|
|
|
@ -157,7 +157,7 @@ async def reddit_marketing_agent():
|
||||||
test_graph = await create_graph(create_test_graph(), user_id=test_user.id)
|
test_graph = await create_graph(create_test_graph(), user_id=test_user.id)
|
||||||
input_data = {"subreddit": "AutoGPT"}
|
input_data = {"subreddit": "AutoGPT"}
|
||||||
response = await server.agent_server.test_execute_graph(
|
response = await server.agent_server.test_execute_graph(
|
||||||
test_graph.id, input_data, test_user.id
|
test_graph.id, test_graph.version, input_data, test_user.id
|
||||||
)
|
)
|
||||||
print(response)
|
print(response)
|
||||||
result = await wait_execution(test_user.id, test_graph.id, response["id"], 120)
|
result = await wait_execution(test_user.id, test_graph.id, response["id"], 120)
|
||||||
|
|
|
@ -8,12 +8,19 @@ from backend.data.user import get_or_create_user
|
||||||
from backend.util.test import SpinTestServer, wait_execution
|
from backend.util.test import SpinTestServer, wait_execution
|
||||||
|
|
||||||
|
|
||||||
async def create_test_user() -> User:
|
async def create_test_user(alt_user: bool = False) -> User:
|
||||||
test_user_data = {
|
if alt_user:
|
||||||
"sub": "ef3b97d7-1161-4eb4-92b2-10c24fb154c1",
|
test_user_data = {
|
||||||
"email": "testuser#example.com",
|
"sub": "3e53486c-cf57-477e-ba2a-cb02dc828e1b",
|
||||||
"name": "Test User",
|
"email": "testuser2#example.com",
|
||||||
}
|
"name": "Test User 2",
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
test_user_data = {
|
||||||
|
"sub": "ef3b97d7-1161-4eb4-92b2-10c24fb154c1",
|
||||||
|
"email": "testuser#example.com",
|
||||||
|
"name": "Test User",
|
||||||
|
}
|
||||||
user = await get_or_create_user(test_user_data)
|
user = await get_or_create_user(test_user_data)
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
@ -79,7 +86,7 @@ async def sample_agent():
|
||||||
test_graph = await create_graph(create_test_graph(), test_user.id)
|
test_graph = await create_graph(create_test_graph(), test_user.id)
|
||||||
input_data = {"input_1": "Hello", "input_2": "World"}
|
input_data = {"input_1": "Hello", "input_2": "World"}
|
||||||
response = await server.agent_server.test_execute_graph(
|
response = await server.agent_server.test_execute_graph(
|
||||||
test_graph.id, input_data, test_user.id
|
test_graph.id, test_graph.version, input_data, test_user.id
|
||||||
)
|
)
|
||||||
print(response)
|
print(response)
|
||||||
result = await wait_execution(test_user.id, test_graph.id, response["id"], 10)
|
result = await wait_execution(test_user.id, test_graph.id, response["id"], 10)
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "AgentPreset" ADD COLUMN "isDeleted" BOOLEAN NOT NULL DEFAULT false;
|
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the `UserAgent` table. If the table is not empty, all the data it contains will be lost.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "UserAgent" DROP CONSTRAINT "UserAgent_agentId_agentVersion_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "UserAgent" DROP CONSTRAINT "UserAgent_agentPresetId_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "UserAgent" DROP CONSTRAINT "UserAgent_userId_fkey";
|
||||||
|
|
||||||
|
-- DropTable
|
||||||
|
DROP TABLE "UserAgent";
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "LibraryAgent" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"userId" TEXT NOT NULL,
|
||||||
|
"agentId" TEXT NOT NULL,
|
||||||
|
"agentVersion" INTEGER NOT NULL,
|
||||||
|
"agentPresetId" TEXT,
|
||||||
|
"isFavorite" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"isCreatedByUser" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"isArchived" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"isDeleted" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
|
||||||
|
CONSTRAINT "LibraryAgent_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "LibraryAgent_userId_idx" ON "LibraryAgent"("userId");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "LibraryAgent" ADD CONSTRAINT "LibraryAgent_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "LibraryAgent" ADD CONSTRAINT "LibraryAgent_agentId_agentVersion_fkey" FOREIGN KEY ("agentId", "agentVersion") REFERENCES "AgentGraph"("id", "version") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "LibraryAgent" ADD CONSTRAINT "LibraryAgent_agentPresetId_fkey" FOREIGN KEY ("agentPresetId") REFERENCES "AgentPreset"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
|
@ -0,0 +1,2 @@
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "LibraryAgent" ADD COLUMN "useGraphIsActiveVersion" BOOLEAN NOT NULL DEFAULT false;
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- A unique constraint covering the columns `[agentId]` on the table `StoreListing` will be added. If there are existing duplicate values, this will fail.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- DropIndex
|
||||||
|
DROP INDEX "StoreListing_agentId_idx";
|
||||||
|
|
||||||
|
-- DropIndex
|
||||||
|
DROP INDEX "StoreListing_isApproved_idx";
|
||||||
|
|
||||||
|
-- DropIndex
|
||||||
|
DROP INDEX "StoreListingVersion_agentId_agentVersion_isApproved_idx";
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "StoreListing_agentId_owningUserId_idx" ON "StoreListing"("agentId", "owningUserId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "StoreListing_isDeleted_isApproved_idx" ON "StoreListing"("isDeleted", "isApproved");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "StoreListing_isDeleted_idx" ON "StoreListing"("isDeleted");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "StoreListing_agentId_key" ON "StoreListing"("agentId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "StoreListingVersion_agentId_agentVersion_isDeleted_idx" ON "StoreListingVersion"("agentId", "agentVersion", "isDeleted");
|
|
@ -30,7 +30,7 @@ model User {
|
||||||
CreditTransaction CreditTransaction[]
|
CreditTransaction CreditTransaction[]
|
||||||
|
|
||||||
AgentPreset AgentPreset[]
|
AgentPreset AgentPreset[]
|
||||||
UserAgent UserAgent[]
|
LibraryAgent LibraryAgent[]
|
||||||
|
|
||||||
Profile Profile[]
|
Profile Profile[]
|
||||||
StoreListing StoreListing[]
|
StoreListing StoreListing[]
|
||||||
|
@ -65,7 +65,7 @@ model AgentGraph {
|
||||||
AgentGraphExecution AgentGraphExecution[]
|
AgentGraphExecution AgentGraphExecution[]
|
||||||
|
|
||||||
AgentPreset AgentPreset[]
|
AgentPreset AgentPreset[]
|
||||||
UserAgent UserAgent[]
|
LibraryAgent LibraryAgent[]
|
||||||
StoreListing StoreListing[]
|
StoreListing StoreListing[]
|
||||||
StoreListingVersion StoreListingVersion?
|
StoreListingVersion StoreListingVersion?
|
||||||
|
|
||||||
|
@ -103,15 +103,17 @@ model AgentPreset {
|
||||||
Agent AgentGraph @relation(fields: [agentId, agentVersion], references: [id, version], onDelete: Cascade)
|
Agent AgentGraph @relation(fields: [agentId, agentVersion], references: [id, version], onDelete: Cascade)
|
||||||
|
|
||||||
InputPresets AgentNodeExecutionInputOutput[] @relation("AgentPresetsInputData")
|
InputPresets AgentNodeExecutionInputOutput[] @relation("AgentPresetsInputData")
|
||||||
UserAgents UserAgent[]
|
LibraryAgents LibraryAgent[]
|
||||||
AgentExecution AgentGraphExecution[]
|
AgentExecution AgentGraphExecution[]
|
||||||
|
|
||||||
|
isDeleted Boolean @default(false)
|
||||||
|
|
||||||
@@index([userId])
|
@@index([userId])
|
||||||
}
|
}
|
||||||
|
|
||||||
// For the library page
|
// For the library page
|
||||||
// It is a user controlled list of agents, that they will see in there library
|
// It is a user controlled list of agents, that they will see in there library
|
||||||
model UserAgent {
|
model LibraryAgent {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @default(now()) @updatedAt
|
updatedAt DateTime @default(now()) @updatedAt
|
||||||
|
@ -126,6 +128,8 @@ model UserAgent {
|
||||||
agentPresetId String?
|
agentPresetId String?
|
||||||
AgentPreset AgentPreset? @relation(fields: [agentPresetId], references: [id])
|
AgentPreset AgentPreset? @relation(fields: [agentPresetId], references: [id])
|
||||||
|
|
||||||
|
useGraphIsActiveVersion Boolean @default(false)
|
||||||
|
|
||||||
isFavorite Boolean @default(false)
|
isFavorite Boolean @default(false)
|
||||||
isCreatedByUser Boolean @default(false)
|
isCreatedByUser Boolean @default(false)
|
||||||
isArchived Boolean @default(false)
|
isArchived Boolean @default(false)
|
||||||
|
@ -235,7 +239,7 @@ model AgentGraphExecution {
|
||||||
|
|
||||||
AgentNodeExecutions AgentNodeExecution[]
|
AgentNodeExecutions AgentNodeExecution[]
|
||||||
|
|
||||||
// Link to User model
|
// Link to User model -- Executed by this user
|
||||||
userId String
|
userId String
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
@ -443,6 +447,8 @@ view Creator {
|
||||||
agent_rating Float
|
agent_rating Float
|
||||||
agent_runs Int
|
agent_runs Int
|
||||||
is_featured Boolean
|
is_featured Boolean
|
||||||
|
|
||||||
|
// Index or unique are not applied to views
|
||||||
}
|
}
|
||||||
|
|
||||||
view StoreAgent {
|
view StoreAgent {
|
||||||
|
@ -465,11 +471,7 @@ view StoreAgent {
|
||||||
rating Float
|
rating Float
|
||||||
versions String[]
|
versions String[]
|
||||||
|
|
||||||
@@unique([creator_username, slug])
|
// Index or unique are not applied to views
|
||||||
@@index([creator_username])
|
|
||||||
@@index([featured])
|
|
||||||
@@index([categories])
|
|
||||||
@@index([storeListingVersionId])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
view StoreSubmission {
|
view StoreSubmission {
|
||||||
|
@ -487,7 +489,7 @@ view StoreSubmission {
|
||||||
agent_id String
|
agent_id String
|
||||||
agent_version Int
|
agent_version Int
|
||||||
|
|
||||||
@@index([user_id])
|
// Index or unique are not applied to views
|
||||||
}
|
}
|
||||||
|
|
||||||
model StoreListing {
|
model StoreListing {
|
||||||
|
@ -510,9 +512,13 @@ model StoreListing {
|
||||||
StoreListingVersions StoreListingVersion[]
|
StoreListingVersions StoreListingVersion[]
|
||||||
StoreListingSubmission StoreListingSubmission[]
|
StoreListingSubmission StoreListingSubmission[]
|
||||||
|
|
||||||
@@index([isApproved])
|
// Unique index on agentId to ensure only one listing per agent, regardless of number of versions the agent has.
|
||||||
@@index([agentId])
|
@@unique([agentId])
|
||||||
|
@@index([agentId, owningUserId])
|
||||||
@@index([owningUserId])
|
@@index([owningUserId])
|
||||||
|
// Used in the view query
|
||||||
|
@@index([isDeleted, isApproved])
|
||||||
|
@@index([isDeleted])
|
||||||
}
|
}
|
||||||
|
|
||||||
model StoreListingVersion {
|
model StoreListingVersion {
|
||||||
|
@ -553,7 +559,7 @@ model StoreListingVersion {
|
||||||
StoreListingReview StoreListingReview[]
|
StoreListingReview StoreListingReview[]
|
||||||
|
|
||||||
@@unique([agentId, agentVersion])
|
@@unique([agentId, agentVersion])
|
||||||
@@index([agentId, agentVersion, isApproved])
|
@@index([agentId, agentVersion, isDeleted])
|
||||||
}
|
}
|
||||||
|
|
||||||
model StoreListingReview {
|
model StoreListingReview {
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
|
import autogpt_libs.auth.models
|
||||||
|
import fastapi.exceptions
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
import backend.server.v2.store.model
|
||||||
from backend.blocks.basic import AgentInputBlock, AgentOutputBlock, StoreValueBlock
|
from backend.blocks.basic import AgentInputBlock, AgentOutputBlock, StoreValueBlock
|
||||||
from backend.data.block import BlockSchema
|
from backend.data.block import BlockSchema
|
||||||
from backend.data.graph import Graph, Link, Node
|
from backend.data.graph import Graph, Link, Node
|
||||||
|
@ -202,3 +205,92 @@ async def test_clean_graph(server: SpinTestServer):
|
||||||
n for n in created_graph.nodes if n.block_id == AgentInputBlock().id
|
n for n in created_graph.nodes if n.block_id == AgentInputBlock().id
|
||||||
)
|
)
|
||||||
assert input_node.input_default["value"] == ""
|
assert input_node.input_default["value"] == ""
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio(scope="session")
|
||||||
|
async def test_access_store_listing_graph(server: SpinTestServer):
|
||||||
|
"""
|
||||||
|
Test the access of a store listing graph.
|
||||||
|
"""
|
||||||
|
graph = Graph(
|
||||||
|
id="test_clean_graph",
|
||||||
|
name="Test Clean Graph",
|
||||||
|
description="Test graph cleaning",
|
||||||
|
nodes=[
|
||||||
|
Node(
|
||||||
|
id="input_node",
|
||||||
|
block_id=AgentInputBlock().id,
|
||||||
|
input_default={
|
||||||
|
"name": "test_input",
|
||||||
|
"value": "test value",
|
||||||
|
"description": "Test input description",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
links=[],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create graph and get model
|
||||||
|
create_graph = CreateGraph(graph=graph)
|
||||||
|
created_graph = await server.agent_server.test_create_graph(
|
||||||
|
create_graph, DEFAULT_USER_ID
|
||||||
|
)
|
||||||
|
|
||||||
|
store_submission_request = backend.server.v2.store.model.StoreSubmissionRequest(
|
||||||
|
agent_id=created_graph.id,
|
||||||
|
agent_version=created_graph.version,
|
||||||
|
slug="test-slug",
|
||||||
|
name="Test name",
|
||||||
|
sub_heading="Test sub heading",
|
||||||
|
video_url=None,
|
||||||
|
image_urls=[],
|
||||||
|
description="Test description",
|
||||||
|
categories=[],
|
||||||
|
)
|
||||||
|
|
||||||
|
# First we check the graph an not be accessed by a different user
|
||||||
|
with pytest.raises(fastapi.exceptions.HTTPException) as exc_info:
|
||||||
|
await server.agent_server.test_get_graph(
|
||||||
|
created_graph.id,
|
||||||
|
created_graph.version,
|
||||||
|
"3e53486c-cf57-477e-ba2a-cb02dc828e1b",
|
||||||
|
)
|
||||||
|
assert exc_info.value.status_code == 404
|
||||||
|
assert "Graph" in str(exc_info.value.detail)
|
||||||
|
|
||||||
|
# Now we create a store listing
|
||||||
|
store_listing = await server.agent_server.test_create_store_listing(
|
||||||
|
store_submission_request, DEFAULT_USER_ID
|
||||||
|
)
|
||||||
|
|
||||||
|
if isinstance(store_listing, fastapi.responses.JSONResponse):
|
||||||
|
assert False, "Failed to create store listing"
|
||||||
|
|
||||||
|
slv_id = (
|
||||||
|
store_listing.store_listing_version_id
|
||||||
|
if store_listing.store_listing_version_id is not None
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|
||||||
|
assert slv_id is not None
|
||||||
|
|
||||||
|
admin = autogpt_libs.auth.models.User(
|
||||||
|
user_id="3e53486c-cf57-477e-ba2a-cb02dc828e1b",
|
||||||
|
role="admin",
|
||||||
|
email="admin@example.com",
|
||||||
|
phone_number="1234567890",
|
||||||
|
)
|
||||||
|
await server.agent_server.test_review_store_listing(
|
||||||
|
backend.server.v2.store.model.ReviewSubmissionRequest(
|
||||||
|
store_listing_version_id=slv_id,
|
||||||
|
isApproved=True,
|
||||||
|
comments="Test comments",
|
||||||
|
),
|
||||||
|
admin,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Now we check the graph can be accessed by a user that does not own the graph
|
||||||
|
got_graph = await server.agent_server.test_get_graph(
|
||||||
|
created_graph.id, created_graph.version, "3e53486c-cf57-477e-ba2a-cb02dc828e1b"
|
||||||
|
)
|
||||||
|
assert got_graph is not None
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
import autogpt_libs.auth.models
|
||||||
|
import fastapi.responses
|
||||||
import pytest
|
import pytest
|
||||||
from prisma.models import User
|
from prisma.models import User
|
||||||
|
|
||||||
from backend.blocks.basic import FindInDictionaryBlock, StoreValueBlock
|
import backend.server.v2.library.model
|
||||||
|
import backend.server.v2.store.model
|
||||||
|
from backend.blocks.basic import AgentInputBlock, FindInDictionaryBlock, StoreValueBlock
|
||||||
from backend.blocks.maths import CalculatorBlock, Operation
|
from backend.blocks.maths import CalculatorBlock, Operation
|
||||||
from backend.data import execution, graph
|
from backend.data import execution, graph
|
||||||
from backend.server.model import CreateGraph
|
from backend.server.model import CreateGraph
|
||||||
|
@ -31,7 +35,7 @@ async def execute_graph(
|
||||||
|
|
||||||
# --- Test adding new executions --- #
|
# --- Test adding new executions --- #
|
||||||
response = await agent_server.test_execute_graph(
|
response = await agent_server.test_execute_graph(
|
||||||
test_graph.id, input_data, test_user.id
|
test_graph.id, test_graph.version, input_data, test_user.id
|
||||||
)
|
)
|
||||||
graph_exec_id = response["id"]
|
graph_exec_id = response["id"]
|
||||||
logger.info(f"Created execution with ID: {graph_exec_id}")
|
logger.info(f"Created execution with ID: {graph_exec_id}")
|
||||||
|
@ -287,3 +291,255 @@ async def test_static_input_link_on_graph(server: SpinTestServer):
|
||||||
assert exec_data.status == execution.ExecutionStatus.COMPLETED
|
assert exec_data.status == execution.ExecutionStatus.COMPLETED
|
||||||
assert exec_data.output_data == {"result": [9]}
|
assert exec_data.output_data == {"result": [9]}
|
||||||
logger.info("Completed test_static_input_link_on_graph")
|
logger.info("Completed test_static_input_link_on_graph")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio(scope="session")
|
||||||
|
async def test_execute_preset(server: SpinTestServer):
|
||||||
|
"""
|
||||||
|
Test executing a preset.
|
||||||
|
|
||||||
|
This test ensures that:
|
||||||
|
1. A preset can be successfully executed
|
||||||
|
2. The execution results are correct
|
||||||
|
|
||||||
|
Args:
|
||||||
|
server (SpinTestServer): The test server instance.
|
||||||
|
"""
|
||||||
|
# Create test graph and user
|
||||||
|
nodes = [
|
||||||
|
graph.Node( # 0
|
||||||
|
block_id=AgentInputBlock().id,
|
||||||
|
input_default={"name": "dictionary"},
|
||||||
|
),
|
||||||
|
graph.Node( # 1
|
||||||
|
block_id=AgentInputBlock().id,
|
||||||
|
input_default={"name": "selected_value"},
|
||||||
|
),
|
||||||
|
graph.Node( # 2
|
||||||
|
block_id=StoreValueBlock().id,
|
||||||
|
input_default={"input": {"key1": "Hi", "key2": "Everyone"}},
|
||||||
|
),
|
||||||
|
graph.Node( # 3
|
||||||
|
block_id=FindInDictionaryBlock().id,
|
||||||
|
input_default={"key": "", "input": {}},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
links = [
|
||||||
|
graph.Link(
|
||||||
|
source_id=nodes[0].id,
|
||||||
|
sink_id=nodes[2].id,
|
||||||
|
source_name="result",
|
||||||
|
sink_name="input",
|
||||||
|
),
|
||||||
|
graph.Link(
|
||||||
|
source_id=nodes[1].id,
|
||||||
|
sink_id=nodes[3].id,
|
||||||
|
source_name="result",
|
||||||
|
sink_name="key",
|
||||||
|
),
|
||||||
|
graph.Link(
|
||||||
|
source_id=nodes[2].id,
|
||||||
|
sink_id=nodes[3].id,
|
||||||
|
source_name="output",
|
||||||
|
sink_name="input",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
test_graph = graph.Graph(
|
||||||
|
name="TestGraph",
|
||||||
|
description="Test graph",
|
||||||
|
nodes=nodes,
|
||||||
|
links=links,
|
||||||
|
)
|
||||||
|
test_user = await create_test_user()
|
||||||
|
test_graph = await create_graph(server, test_graph, test_user)
|
||||||
|
|
||||||
|
# Create preset with initial values
|
||||||
|
preset = backend.server.v2.library.model.CreateLibraryAgentPresetRequest(
|
||||||
|
name="Test Preset With Clash",
|
||||||
|
description="Test preset with clashing input values",
|
||||||
|
agent_id=test_graph.id,
|
||||||
|
agent_version=test_graph.version,
|
||||||
|
inputs={
|
||||||
|
"dictionary": {"key1": "Hello", "key2": "World"},
|
||||||
|
"selected_value": "key2",
|
||||||
|
},
|
||||||
|
is_active=True,
|
||||||
|
)
|
||||||
|
created_preset = await server.agent_server.test_create_preset(preset, test_user.id)
|
||||||
|
|
||||||
|
# Execute preset with overriding values
|
||||||
|
result = await server.agent_server.test_execute_preset(
|
||||||
|
graph_id=test_graph.id,
|
||||||
|
graph_version=test_graph.version,
|
||||||
|
preset_id=created_preset.id,
|
||||||
|
node_input={},
|
||||||
|
user_id=test_user.id,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify execution
|
||||||
|
assert result is not None
|
||||||
|
graph_exec_id = result["id"]
|
||||||
|
|
||||||
|
# Wait for execution to complete
|
||||||
|
executions = await wait_execution(test_user.id, test_graph.id, graph_exec_id)
|
||||||
|
assert len(executions) == 4
|
||||||
|
|
||||||
|
# FindInDictionaryBlock should wait for the input pin to be provided,
|
||||||
|
# Hence executing extraction of "key" from {"key1": "value1", "key2": "value2"}
|
||||||
|
assert executions[3].status == execution.ExecutionStatus.COMPLETED
|
||||||
|
assert executions[3].output_data == {"output": ["World"]}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio(scope="session")
|
||||||
|
async def test_execute_preset_with_clash(server: SpinTestServer):
|
||||||
|
"""
|
||||||
|
Test executing a preset with clashing input data.
|
||||||
|
"""
|
||||||
|
# Create test graph and user
|
||||||
|
nodes = [
|
||||||
|
graph.Node( # 0
|
||||||
|
block_id=AgentInputBlock().id,
|
||||||
|
input_default={"name": "dictionary"},
|
||||||
|
),
|
||||||
|
graph.Node( # 1
|
||||||
|
block_id=AgentInputBlock().id,
|
||||||
|
input_default={"name": "selected_value"},
|
||||||
|
),
|
||||||
|
graph.Node( # 2
|
||||||
|
block_id=StoreValueBlock().id,
|
||||||
|
input_default={"input": {"key1": "Hi", "key2": "Everyone"}},
|
||||||
|
),
|
||||||
|
graph.Node( # 3
|
||||||
|
block_id=FindInDictionaryBlock().id,
|
||||||
|
input_default={"key": "", "input": {}},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
links = [
|
||||||
|
graph.Link(
|
||||||
|
source_id=nodes[0].id,
|
||||||
|
sink_id=nodes[2].id,
|
||||||
|
source_name="result",
|
||||||
|
sink_name="input",
|
||||||
|
),
|
||||||
|
graph.Link(
|
||||||
|
source_id=nodes[1].id,
|
||||||
|
sink_id=nodes[3].id,
|
||||||
|
source_name="result",
|
||||||
|
sink_name="key",
|
||||||
|
),
|
||||||
|
graph.Link(
|
||||||
|
source_id=nodes[2].id,
|
||||||
|
sink_id=nodes[3].id,
|
||||||
|
source_name="output",
|
||||||
|
sink_name="input",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
test_graph = graph.Graph(
|
||||||
|
name="TestGraph",
|
||||||
|
description="Test graph",
|
||||||
|
nodes=nodes,
|
||||||
|
links=links,
|
||||||
|
)
|
||||||
|
test_user = await create_test_user()
|
||||||
|
test_graph = await create_graph(server, test_graph, test_user)
|
||||||
|
|
||||||
|
# Create preset with initial values
|
||||||
|
preset = backend.server.v2.library.model.CreateLibraryAgentPresetRequest(
|
||||||
|
name="Test Preset With Clash",
|
||||||
|
description="Test preset with clashing input values",
|
||||||
|
agent_id=test_graph.id,
|
||||||
|
agent_version=test_graph.version,
|
||||||
|
inputs={
|
||||||
|
"dictionary": {"key1": "Hello", "key2": "World"},
|
||||||
|
"selected_value": "key2",
|
||||||
|
},
|
||||||
|
is_active=True,
|
||||||
|
)
|
||||||
|
created_preset = await server.agent_server.test_create_preset(preset, test_user.id)
|
||||||
|
|
||||||
|
# Execute preset with overriding values
|
||||||
|
result = await server.agent_server.test_execute_preset(
|
||||||
|
graph_id=test_graph.id,
|
||||||
|
graph_version=test_graph.version,
|
||||||
|
preset_id=created_preset.id,
|
||||||
|
node_input={"selected_value": "key1"},
|
||||||
|
user_id=test_user.id,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify execution
|
||||||
|
assert result is not None
|
||||||
|
graph_exec_id = result["id"]
|
||||||
|
|
||||||
|
# Wait for execution to complete
|
||||||
|
executions = await wait_execution(test_user.id, test_graph.id, graph_exec_id)
|
||||||
|
assert len(executions) == 4
|
||||||
|
|
||||||
|
# FindInDictionaryBlock should wait for the input pin to be provided,
|
||||||
|
# Hence executing extraction of "key" from {"key1": "value1", "key2": "value2"}
|
||||||
|
assert executions[3].status == execution.ExecutionStatus.COMPLETED
|
||||||
|
assert executions[3].output_data == {"output": ["Hello"]}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio(scope="session")
|
||||||
|
async def test_store_listing_graph(server: SpinTestServer):
|
||||||
|
logger.info("Starting test_agent_execution")
|
||||||
|
test_user = await create_test_user()
|
||||||
|
test_graph = await create_graph(server, create_test_graph(), test_user)
|
||||||
|
|
||||||
|
store_submission_request = backend.server.v2.store.model.StoreSubmissionRequest(
|
||||||
|
agent_id=test_graph.id,
|
||||||
|
agent_version=test_graph.version,
|
||||||
|
slug="test-slug",
|
||||||
|
name="Test name",
|
||||||
|
sub_heading="Test sub heading",
|
||||||
|
video_url=None,
|
||||||
|
image_urls=[],
|
||||||
|
description="Test description",
|
||||||
|
categories=[],
|
||||||
|
)
|
||||||
|
|
||||||
|
store_listing = await server.agent_server.test_create_store_listing(
|
||||||
|
store_submission_request, test_user.id
|
||||||
|
)
|
||||||
|
|
||||||
|
if isinstance(store_listing, fastapi.responses.JSONResponse):
|
||||||
|
assert False, "Failed to create store listing"
|
||||||
|
|
||||||
|
slv_id = (
|
||||||
|
store_listing.store_listing_version_id
|
||||||
|
if store_listing.store_listing_version_id is not None
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|
||||||
|
assert slv_id is not None
|
||||||
|
|
||||||
|
admin = autogpt_libs.auth.models.User(
|
||||||
|
user_id="3e53486c-cf57-477e-ba2a-cb02dc828e1b",
|
||||||
|
role="admin",
|
||||||
|
email="admin@example.com",
|
||||||
|
phone_number="1234567890",
|
||||||
|
)
|
||||||
|
await server.agent_server.test_review_store_listing(
|
||||||
|
backend.server.v2.store.model.ReviewSubmissionRequest(
|
||||||
|
store_listing_version_id=slv_id,
|
||||||
|
isApproved=True,
|
||||||
|
comments="Test comments",
|
||||||
|
),
|
||||||
|
admin,
|
||||||
|
)
|
||||||
|
|
||||||
|
alt_test_user = await create_test_user(alt_user=True)
|
||||||
|
|
||||||
|
data = {"input_1": "Hello", "input_2": "World"}
|
||||||
|
graph_exec_id = await execute_graph(
|
||||||
|
server.agent_server,
|
||||||
|
test_graph,
|
||||||
|
alt_test_user,
|
||||||
|
data,
|
||||||
|
4,
|
||||||
|
)
|
||||||
|
|
||||||
|
await assert_sample_graph_executions(
|
||||||
|
server.agent_server, test_graph, alt_test_user, graph_exec_id
|
||||||
|
)
|
||||||
|
logger.info("Completed test_agent_execution")
|
||||||
|
|
|
@ -140,10 +140,10 @@ async def main():
|
||||||
print(f"Inserting {NUM_USERS * MAX_AGENTS_PER_USER} user agents")
|
print(f"Inserting {NUM_USERS * MAX_AGENTS_PER_USER} user agents")
|
||||||
for user in users:
|
for user in users:
|
||||||
num_agents = random.randint(MIN_AGENTS_PER_USER, MAX_AGENTS_PER_USER)
|
num_agents = random.randint(MIN_AGENTS_PER_USER, MAX_AGENTS_PER_USER)
|
||||||
for _ in range(num_agents): # Create 1 UserAgent per user
|
for _ in range(num_agents): # Create 1 LibraryAgent per user
|
||||||
graph = random.choice(agent_graphs)
|
graph = random.choice(agent_graphs)
|
||||||
preset = random.choice(agent_presets)
|
preset = random.choice(agent_presets)
|
||||||
user_agent = await db.useragent.create(
|
user_agent = await db.libraryagent.create(
|
||||||
data={
|
data={
|
||||||
"userId": user.id,
|
"userId": user.id,
|
||||||
"agentId": graph.id,
|
"agentId": graph.id,
|
||||||
|
|
|
@ -49,7 +49,7 @@ export default async function RootLayout({
|
||||||
links={[
|
links={[
|
||||||
{
|
{
|
||||||
name: "Marketplace",
|
name: "Marketplace",
|
||||||
href: "/store",
|
href: "/marketplace",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Library",
|
name: "Library",
|
||||||
|
@ -66,7 +66,7 @@ export default async function RootLayout({
|
||||||
{
|
{
|
||||||
icon: IconType.Edit,
|
icon: IconType.Edit,
|
||||||
text: "Edit profile",
|
text: "Edit profile",
|
||||||
href: "/store/profile",
|
href: "/marketplace/profile",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -75,7 +75,7 @@ export default async function RootLayout({
|
||||||
{
|
{
|
||||||
icon: IconType.LayoutDashboard,
|
icon: IconType.LayoutDashboard,
|
||||||
text: "Creator Dashboard",
|
text: "Creator Dashboard",
|
||||||
href: "/store/dashboard",
|
href: "/marketplace/dashboard",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: IconType.UploadCloud,
|
icon: IconType.UploadCloud,
|
||||||
|
@ -88,7 +88,7 @@ export default async function RootLayout({
|
||||||
{
|
{
|
||||||
icon: IconType.Settings,
|
icon: IconType.Settings,
|
||||||
text: "Settings",
|
text: "Settings",
|
||||||
href: "/store/settings",
|
href: "/marketplace/settings",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -5,12 +5,12 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||||
const sidebarLinkGroups = [
|
const sidebarLinkGroups = [
|
||||||
{
|
{
|
||||||
links: [
|
links: [
|
||||||
{ text: "Creator Dashboard", href: "/store/dashboard" },
|
{ text: "Creator Dashboard", href: "/marketplace/dashboard" },
|
||||||
{ text: "Agent dashboard", href: "/store/agent-dashboard" },
|
{ text: "Agent dashboard", href: "/marketplace/agent-dashboard" },
|
||||||
{ text: "Integrations", href: "/store/integrations" },
|
{ text: "Integrations", href: "/marketplace/integrations" },
|
||||||
{ text: "API Keys", href: "/store/api_keys" },
|
{ text: "API Keys", href: "/marketplace/api_keys" },
|
||||||
{ text: "Profile", href: "/store/profile" },
|
{ text: "Profile", href: "/marketplace/profile" },
|
||||||
{ text: "Settings", href: "/store/settings" },
|
{ text: "Settings", href: "/marketplace/settings" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
|
@ -45,10 +45,10 @@ export default async function Page({
|
||||||
});
|
});
|
||||||
|
|
||||||
const breadcrumbs = [
|
const breadcrumbs = [
|
||||||
{ name: "Store", link: "/store" },
|
{ name: "Store", link: "/marketplace" },
|
||||||
{
|
{
|
||||||
name: agent.creator,
|
name: agent.creator,
|
||||||
link: `/store/creator/${encodeURIComponent(agent.creator)}`,
|
link: `/marketplace/creator/${encodeURIComponent(agent.creator)}`,
|
||||||
},
|
},
|
||||||
{ name: agent.agent_name, link: "#" },
|
{ name: agent.agent_name, link: "#" },
|
||||||
];
|
];
|
|
@ -47,7 +47,7 @@ export default async function Page({
|
||||||
<main className="mt-5 px-4">
|
<main className="mt-5 px-4">
|
||||||
<BreadCrumbs
|
<BreadCrumbs
|
||||||
items={[
|
items={[
|
||||||
{ name: "Store", link: "/store" },
|
{ name: "Store", link: "/marketplace" },
|
||||||
{ name: creator.name, link: "#" },
|
{ name: creator.name, link: "#" },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
|
@ -1,7 +1,179 @@
|
||||||
"use client";
|
import * as React from "react";
|
||||||
|
import { HeroSection } from "@/components/agptui/composite/HeroSection";
|
||||||
|
import {
|
||||||
|
FeaturedSection,
|
||||||
|
FeaturedAgent,
|
||||||
|
} from "@/components/agptui/composite/FeaturedSection";
|
||||||
|
import {
|
||||||
|
AgentsSection,
|
||||||
|
Agent,
|
||||||
|
} from "@/components/agptui/composite/AgentsSection";
|
||||||
|
import { BecomeACreator } from "@/components/agptui/BecomeACreator";
|
||||||
|
import {
|
||||||
|
FeaturedCreators,
|
||||||
|
FeaturedCreator,
|
||||||
|
} from "@/components/agptui/composite/FeaturedCreators";
|
||||||
|
import { Separator } from "@/components/ui/separator";
|
||||||
|
import { Metadata } from "next";
|
||||||
|
import {
|
||||||
|
StoreAgentsResponse,
|
||||||
|
CreatorsResponse,
|
||||||
|
} from "@/lib/autogpt-server-api/types";
|
||||||
|
import BackendAPI from "@/lib/autogpt-server-api";
|
||||||
|
|
||||||
import { redirect } from "next/navigation";
|
async function getStoreData() {
|
||||||
|
try {
|
||||||
|
const api = new BackendAPI();
|
||||||
|
|
||||||
export default function Page() {
|
// Add error handling and default values
|
||||||
redirect("/store");
|
let featuredAgents: StoreAgentsResponse = {
|
||||||
|
agents: [],
|
||||||
|
pagination: {
|
||||||
|
total_items: 0,
|
||||||
|
total_pages: 0,
|
||||||
|
current_page: 0,
|
||||||
|
page_size: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let topAgents: StoreAgentsResponse = {
|
||||||
|
agents: [],
|
||||||
|
pagination: {
|
||||||
|
total_items: 0,
|
||||||
|
total_pages: 0,
|
||||||
|
current_page: 0,
|
||||||
|
page_size: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let featuredCreators: CreatorsResponse = {
|
||||||
|
creators: [],
|
||||||
|
pagination: {
|
||||||
|
total_items: 0,
|
||||||
|
total_pages: 0,
|
||||||
|
current_page: 0,
|
||||||
|
page_size: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
[featuredAgents, topAgents, featuredCreators] = await Promise.all([
|
||||||
|
api.getStoreAgents({ featured: true }),
|
||||||
|
api.getStoreAgents({ sorted_by: "runs" }),
|
||||||
|
api.getStoreCreators({ featured: true, sorted_by: "num_agents" }),
|
||||||
|
]);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching store data:", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
featuredAgents,
|
||||||
|
topAgents,
|
||||||
|
featuredCreators,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error in getStoreData:", error);
|
||||||
|
return {
|
||||||
|
featuredAgents: {
|
||||||
|
agents: [],
|
||||||
|
pagination: {
|
||||||
|
total_items: 0,
|
||||||
|
total_pages: 0,
|
||||||
|
current_page: 0,
|
||||||
|
page_size: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
topAgents: {
|
||||||
|
agents: [],
|
||||||
|
pagination: {
|
||||||
|
total_items: 0,
|
||||||
|
total_pages: 0,
|
||||||
|
current_page: 0,
|
||||||
|
page_size: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
featuredCreators: {
|
||||||
|
creators: [],
|
||||||
|
pagination: {
|
||||||
|
total_items: 0,
|
||||||
|
total_pages: 0,
|
||||||
|
current_page: 0,
|
||||||
|
page_size: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIX: Correct metadata
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Marketplace - NextGen AutoGPT",
|
||||||
|
description: "Find and use AI Agents created by our community",
|
||||||
|
applicationName: "NextGen AutoGPT Store",
|
||||||
|
authors: [{ name: "AutoGPT Team" }],
|
||||||
|
keywords: [
|
||||||
|
"AI agents",
|
||||||
|
"automation",
|
||||||
|
"artificial intelligence",
|
||||||
|
"AutoGPT",
|
||||||
|
"marketplace",
|
||||||
|
],
|
||||||
|
robots: {
|
||||||
|
index: true,
|
||||||
|
follow: true,
|
||||||
|
},
|
||||||
|
openGraph: {
|
||||||
|
title: "Marketplace - NextGen AutoGPT",
|
||||||
|
description: "Find and use AI Agents created by our community",
|
||||||
|
type: "website",
|
||||||
|
siteName: "NextGen AutoGPT Store",
|
||||||
|
images: [
|
||||||
|
{
|
||||||
|
url: "/images/store-og.png",
|
||||||
|
width: 1200,
|
||||||
|
height: 630,
|
||||||
|
alt: "NextGen AutoGPT Store",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
twitter: {
|
||||||
|
card: "summary_large_image",
|
||||||
|
title: "Marketplace - NextGen AutoGPT",
|
||||||
|
description: "Find and use AI Agents created by our community",
|
||||||
|
images: ["/images/store-twitter.png"],
|
||||||
|
},
|
||||||
|
icons: {
|
||||||
|
icon: "/favicon.ico",
|
||||||
|
shortcut: "/favicon-16x16.png",
|
||||||
|
apple: "/apple-touch-icon.png",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function Page({}: {}) {
|
||||||
|
// Get data server-side
|
||||||
|
const { featuredAgents, topAgents, featuredCreators } = await getStoreData();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mx-auto w-screen max-w-[1360px]">
|
||||||
|
<main className="px-4">
|
||||||
|
<HeroSection />
|
||||||
|
<FeaturedSection
|
||||||
|
featuredAgents={featuredAgents.agents as FeaturedAgent[]}
|
||||||
|
/>
|
||||||
|
<Separator />
|
||||||
|
<AgentsSection
|
||||||
|
sectionTitle="Top Agents"
|
||||||
|
agents={topAgents.agents as Agent[]}
|
||||||
|
/>
|
||||||
|
<Separator />
|
||||||
|
<FeaturedCreators
|
||||||
|
featuredCreators={featuredCreators.creators as FeaturedCreator[]}
|
||||||
|
/>
|
||||||
|
<Separator />
|
||||||
|
<BecomeACreator
|
||||||
|
title="Become a Creator"
|
||||||
|
description="Join our ever-growing community of hackers and tinkerers"
|
||||||
|
buttonText="Become a Creator"
|
||||||
|
/>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,5 +3,5 @@
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
redirect("/store");
|
redirect("/marketplace");
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ export async function signup(values: z.infer<typeof signupFormSchema>) {
|
||||||
}
|
}
|
||||||
console.log("Signed up");
|
console.log("Signed up");
|
||||||
revalidatePath("/", "layout");
|
revalidatePath("/", "layout");
|
||||||
redirect("/store/profile");
|
redirect("/marketplace/profile");
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,181 +0,0 @@
|
||||||
import * as React from "react";
|
|
||||||
import { HeroSection } from "@/components/agptui/composite/HeroSection";
|
|
||||||
import {
|
|
||||||
FeaturedSection,
|
|
||||||
FeaturedAgent,
|
|
||||||
} from "@/components/agptui/composite/FeaturedSection";
|
|
||||||
import {
|
|
||||||
AgentsSection,
|
|
||||||
Agent,
|
|
||||||
} from "@/components/agptui/composite/AgentsSection";
|
|
||||||
import { BecomeACreator } from "@/components/agptui/BecomeACreator";
|
|
||||||
import {
|
|
||||||
FeaturedCreators,
|
|
||||||
FeaturedCreator,
|
|
||||||
} from "@/components/agptui/composite/FeaturedCreators";
|
|
||||||
import { Separator } from "@/components/ui/separator";
|
|
||||||
import { Metadata } from "next";
|
|
||||||
import {
|
|
||||||
StoreAgentsResponse,
|
|
||||||
CreatorsResponse,
|
|
||||||
} from "@/lib/autogpt-server-api/types";
|
|
||||||
import BackendAPI from "@/lib/autogpt-server-api";
|
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
|
||||||
|
|
||||||
async function getStoreData() {
|
|
||||||
try {
|
|
||||||
const api = new BackendAPI();
|
|
||||||
|
|
||||||
// Add error handling and default values
|
|
||||||
let featuredAgents: StoreAgentsResponse = {
|
|
||||||
agents: [],
|
|
||||||
pagination: {
|
|
||||||
total_items: 0,
|
|
||||||
total_pages: 0,
|
|
||||||
current_page: 0,
|
|
||||||
page_size: 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
let topAgents: StoreAgentsResponse = {
|
|
||||||
agents: [],
|
|
||||||
pagination: {
|
|
||||||
total_items: 0,
|
|
||||||
total_pages: 0,
|
|
||||||
current_page: 0,
|
|
||||||
page_size: 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
let featuredCreators: CreatorsResponse = {
|
|
||||||
creators: [],
|
|
||||||
pagination: {
|
|
||||||
total_items: 0,
|
|
||||||
total_pages: 0,
|
|
||||||
current_page: 0,
|
|
||||||
page_size: 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
[featuredAgents, topAgents, featuredCreators] = await Promise.all([
|
|
||||||
api.getStoreAgents({ featured: true }),
|
|
||||||
api.getStoreAgents({ sorted_by: "runs" }),
|
|
||||||
api.getStoreCreators({ featured: true, sorted_by: "num_agents" }),
|
|
||||||
]);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching store data:", error);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
featuredAgents,
|
|
||||||
topAgents,
|
|
||||||
featuredCreators,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error in getStoreData:", error);
|
|
||||||
return {
|
|
||||||
featuredAgents: {
|
|
||||||
agents: [],
|
|
||||||
pagination: {
|
|
||||||
total_items: 0,
|
|
||||||
total_pages: 0,
|
|
||||||
current_page: 0,
|
|
||||||
page_size: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
topAgents: {
|
|
||||||
agents: [],
|
|
||||||
pagination: {
|
|
||||||
total_items: 0,
|
|
||||||
total_pages: 0,
|
|
||||||
current_page: 0,
|
|
||||||
page_size: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
featuredCreators: {
|
|
||||||
creators: [],
|
|
||||||
pagination: {
|
|
||||||
total_items: 0,
|
|
||||||
total_pages: 0,
|
|
||||||
current_page: 0,
|
|
||||||
page_size: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIX: Correct metadata
|
|
||||||
export const metadata: Metadata = {
|
|
||||||
title: "Marketplace - NextGen AutoGPT",
|
|
||||||
description: "Find and use AI Agents created by our community",
|
|
||||||
applicationName: "NextGen AutoGPT Store",
|
|
||||||
authors: [{ name: "AutoGPT Team" }],
|
|
||||||
keywords: [
|
|
||||||
"AI agents",
|
|
||||||
"automation",
|
|
||||||
"artificial intelligence",
|
|
||||||
"AutoGPT",
|
|
||||||
"marketplace",
|
|
||||||
],
|
|
||||||
robots: {
|
|
||||||
index: true,
|
|
||||||
follow: true,
|
|
||||||
},
|
|
||||||
openGraph: {
|
|
||||||
title: "Marketplace - NextGen AutoGPT",
|
|
||||||
description: "Find and use AI Agents created by our community",
|
|
||||||
type: "website",
|
|
||||||
siteName: "NextGen AutoGPT Store",
|
|
||||||
images: [
|
|
||||||
{
|
|
||||||
url: "/images/store-og.png",
|
|
||||||
width: 1200,
|
|
||||||
height: 630,
|
|
||||||
alt: "NextGen AutoGPT Store",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
twitter: {
|
|
||||||
card: "summary_large_image",
|
|
||||||
title: "Marketplace - NextGen AutoGPT",
|
|
||||||
description: "Find and use AI Agents created by our community",
|
|
||||||
images: ["/images/store-twitter.png"],
|
|
||||||
},
|
|
||||||
icons: {
|
|
||||||
icon: "/favicon.ico",
|
|
||||||
shortcut: "/favicon-16x16.png",
|
|
||||||
apple: "/apple-touch-icon.png",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default async function Page({}: {}) {
|
|
||||||
// Get data server-side
|
|
||||||
const { featuredAgents, topAgents, featuredCreators } = await getStoreData();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="mx-auto w-screen max-w-[1360px]">
|
|
||||||
<main className="px-4">
|
|
||||||
<HeroSection />
|
|
||||||
<FeaturedSection
|
|
||||||
featuredAgents={featuredAgents.agents as FeaturedAgent[]}
|
|
||||||
/>
|
|
||||||
<Separator />
|
|
||||||
<AgentsSection
|
|
||||||
sectionTitle="Top Agents"
|
|
||||||
agents={topAgents.agents as Agent[]}
|
|
||||||
/>
|
|
||||||
<Separator />
|
|
||||||
<FeaturedCreators
|
|
||||||
featuredCreators={featuredCreators.creators as FeaturedCreator[]}
|
|
||||||
/>
|
|
||||||
<Separator />
|
|
||||||
<BecomeACreator
|
|
||||||
title="Become a Creator"
|
|
||||||
description="Join our ever-growing community of hackers and tinkerers"
|
|
||||||
buttonText="Become a Creator"
|
|
||||||
/>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -105,7 +105,7 @@ export const AgentInfo: React.FC<AgentInfoProps> = ({
|
||||||
by
|
by
|
||||||
</div>
|
</div>
|
||||||
<Link
|
<Link
|
||||||
href={`/store/creator/${encodeURIComponent(creator)}`}
|
href={`/marketplace/creator/${encodeURIComponent(creator)}`}
|
||||||
className="font-geist text-base font-medium text-neutral-800 hover:underline dark:text-neutral-200 sm:text-lg lg:text-xl"
|
className="font-geist text-base font-medium text-neutral-800 hover:underline dark:text-neutral-200 sm:text-lg lg:text-xl"
|
||||||
>
|
>
|
||||||
{creator}
|
{creator}
|
||||||
|
|
|
@ -28,7 +28,7 @@ export const NavbarLink = ({ name, href }: NavbarLinkProps) => {
|
||||||
: ""
|
: ""
|
||||||
} flex items-center justify-start gap-3`}
|
} flex items-center justify-start gap-3`}
|
||||||
>
|
>
|
||||||
{href === "/store" && (
|
{href === "/marketplace" && (
|
||||||
<IconShoppingCart
|
<IconShoppingCart
|
||||||
className={`h-6 w-6 ${activeLink === href ? "text-white dark:text-black" : ""}`}
|
className={`h-6 w-6 ${activeLink === href ? "text-white dark:text-black" : ""}`}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -36,7 +36,7 @@ export const SearchBar: React.FC<SearchBarProps> = ({
|
||||||
if (searchQuery.trim()) {
|
if (searchQuery.trim()) {
|
||||||
// Encode the search term and navigate to the desired path
|
// Encode the search term and navigate to the desired path
|
||||||
const encodedTerm = encodeURIComponent(searchQuery);
|
const encodedTerm = encodeURIComponent(searchQuery);
|
||||||
router.push(`/store/search?searchTerm=${encodedTerm}`);
|
router.push(`/marketplace/search?searchTerm=${encodedTerm}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ export const Sidebar: React.FC<SidebarProps> = ({ linkGroups }) => {
|
||||||
<div className="h-full w-full rounded-2xl bg-zinc-200 dark:bg-zinc-800">
|
<div className="h-full w-full rounded-2xl bg-zinc-200 dark:bg-zinc-800">
|
||||||
<div className="inline-flex h-[264px] flex-col items-start justify-start gap-6 p-3">
|
<div className="inline-flex h-[264px] flex-col items-start justify-start gap-6 p-3">
|
||||||
<Link
|
<Link
|
||||||
href="/store/dashboard"
|
href="/marketplace/dashboard"
|
||||||
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
<IconDashboardLayout className="h-6 w-6" />
|
<IconDashboardLayout className="h-6 w-6" />
|
||||||
|
@ -50,7 +50,7 @@ export const Sidebar: React.FC<SidebarProps> = ({ linkGroups }) => {
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href="/store/integrations"
|
href="/marketplace/integrations"
|
||||||
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
<IconIntegrations className="h-6 w-6" />
|
<IconIntegrations className="h-6 w-6" />
|
||||||
|
@ -59,7 +59,7 @@ export const Sidebar: React.FC<SidebarProps> = ({ linkGroups }) => {
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href="/store/api_keys"
|
href="/marketplace/api_keys"
|
||||||
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
<KeyIcon className="h-6 w-6" />
|
<KeyIcon className="h-6 w-6" />
|
||||||
|
@ -68,7 +68,7 @@ export const Sidebar: React.FC<SidebarProps> = ({ linkGroups }) => {
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href="/store/profile"
|
href="/marketplace/profile"
|
||||||
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
<IconProfile className="h-6 w-6" />
|
<IconProfile className="h-6 w-6" />
|
||||||
|
@ -77,7 +77,7 @@ export const Sidebar: React.FC<SidebarProps> = ({ linkGroups }) => {
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href="/store/settings"
|
href="/marketplace/settings"
|
||||||
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
<IconSliders className="h-6 w-6" />
|
<IconSliders className="h-6 w-6" />
|
||||||
|
@ -94,7 +94,7 @@ export const Sidebar: React.FC<SidebarProps> = ({ linkGroups }) => {
|
||||||
<div className="h-full w-full rounded-2xl bg-zinc-200 dark:bg-zinc-800">
|
<div className="h-full w-full rounded-2xl bg-zinc-200 dark:bg-zinc-800">
|
||||||
<div className="inline-flex h-[264px] flex-col items-start justify-start gap-6 p-3">
|
<div className="inline-flex h-[264px] flex-col items-start justify-start gap-6 p-3">
|
||||||
<Link
|
<Link
|
||||||
href="/store/dashboard"
|
href="/marketplace/dashboard"
|
||||||
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
<IconDashboardLayout className="h-6 w-6" />
|
<IconDashboardLayout className="h-6 w-6" />
|
||||||
|
@ -103,7 +103,7 @@ export const Sidebar: React.FC<SidebarProps> = ({ linkGroups }) => {
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href="/store/integrations"
|
href="/marketplace/integrations"
|
||||||
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
<IconIntegrations className="h-6 w-6" />
|
<IconIntegrations className="h-6 w-6" />
|
||||||
|
@ -112,7 +112,7 @@ export const Sidebar: React.FC<SidebarProps> = ({ linkGroups }) => {
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href="/store/api_keys"
|
href="/marketplace/api_keys"
|
||||||
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
<KeyIcon className="h-6 w-6" strokeWidth={1} />
|
<KeyIcon className="h-6 w-6" strokeWidth={1} />
|
||||||
|
@ -121,7 +121,7 @@ export const Sidebar: React.FC<SidebarProps> = ({ linkGroups }) => {
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href="/store/profile"
|
href="/marketplace/profile"
|
||||||
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
<IconProfile className="h-6 w-6" />
|
<IconProfile className="h-6 w-6" />
|
||||||
|
@ -130,7 +130,7 @@ export const Sidebar: React.FC<SidebarProps> = ({ linkGroups }) => {
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href="/store/settings"
|
href="/marketplace/settings"
|
||||||
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
<IconSliders className="h-6 w-6" />
|
<IconSliders className="h-6 w-6" />
|
||||||
|
|
|
@ -39,7 +39,7 @@ export const AgentsSection: React.FC<AgentsSectionProps> = ({
|
||||||
|
|
||||||
const handleCardClick = (creator: string, slug: string) => {
|
const handleCardClick = (creator: string, slug: string) => {
|
||||||
router.push(
|
router.push(
|
||||||
`/store/agent/${encodeURIComponent(creator)}/${encodeURIComponent(slug)}`,
|
`/marketplace/agent/${encodeURIComponent(creator)}/${encodeURIComponent(slug)}`,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ export const FeaturedCreators: React.FC<FeaturedCreatorsProps> = ({
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const handleCardClick = (creator: string) => {
|
const handleCardClick = (creator: string) => {
|
||||||
router.push(`/store/creator/${encodeURIComponent(creator)}`);
|
router.push(`/marketplace/creator/${encodeURIComponent(creator)}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Only show first 4 creators
|
// Only show first 4 creators
|
||||||
|
|
|
@ -43,7 +43,7 @@ export const FeaturedSection: React.FC<FeaturedSectionProps> = ({
|
||||||
|
|
||||||
const handleCardClick = (creator: string, slug: string) => {
|
const handleCardClick = (creator: string, slug: string) => {
|
||||||
router.push(
|
router.push(
|
||||||
`/store/agent/${encodeURIComponent(creator)}/${encodeURIComponent(slug)}`,
|
`/marketplace/agent/${encodeURIComponent(creator)}/${encodeURIComponent(slug)}`,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ export const HeroSection: React.FC = () => {
|
||||||
|
|
||||||
function onFilterChange(selectedFilters: string[]) {
|
function onFilterChange(selectedFilters: string[]) {
|
||||||
const encodedTerm = encodeURIComponent(selectedFilters.join(", "));
|
const encodedTerm = encodeURIComponent(selectedFilters.join(", "));
|
||||||
router.push(`/store/search?searchTerm=${encodedTerm}`);
|
router.push(`/marketplace/search?searchTerm=${encodedTerm}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -260,7 +260,7 @@ export const PublishAgentPopout: React.FC<PublishAgentPopoutProps> = ({
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
onDone={handleClose}
|
onDone={handleClose}
|
||||||
onViewProgress={() => {
|
onViewProgress={() => {
|
||||||
router.push("/store/dashboard");
|
router.push("/marketplace/dashboard");
|
||||||
handleClose();
|
handleClose();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -24,7 +24,7 @@ export function NavBarButtons({ className }: { className?: string }) {
|
||||||
icon: <BsBoxes />,
|
icon: <BsBoxes />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: "/store",
|
href: "/marketplace",
|
||||||
text: "Marketplace",
|
text: "Marketplace",
|
||||||
icon: <IconMarketplace />,
|
icon: <IconMarketplace />,
|
||||||
},
|
},
|
||||||
|
|
|
@ -5,9 +5,9 @@ import { NextResponse, type NextRequest } from "next/server";
|
||||||
const PROTECTED_PAGES = [
|
const PROTECTED_PAGES = [
|
||||||
"/monitor",
|
"/monitor",
|
||||||
"/build",
|
"/build",
|
||||||
"/store/profile",
|
"/marketplace/profile",
|
||||||
"/store/settings",
|
"/marketplace/settings",
|
||||||
"/store/dashboard",
|
"/marketplace/dashboard",
|
||||||
];
|
];
|
||||||
const ADMIN_PAGES = ["/admin"];
|
const ADMIN_PAGES = ["/admin"];
|
||||||
|
|
||||||
|
@ -87,7 +87,7 @@ export async function updateSession(request: NextRequest) {
|
||||||
ADMIN_PAGES.some((page) => request.nextUrl.pathname.startsWith(`${page}`))
|
ADMIN_PAGES.some((page) => request.nextUrl.pathname.startsWith(`${page}`))
|
||||||
) {
|
) {
|
||||||
// no user, potentially respond by redirecting the user to the login page
|
// no user, potentially respond by redirecting the user to the login page
|
||||||
url.pathname = `/store`;
|
url.pathname = `/marketplace`;
|
||||||
return NextResponse.redirect(url);
|
return NextResponse.redirect(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ test.describe("Authentication", () => {
|
||||||
test("user can login successfully", async ({ page, loginPage, testUser }) => {
|
test("user can login successfully", async ({ page, loginPage, testUser }) => {
|
||||||
await page.goto("/login");
|
await page.goto("/login");
|
||||||
await loginPage.login(testUser.email, testUser.password);
|
await loginPage.login(testUser.email, testUser.password);
|
||||||
await test.expect(page).toHaveURL("/store");
|
await test.expect(page).toHaveURL("/marketplace");
|
||||||
await test
|
await test
|
||||||
.expect(page.getByTestId("profile-popout-menu-trigger"))
|
.expect(page.getByTestId("profile-popout-menu-trigger"))
|
||||||
.toBeVisible();
|
.toBeVisible();
|
||||||
|
@ -19,7 +19,7 @@ test.describe("Authentication", () => {
|
||||||
await page.goto("/login");
|
await page.goto("/login");
|
||||||
await loginPage.login(testUser.email, testUser.password);
|
await loginPage.login(testUser.email, testUser.password);
|
||||||
|
|
||||||
await test.expect(page).toHaveURL("/store");
|
await test.expect(page).toHaveURL("/marketplace");
|
||||||
|
|
||||||
// Click on the profile menu trigger to open popout
|
// Click on the profile menu trigger to open popout
|
||||||
await page.getByTestId("profile-popout-menu-trigger").click();
|
await page.getByTestId("profile-popout-menu-trigger").click();
|
||||||
|
@ -43,7 +43,7 @@ test.describe("Authentication", () => {
|
||||||
}) => {
|
}) => {
|
||||||
await page.goto("/login");
|
await page.goto("/login");
|
||||||
await loginPage.login(testUser.email, testUser.password);
|
await loginPage.login(testUser.email, testUser.password);
|
||||||
await test.expect(page).toHaveURL("/store");
|
await test.expect(page).toHaveURL("/marketplace");
|
||||||
// Click on the profile menu trigger to open popout
|
// Click on the profile menu trigger to open popout
|
||||||
await page.getByTestId("profile-popout-menu-trigger").click();
|
await page.getByTestId("profile-popout-menu-trigger").click();
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ test.describe("Authentication", () => {
|
||||||
|
|
||||||
await test.expect(page).toHaveURL("/login");
|
await test.expect(page).toHaveURL("/login");
|
||||||
await loginPage.login(testUser.email, testUser.password);
|
await loginPage.login(testUser.email, testUser.password);
|
||||||
await test.expect(page).toHaveURL("/store");
|
await test.expect(page).toHaveURL("/marketplace");
|
||||||
await test
|
await test
|
||||||
.expect(page.getByTestId("profile-popout-menu-trigger"))
|
.expect(page.getByTestId("profile-popout-menu-trigger"))
|
||||||
.toBeVisible();
|
.toBeVisible();
|
||||||
|
|
|
@ -10,7 +10,7 @@ test.describe("Profile", () => {
|
||||||
// Start each test with login using worker auth
|
// Start each test with login using worker auth
|
||||||
await page.goto("/login");
|
await page.goto("/login");
|
||||||
await loginPage.login(testUser.email, testUser.password);
|
await loginPage.login(testUser.email, testUser.password);
|
||||||
await test.expect(page).toHaveURL("/store");
|
await test.expect(page).toHaveURL("/marketplace");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("user can view their profile information", async ({
|
test("user can view their profile information", async ({
|
||||||
|
|
Loading…
Reference in New Issue