mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-01-09 15:17:59 -05:00
feat(platform/library): Library v2 > Agent Runs page (#9051)
- Resolves #8780 - Part of #8774 ### Changes 🏗️ - Add new UI components - Add `/agents/[id]` page, with sub-components: - `AgentRunsSelectorList` - `AgentRunSummaryCard` - `AgentRunStatusChip` - `AgentRunDetailsView` - `AgentRunDraftView` - `AgentScheduleDetailsView` Backend improvements: - Improve output of execution-related API endpoints: return `GraphExecution` instead of `NodeExecutionResult[]` - Reduce log spam from Prisma in tests General frontend improvements: - Hide nav link names on smaller screens to prevent navbar overflow - Clean up styling and fix sizing of `agptui/Button` Technical frontend improvements: - Fix tailwind config size increments - Rename `font-poppin` -> `font-poppins` - Clean up component implementations and usages - Yeet all occurrences of `variant="default"` - Remove `default` button variant as duplicate of `outline`; make `outline` the default - Fix minor typing issues DX: - Add front end type-check step to `pre-commit` config - Fix logging setup in conftest.py ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - `/agents/[id]` (new) - Go to page -> list of runs loads - Create new run -> runs; all I/O is visible - Click "Run again" -> runs again with same input - `/monitoring` (existing) - Go to page -> everything loads - Selecting agents and agent runs works --------- Co-authored-by: Nicholas Tindle <nicktindle@outlook.com> Co-authored-by: Nicholas Tindle <nicholas.tindle@agpt.co> Co-authored-by: Swifty <craigswift13@gmail.com> Co-authored-by: Zamil Majdy <zamil.majdy@agpt.co>
This commit is contained in:
committed by
GitHub
parent
f722c70c50
commit
296eee0b4f
@@ -170,6 +170,16 @@ repos:
|
|||||||
files: ^classic/benchmark/(agbenchmark|tests)/((?!reports).)*[/.]
|
files: ^classic/benchmark/(agbenchmark|tests)/((?!reports).)*[/.]
|
||||||
args: [--config=classic/benchmark/.flake8]
|
args: [--config=classic/benchmark/.flake8]
|
||||||
|
|
||||||
|
- repo: local
|
||||||
|
hooks:
|
||||||
|
- id: prettier
|
||||||
|
name: Format (Prettier) - AutoGPT Platform - Frontend
|
||||||
|
alias: format-platform-frontend
|
||||||
|
entry: bash -c 'cd autogpt_platform/frontend && npx prettier --write $(echo "$@" | sed "s|autogpt_platform/frontend/||g")' --
|
||||||
|
files: ^autogpt_platform/frontend/
|
||||||
|
types: [file]
|
||||||
|
language: system
|
||||||
|
|
||||||
- repo: local
|
- repo: local
|
||||||
# To have watertight type checking, we check *all* the files in an affected
|
# To have watertight type checking, we check *all* the files in an affected
|
||||||
# project. To trigger on poetry.lock we also reset the file `types` filter.
|
# project. To trigger on poetry.lock we also reset the file `types` filter.
|
||||||
@@ -221,6 +231,16 @@ repos:
|
|||||||
language: system
|
language: system
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
|
|
||||||
|
- repo: local
|
||||||
|
hooks:
|
||||||
|
- id: tsc
|
||||||
|
name: Typecheck - AutoGPT Platform - Frontend
|
||||||
|
entry: bash -c 'cd autogpt_platform/frontend && npm run type-check'
|
||||||
|
files: ^autogpt_platform/frontend/
|
||||||
|
types: [file]
|
||||||
|
language: system
|
||||||
|
pass_filenames: false
|
||||||
|
|
||||||
- repo: local
|
- repo: local
|
||||||
hooks:
|
hooks:
|
||||||
- id: pytest
|
- id: pytest
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ from typing_extensions import ParamSpec
|
|||||||
from .config import SETTINGS
|
from .config import SETTINGS
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
|
||||||
|
|
||||||
P = ParamSpec("P")
|
P = ParamSpec("P")
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
|
|||||||
@@ -23,12 +23,15 @@ from backend.util import type
|
|||||||
|
|
||||||
from .block import BlockInput, BlockType, get_block, get_blocks
|
from .block import BlockInput, BlockType, get_block, get_blocks
|
||||||
from .db import BaseDbModel, transaction
|
from .db import BaseDbModel, transaction
|
||||||
from .execution import ExecutionStatus
|
from .execution import ExecutionResult, ExecutionStatus
|
||||||
from .includes import AGENT_GRAPH_INCLUDE, AGENT_NODE_INCLUDE
|
from .includes import AGENT_GRAPH_INCLUDE, AGENT_NODE_INCLUDE
|
||||||
from .integrations import Webhook
|
from .integrations import Webhook
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
_INPUT_BLOCK_ID = AgentInputBlock().id
|
||||||
|
_OUTPUT_BLOCK_ID = AgentOutputBlock().id
|
||||||
|
|
||||||
|
|
||||||
class Link(BaseDbModel):
|
class Link(BaseDbModel):
|
||||||
source_id: str
|
source_id: str
|
||||||
@@ -105,7 +108,7 @@ class NodeModel(Node):
|
|||||||
Webhook.model_rebuild()
|
Webhook.model_rebuild()
|
||||||
|
|
||||||
|
|
||||||
class GraphExecution(BaseDbModel):
|
class GraphExecutionMeta(BaseDbModel):
|
||||||
execution_id: str
|
execution_id: str
|
||||||
started_at: datetime
|
started_at: datetime
|
||||||
ended_at: datetime
|
ended_at: datetime
|
||||||
@@ -114,33 +117,83 @@ class GraphExecution(BaseDbModel):
|
|||||||
status: ExecutionStatus
|
status: ExecutionStatus
|
||||||
graph_id: str
|
graph_id: str
|
||||||
graph_version: int
|
graph_version: int
|
||||||
|
preset_id: Optional[str]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_db(execution: AgentGraphExecution):
|
def from_db(_graph_exec: AgentGraphExecution):
|
||||||
now = datetime.now(timezone.utc)
|
now = datetime.now(timezone.utc)
|
||||||
start_time = execution.startedAt or execution.createdAt
|
start_time = _graph_exec.startedAt or _graph_exec.createdAt
|
||||||
end_time = execution.updatedAt or now
|
end_time = _graph_exec.updatedAt or now
|
||||||
duration = (end_time - start_time).total_seconds()
|
duration = (end_time - start_time).total_seconds()
|
||||||
total_run_time = duration
|
total_run_time = duration
|
||||||
|
|
||||||
try:
|
try:
|
||||||
stats = type.convert(execution.stats or {}, dict[str, Any])
|
stats = type.convert(_graph_exec.stats or {}, dict[str, Any])
|
||||||
except ValueError:
|
except ValueError:
|
||||||
stats = {}
|
stats = {}
|
||||||
|
|
||||||
duration = stats.get("walltime", duration)
|
duration = stats.get("walltime", duration)
|
||||||
total_run_time = stats.get("nodes_walltime", total_run_time)
|
total_run_time = stats.get("nodes_walltime", total_run_time)
|
||||||
|
|
||||||
return GraphExecution(
|
return GraphExecutionMeta(
|
||||||
id=execution.id,
|
id=_graph_exec.id,
|
||||||
execution_id=execution.id,
|
execution_id=_graph_exec.id,
|
||||||
started_at=start_time,
|
started_at=start_time,
|
||||||
ended_at=end_time,
|
ended_at=end_time,
|
||||||
duration=duration,
|
duration=duration,
|
||||||
total_run_time=total_run_time,
|
total_run_time=total_run_time,
|
||||||
status=ExecutionStatus(execution.executionStatus),
|
status=ExecutionStatus(_graph_exec.executionStatus),
|
||||||
graph_id=execution.agentGraphId,
|
graph_id=_graph_exec.agentGraphId,
|
||||||
graph_version=execution.agentGraphVersion,
|
graph_version=_graph_exec.agentGraphVersion,
|
||||||
|
preset_id=_graph_exec.agentPresetId,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class GraphExecution(GraphExecutionMeta):
|
||||||
|
inputs: dict[str, Any]
|
||||||
|
outputs: dict[str, list[Any]]
|
||||||
|
node_executions: list[ExecutionResult]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_db(_graph_exec: AgentGraphExecution):
|
||||||
|
if _graph_exec.AgentNodeExecutions is None:
|
||||||
|
raise ValueError("Node executions must be included in query")
|
||||||
|
|
||||||
|
graph_exec = GraphExecutionMeta.from_db(_graph_exec)
|
||||||
|
|
||||||
|
node_executions = [
|
||||||
|
ExecutionResult.from_db(ne) for ne in _graph_exec.AgentNodeExecutions
|
||||||
|
]
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
**{
|
||||||
|
# inputs from Agent Input Blocks
|
||||||
|
exec.input_data["name"]: exec.input_data["value"]
|
||||||
|
for exec in node_executions
|
||||||
|
if exec.block_id == _INPUT_BLOCK_ID
|
||||||
|
},
|
||||||
|
**{
|
||||||
|
# input from webhook-triggered block
|
||||||
|
"payload": exec.input_data["payload"]
|
||||||
|
for exec in node_executions
|
||||||
|
if (block := get_block(exec.block_id))
|
||||||
|
and block.block_type in [BlockType.WEBHOOK, BlockType.WEBHOOK_MANUAL]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
outputs: dict[str, list] = defaultdict(list)
|
||||||
|
for exec in node_executions:
|
||||||
|
if exec.block_id == _OUTPUT_BLOCK_ID:
|
||||||
|
outputs[exec.input_data["name"]].append(exec.input_data["value"])
|
||||||
|
|
||||||
|
return GraphExecution(
|
||||||
|
**{
|
||||||
|
field_name: getattr(graph_exec, field_name)
|
||||||
|
for field_name in graph_exec.model_fields
|
||||||
|
},
|
||||||
|
inputs=inputs,
|
||||||
|
outputs=outputs,
|
||||||
|
node_executions=node_executions,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -514,17 +567,45 @@ async def get_graphs(
|
|||||||
return graph_models
|
return graph_models
|
||||||
|
|
||||||
|
|
||||||
async def get_executions(user_id: str) -> list[GraphExecution]:
|
async def get_graphs_executions(user_id: str) -> list[GraphExecutionMeta]:
|
||||||
executions = await AgentGraphExecution.prisma().find_many(
|
executions = await AgentGraphExecution.prisma().find_many(
|
||||||
where={"userId": user_id},
|
where={"userId": user_id},
|
||||||
order={"createdAt": "desc"},
|
order={"createdAt": "desc"},
|
||||||
)
|
)
|
||||||
return [GraphExecution.from_db(execution) for execution in executions]
|
return [GraphExecutionMeta.from_db(execution) for execution in executions]
|
||||||
|
|
||||||
|
|
||||||
|
async def get_graph_executions(graph_id: str, user_id: str) -> list[GraphExecutionMeta]:
|
||||||
|
executions = await AgentGraphExecution.prisma().find_many(
|
||||||
|
where={"agentGraphId": graph_id, "userId": user_id},
|
||||||
|
order={"createdAt": "desc"},
|
||||||
|
)
|
||||||
|
return [GraphExecutionMeta.from_db(execution) for execution in executions]
|
||||||
|
|
||||||
|
|
||||||
|
async def get_execution_meta(
|
||||||
|
user_id: str, execution_id: str
|
||||||
|
) -> GraphExecutionMeta | None:
|
||||||
|
execution = await AgentGraphExecution.prisma().find_first(
|
||||||
|
where={"id": execution_id, "userId": user_id}
|
||||||
|
)
|
||||||
|
return GraphExecutionMeta.from_db(execution) if execution else None
|
||||||
|
|
||||||
|
|
||||||
async def get_execution(user_id: str, execution_id: str) -> GraphExecution | None:
|
async def get_execution(user_id: str, execution_id: str) -> GraphExecution | None:
|
||||||
execution = await AgentGraphExecution.prisma().find_first(
|
execution = await AgentGraphExecution.prisma().find_first(
|
||||||
where={"id": execution_id, "userId": user_id}
|
where={"id": execution_id, "userId": user_id},
|
||||||
|
include={
|
||||||
|
"AgentNodeExecutions": {
|
||||||
|
"include": {"AgentNode": True, "Input": True, "Output": True},
|
||||||
|
"order_by": [
|
||||||
|
{"queuedTime": "asc"},
|
||||||
|
{ # Fallback: Incomplete execs has no queuedTime.
|
||||||
|
"addedTime": "asc"
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
)
|
)
|
||||||
return GraphExecution.from_db(execution) if execution else None
|
return GraphExecution.from_db(execution) if execution else None
|
||||||
|
|
||||||
@@ -629,7 +710,7 @@ async def create_graph(graph: Graph, user_id: str) -> GraphModel:
|
|||||||
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, graph.is_template, user_id=user_id
|
graph.id, graph.version, template=graph.is_template, user_id=user_id
|
||||||
):
|
):
|
||||||
return created_graph
|
return created_graph
|
||||||
|
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ class AgentServer(backend.util.service.AppProcess):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def test_get_graph_run_status(graph_exec_id: str, user_id: str):
|
async def test_get_graph_run_status(graph_exec_id: str, user_id: str):
|
||||||
execution = await backend.data.graph.get_execution(
|
execution = await backend.data.graph.get_execution_meta(
|
||||||
user_id=user_id, execution_id=graph_exec_id
|
user_id=user_id, execution_id=graph_exec_id
|
||||||
)
|
)
|
||||||
if not execution:
|
if not execution:
|
||||||
@@ -163,10 +163,10 @@ class AgentServer(backend.util.service.AppProcess):
|
|||||||
return execution.status
|
return execution.status
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def test_get_graph_run_node_execution_results(
|
async def test_get_graph_run_results(
|
||||||
graph_id: str, graph_exec_id: str, user_id: str
|
graph_id: str, graph_exec_id: str, user_id: str
|
||||||
):
|
):
|
||||||
return await backend.server.routers.v1.get_graph_run_node_execution_results(
|
return await backend.server.routers.v1.get_graph_execution(
|
||||||
graph_id, graph_exec_id, user_id
|
graph_id, graph_exec_id, user_id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ 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 as library_db
|
import backend.server.v2.library.db as library_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 (
|
||||||
APIKeyError,
|
APIKeyError,
|
||||||
@@ -546,7 +545,7 @@ async def set_graph_active_version(
|
|||||||
)
|
)
|
||||||
def execute_graph(
|
def execute_graph(
|
||||||
graph_id: str,
|
graph_id: str,
|
||||||
node_input: Annotated[dict[str, Any], Body(..., embed=True, default_factory=dict)],
|
node_input: Annotated[dict[str, Any], Body(..., default_factory=dict)],
|
||||||
user_id: Annotated[str, Depends(get_user_id)],
|
user_id: Annotated[str, Depends(get_user_id)],
|
||||||
graph_version: Optional[int] = None,
|
graph_version: Optional[int] = None,
|
||||||
) -> ExecuteGraphResponse:
|
) -> ExecuteGraphResponse:
|
||||||
@@ -567,8 +566,10 @@ def execute_graph(
|
|||||||
)
|
)
|
||||||
async def stop_graph_run(
|
async def stop_graph_run(
|
||||||
graph_exec_id: str, user_id: Annotated[str, Depends(get_user_id)]
|
graph_exec_id: str, user_id: Annotated[str, Depends(get_user_id)]
|
||||||
) -> Sequence[execution_db.ExecutionResult]:
|
) -> graph_db.GraphExecution:
|
||||||
if not await graph_db.get_execution(user_id=user_id, execution_id=graph_exec_id):
|
if not await graph_db.get_execution_meta(
|
||||||
|
user_id=user_id, execution_id=graph_exec_id
|
||||||
|
):
|
||||||
raise HTTPException(404, detail=f"Agent execution #{graph_exec_id} not found")
|
raise HTTPException(404, detail=f"Agent execution #{graph_exec_id} not found")
|
||||||
|
|
||||||
await asyncio.to_thread(
|
await asyncio.to_thread(
|
||||||
@@ -576,7 +577,13 @@ async def stop_graph_run(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Retrieve & return canceled graph execution in its final state
|
# Retrieve & return canceled graph execution in its final state
|
||||||
return await execution_db.get_execution_results(graph_exec_id)
|
result = await graph_db.get_execution(execution_id=graph_exec_id, user_id=user_id)
|
||||||
|
if not result:
|
||||||
|
raise HTTPException(
|
||||||
|
500,
|
||||||
|
detail=f"Could not fetch graph execution #{graph_exec_id} after stopping",
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
@v1_router.get(
|
@v1_router.get(
|
||||||
@@ -584,10 +591,22 @@ async def stop_graph_run(
|
|||||||
tags=["graphs"],
|
tags=["graphs"],
|
||||||
dependencies=[Depends(auth_middleware)],
|
dependencies=[Depends(auth_middleware)],
|
||||||
)
|
)
|
||||||
async def get_executions(
|
async def get_graphs_executions(
|
||||||
user_id: Annotated[str, Depends(get_user_id)],
|
user_id: Annotated[str, Depends(get_user_id)],
|
||||||
) -> list[graph_db.GraphExecution]:
|
) -> list[graph_db.GraphExecutionMeta]:
|
||||||
return await graph_db.get_executions(user_id=user_id)
|
return await graph_db.get_graphs_executions(user_id=user_id)
|
||||||
|
|
||||||
|
|
||||||
|
@v1_router.get(
|
||||||
|
path="/graphs/{graph_id}/executions",
|
||||||
|
tags=["graphs"],
|
||||||
|
dependencies=[Depends(auth_middleware)],
|
||||||
|
)
|
||||||
|
async def get_graph_executions(
|
||||||
|
graph_id: str,
|
||||||
|
user_id: Annotated[str, Depends(get_user_id)],
|
||||||
|
) -> list[graph_db.GraphExecutionMeta]:
|
||||||
|
return await graph_db.get_graph_executions(graph_id=graph_id, user_id=user_id)
|
||||||
|
|
||||||
|
|
||||||
@v1_router.get(
|
@v1_router.get(
|
||||||
@@ -595,16 +614,20 @@ async def get_executions(
|
|||||||
tags=["graphs"],
|
tags=["graphs"],
|
||||||
dependencies=[Depends(auth_middleware)],
|
dependencies=[Depends(auth_middleware)],
|
||||||
)
|
)
|
||||||
async def get_graph_run_node_execution_results(
|
async def get_graph_execution(
|
||||||
graph_id: str,
|
graph_id: str,
|
||||||
graph_exec_id: str,
|
graph_exec_id: str,
|
||||||
user_id: Annotated[str, Depends(get_user_id)],
|
user_id: Annotated[str, Depends(get_user_id)],
|
||||||
) -> Sequence[execution_db.ExecutionResult]:
|
) -> graph_db.GraphExecution:
|
||||||
graph = await graph_db.get_graph(graph_id, user_id=user_id)
|
graph = await graph_db.get_graph(graph_id, user_id=user_id)
|
||||||
if not graph:
|
if not graph:
|
||||||
raise HTTPException(status_code=404, detail=f"Graph #{graph_id} not found.")
|
raise HTTPException(status_code=404, detail=f"Graph #{graph_id} not found.")
|
||||||
|
|
||||||
return await execution_db.get_execution_results(graph_exec_id)
|
result = await graph_db.get_execution(execution_id=graph_exec_id, user_id=user_id)
|
||||||
|
if not result:
|
||||||
|
raise HTTPException(status_code=404, detail=f"Graph #{graph_id} not found.")
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
########################################################
|
########################################################
|
||||||
|
|||||||
@@ -669,7 +669,8 @@ async def review_submission(
|
|||||||
reviewer_id=user.user_id,
|
reviewer_id=user.user_id,
|
||||||
)
|
)
|
||||||
return submission
|
return submission
|
||||||
except Exception:
|
except Exception as e:
|
||||||
|
logger.error(f"Could not create store submission review: {e}")
|
||||||
raise fastapi.HTTPException(
|
raise fastapi.HTTPException(
|
||||||
status_code=500,
|
status_code=500,
|
||||||
detail="An error occurred while creating the store submission review",
|
detail="An error occurred while creating the store submission review",
|
||||||
|
|||||||
@@ -78,9 +78,10 @@ async def wait_execution(
|
|||||||
# Wait for the executions to complete
|
# Wait for the executions to complete
|
||||||
for i in range(timeout):
|
for i in range(timeout):
|
||||||
if await is_execution_completed():
|
if await is_execution_completed():
|
||||||
return await AgentServer().test_get_graph_run_node_execution_results(
|
graph_exec = await AgentServer().test_get_graph_run_results(
|
||||||
graph_id, graph_exec_id, user_id
|
graph_id, graph_exec_id, user_id
|
||||||
)
|
)
|
||||||
|
return graph_exec.node_executions
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
assert False, "Execution did not complete in time."
|
assert False, "Execution did not complete in time."
|
||||||
|
|||||||
@@ -1,23 +1,25 @@
|
|||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from backend.util.test import SpinTestServer
|
from backend.util.logging import configure_logging
|
||||||
|
|
||||||
# NOTE: You can run tests like with the --log-cli-level=INFO to see the logs
|
# NOTE: You can run tests like with the --log-cli-level=INFO to see the logs
|
||||||
# Set up logging
|
# Set up logging
|
||||||
|
configure_logging()
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Create console handler with formatting
|
# Reduce Prisma log spam unless PRISMA_DEBUG is set
|
||||||
ch = logging.StreamHandler()
|
if not os.getenv("PRISMA_DEBUG"):
|
||||||
ch.setLevel(logging.INFO)
|
prisma_logger = logging.getLogger("prisma")
|
||||||
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
prisma_logger.setLevel(logging.INFO)
|
||||||
ch.setFormatter(formatter)
|
|
||||||
logger.addHandler(ch)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
async def server():
|
async def server():
|
||||||
|
from backend.util.test import SpinTestServer
|
||||||
|
|
||||||
async with SpinTestServer() as server:
|
async with SpinTestServer() as server:
|
||||||
yield server
|
yield server
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ async def assert_sample_graph_executions(
|
|||||||
graph_exec_id: str,
|
graph_exec_id: str,
|
||||||
):
|
):
|
||||||
logger.info(f"Checking execution results for graph {test_graph.id}")
|
logger.info(f"Checking execution results for graph {test_graph.id}")
|
||||||
executions = await agent_server.test_get_graph_run_node_execution_results(
|
graph_run = await agent_server.test_get_graph_run_results(
|
||||||
test_graph.id,
|
test_graph.id,
|
||||||
graph_exec_id,
|
graph_exec_id,
|
||||||
test_user.id,
|
test_user.id,
|
||||||
@@ -77,7 +77,7 @@ async def assert_sample_graph_executions(
|
|||||||
]
|
]
|
||||||
|
|
||||||
# Executing StoreValueBlock
|
# Executing StoreValueBlock
|
||||||
exec = executions[0]
|
exec = graph_run.node_executions[0]
|
||||||
logger.info(f"Checking first StoreValueBlock execution: {exec}")
|
logger.info(f"Checking first StoreValueBlock execution: {exec}")
|
||||||
assert exec.status == execution.ExecutionStatus.COMPLETED
|
assert exec.status == execution.ExecutionStatus.COMPLETED
|
||||||
assert exec.graph_exec_id == graph_exec_id
|
assert exec.graph_exec_id == graph_exec_id
|
||||||
@@ -90,7 +90,7 @@ async def assert_sample_graph_executions(
|
|||||||
assert exec.node_id in [test_graph.nodes[0].id, test_graph.nodes[1].id]
|
assert exec.node_id in [test_graph.nodes[0].id, test_graph.nodes[1].id]
|
||||||
|
|
||||||
# Executing StoreValueBlock
|
# Executing StoreValueBlock
|
||||||
exec = executions[1]
|
exec = graph_run.node_executions[1]
|
||||||
logger.info(f"Checking second StoreValueBlock execution: {exec}")
|
logger.info(f"Checking second StoreValueBlock execution: {exec}")
|
||||||
assert exec.status == execution.ExecutionStatus.COMPLETED
|
assert exec.status == execution.ExecutionStatus.COMPLETED
|
||||||
assert exec.graph_exec_id == graph_exec_id
|
assert exec.graph_exec_id == graph_exec_id
|
||||||
@@ -103,7 +103,7 @@ async def assert_sample_graph_executions(
|
|||||||
assert exec.node_id in [test_graph.nodes[0].id, test_graph.nodes[1].id]
|
assert exec.node_id in [test_graph.nodes[0].id, test_graph.nodes[1].id]
|
||||||
|
|
||||||
# Executing FillTextTemplateBlock
|
# Executing FillTextTemplateBlock
|
||||||
exec = executions[2]
|
exec = graph_run.node_executions[2]
|
||||||
logger.info(f"Checking FillTextTemplateBlock execution: {exec}")
|
logger.info(f"Checking FillTextTemplateBlock execution: {exec}")
|
||||||
assert exec.status == execution.ExecutionStatus.COMPLETED
|
assert exec.status == execution.ExecutionStatus.COMPLETED
|
||||||
assert exec.graph_exec_id == graph_exec_id
|
assert exec.graph_exec_id == graph_exec_id
|
||||||
@@ -118,7 +118,7 @@ async def assert_sample_graph_executions(
|
|||||||
assert exec.node_id == test_graph.nodes[2].id
|
assert exec.node_id == test_graph.nodes[2].id
|
||||||
|
|
||||||
# Executing PrintToConsoleBlock
|
# Executing PrintToConsoleBlock
|
||||||
exec = executions[3]
|
exec = graph_run.node_executions[3]
|
||||||
logger.info(f"Checking PrintToConsoleBlock execution: {exec}")
|
logger.info(f"Checking PrintToConsoleBlock execution: {exec}")
|
||||||
assert exec.status == execution.ExecutionStatus.COMPLETED
|
assert exec.status == execution.ExecutionStatus.COMPLETED
|
||||||
assert exec.graph_exec_id == graph_exec_id
|
assert exec.graph_exec_id == graph_exec_id
|
||||||
@@ -201,14 +201,14 @@ async def test_input_pin_always_waited(server: SpinTestServer):
|
|||||||
)
|
)
|
||||||
|
|
||||||
logger.info("Checking execution results")
|
logger.info("Checking execution results")
|
||||||
executions = await server.agent_server.test_get_graph_run_node_execution_results(
|
graph_exec = await server.agent_server.test_get_graph_run_results(
|
||||||
test_graph.id, graph_exec_id, test_user.id
|
test_graph.id, graph_exec_id, test_user.id
|
||||||
)
|
)
|
||||||
assert len(executions) == 3
|
assert len(graph_exec.node_executions) == 3
|
||||||
# FindInDictionaryBlock should wait for the input pin to be provided,
|
# FindInDictionaryBlock should wait for the input pin to be provided,
|
||||||
# Hence executing extraction of "key" from {"key1": "value1", "key2": "value2"}
|
# Hence executing extraction of "key" from {"key1": "value1", "key2": "value2"}
|
||||||
assert executions[2].status == execution.ExecutionStatus.COMPLETED
|
assert graph_exec.node_executions[2].status == execution.ExecutionStatus.COMPLETED
|
||||||
assert executions[2].output_data == {"output": ["value2"]}
|
assert graph_exec.node_executions[2].output_data == {"output": ["value2"]}
|
||||||
logger.info("Completed test_input_pin_always_waited")
|
logger.info("Completed test_input_pin_always_waited")
|
||||||
|
|
||||||
|
|
||||||
@@ -284,12 +284,12 @@ async def test_static_input_link_on_graph(server: SpinTestServer):
|
|||||||
server.agent_server, test_graph, test_user, {}, 8
|
server.agent_server, test_graph, test_user, {}, 8
|
||||||
)
|
)
|
||||||
logger.info("Checking execution results")
|
logger.info("Checking execution results")
|
||||||
executions = await server.agent_server.test_get_graph_run_node_execution_results(
|
graph_exec = await server.agent_server.test_get_graph_run_results(
|
||||||
test_graph.id, graph_exec_id, test_user.id
|
test_graph.id, graph_exec_id, test_user.id
|
||||||
)
|
)
|
||||||
assert len(executions) == 8
|
assert len(graph_exec.node_executions) == 8
|
||||||
# The last 3 executions will be a+b=4+5=9
|
# The last 3 executions will be a+b=4+5=9
|
||||||
for i, exec_data in enumerate(executions[-3:]):
|
for i, exec_data in enumerate(graph_exec.node_executions[-3:]):
|
||||||
logger.info(f"Checking execution {i+1} of last 3: {exec_data}")
|
logger.info(f"Checking execution {i+1} of last 3: {exec_data}")
|
||||||
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]}
|
||||||
|
|||||||
184
autogpt_platform/frontend/src/app/agents/[id]/page.tsx
Normal file
184
autogpt_platform/frontend/src/app/agents/[id]/page.tsx
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
"use client";
|
||||||
|
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
|
import { useParams, useRouter } from "next/navigation";
|
||||||
|
|
||||||
|
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
||||||
|
import {
|
||||||
|
GraphExecution,
|
||||||
|
GraphExecutionMeta,
|
||||||
|
GraphMeta,
|
||||||
|
Schedule,
|
||||||
|
} from "@/lib/autogpt-server-api";
|
||||||
|
|
||||||
|
import AgentRunDraftView from "@/components/agents/agent-run-draft-view";
|
||||||
|
import AgentRunDetailsView from "@/components/agents/agent-run-details-view";
|
||||||
|
import AgentRunsSelectorList from "@/components/agents/agent-runs-selector-list";
|
||||||
|
import AgentScheduleDetailsView from "@/components/agents/agent-schedule-details-view";
|
||||||
|
|
||||||
|
export default function AgentRunsPage(): React.ReactElement {
|
||||||
|
const { id: agentID }: { id: string } = useParams();
|
||||||
|
const router = useRouter();
|
||||||
|
const api = useBackendAPI();
|
||||||
|
|
||||||
|
const [agent, setAgent] = useState<GraphMeta | null>(null);
|
||||||
|
const [agentRuns, setAgentRuns] = useState<GraphExecutionMeta[]>([]);
|
||||||
|
const [schedules, setSchedules] = useState<Schedule[]>([]);
|
||||||
|
const [selectedView, selectView] = useState<{
|
||||||
|
type: "run" | "schedule";
|
||||||
|
id?: string;
|
||||||
|
}>({ type: "run" });
|
||||||
|
const [selectedRun, setSelectedRun] = useState<
|
||||||
|
GraphExecution | GraphExecutionMeta | null
|
||||||
|
>(null);
|
||||||
|
const [selectedSchedule, setSelectedSchedule] = useState<Schedule | null>(
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
const [isFirstLoad, setIsFirstLoad] = useState<boolean>(true);
|
||||||
|
|
||||||
|
const openRunDraftView = useCallback(() => {
|
||||||
|
selectView({ type: "run" });
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const selectRun = useCallback((id: string) => {
|
||||||
|
selectView({ type: "run", id });
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const selectSchedule = useCallback((schedule: Schedule) => {
|
||||||
|
selectView({ type: "schedule", id: schedule.id });
|
||||||
|
setSelectedSchedule(schedule);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const fetchAgents = useCallback(() => {
|
||||||
|
api.getGraph(agentID).then(setAgent);
|
||||||
|
api.getGraphExecutions(agentID).then((agentRuns) => {
|
||||||
|
const sortedRuns = agentRuns.toSorted(
|
||||||
|
(a, b) => b.started_at - a.started_at,
|
||||||
|
);
|
||||||
|
setAgentRuns(sortedRuns);
|
||||||
|
|
||||||
|
if (!selectedView.id && isFirstLoad && sortedRuns.length > 0) {
|
||||||
|
// only for first load or first execution
|
||||||
|
setIsFirstLoad(false);
|
||||||
|
selectView({ type: "run", id: sortedRuns[0].execution_id });
|
||||||
|
setSelectedRun(sortedRuns[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (selectedView.type == "run" && selectedView.id) {
|
||||||
|
api.getGraphExecutionInfo(agentID, selectedView.id).then(setSelectedRun);
|
||||||
|
}
|
||||||
|
}, [api, agentID, selectedView, isFirstLoad]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchAgents();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// load selectedRun based on selectedView
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedView.type != "run" || !selectedView.id) return;
|
||||||
|
|
||||||
|
// pull partial data from "cache" while waiting for the rest to load
|
||||||
|
if (selectedView.id !== selectedRun?.execution_id) {
|
||||||
|
setSelectedRun(
|
||||||
|
agentRuns.find((r) => r.execution_id == selectedView.id) ?? null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
api.getGraphExecutionInfo(agentID, selectedView.id).then(setSelectedRun);
|
||||||
|
}, [api, selectedView, agentRuns, agentID]);
|
||||||
|
|
||||||
|
const fetchSchedules = useCallback(async () => {
|
||||||
|
// TODO: filter in backend - https://github.com/Significant-Gravitas/AutoGPT/issues/9183
|
||||||
|
setSchedules(
|
||||||
|
(await api.listSchedules()).filter((s) => s.graph_id == agentID),
|
||||||
|
);
|
||||||
|
}, [api, agentID]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchSchedules();
|
||||||
|
}, [fetchSchedules]);
|
||||||
|
|
||||||
|
const removeSchedule = useCallback(
|
||||||
|
async (scheduleId: string) => {
|
||||||
|
const removedSchedule = await api.deleteSchedule(scheduleId);
|
||||||
|
setSchedules(schedules.filter((s) => s.id !== removedSchedule.id));
|
||||||
|
},
|
||||||
|
[schedules, api],
|
||||||
|
);
|
||||||
|
|
||||||
|
/* TODO: use websockets instead of polling - https://github.com/Significant-Gravitas/AutoGPT/issues/8782 */
|
||||||
|
useEffect(() => {
|
||||||
|
const intervalId = setInterval(() => fetchAgents(), 5000);
|
||||||
|
return () => clearInterval(intervalId);
|
||||||
|
}, [fetchAgents, agent]);
|
||||||
|
|
||||||
|
const agentActions: { label: string; callback: () => void }[] = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
label: "Open in builder",
|
||||||
|
callback: () => agent && router.push(`/build?flowID=${agent.id}`),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[agent, router],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!agent) {
|
||||||
|
/* TODO: implement loading indicators / skeleton page */
|
||||||
|
return <span>Loading...</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container justify-stretch p-0 lg:flex">
|
||||||
|
{/* Sidebar w/ list of runs */}
|
||||||
|
{/* TODO: render this below header in sm and md layouts */}
|
||||||
|
<AgentRunsSelectorList
|
||||||
|
className="agpt-div w-full border-b lg:w-auto lg:border-b-0 lg:border-r"
|
||||||
|
agent={agent}
|
||||||
|
agentRuns={agentRuns}
|
||||||
|
schedules={schedules}
|
||||||
|
selectedView={selectedView}
|
||||||
|
onSelectRun={selectRun}
|
||||||
|
onSelectSchedule={selectSchedule}
|
||||||
|
onDraftNewRun={openRunDraftView}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="flex-1">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="agpt-div w-full border-b">
|
||||||
|
<h1 className="font-poppins text-3xl font-medium">
|
||||||
|
{
|
||||||
|
agent.name /* TODO: use dynamic/custom run title - https://github.com/Significant-Gravitas/AutoGPT/issues/9184 */
|
||||||
|
}
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Run / Schedule views */}
|
||||||
|
{(selectedView.type == "run" ? (
|
||||||
|
selectedView.id ? (
|
||||||
|
selectedRun && (
|
||||||
|
<AgentRunDetailsView
|
||||||
|
agent={agent}
|
||||||
|
run={selectedRun}
|
||||||
|
agentActions={agentActions}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<AgentRunDraftView
|
||||||
|
agent={agent}
|
||||||
|
onRun={(runID) => selectRun(runID)}
|
||||||
|
agentActions={agentActions}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
) : selectedView.type == "schedule" ? (
|
||||||
|
selectedSchedule && (
|
||||||
|
<AgentScheduleDetailsView
|
||||||
|
agent={agent}
|
||||||
|
schedule={selectedSchedule}
|
||||||
|
onForcedRun={(runID) => selectRun(runID)}
|
||||||
|
agentActions={agentActions}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
) : null) || <p>Loading...</p>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -2,54 +2,9 @@
|
|||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
@layer base {
|
|
||||||
.font-neue {
|
|
||||||
font-family: "PP Neue Montreal TT", sans-serif;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@layer utilities {
|
|
||||||
.w-110 {
|
|
||||||
width: 27.5rem;
|
|
||||||
}
|
|
||||||
.h-7\.5 {
|
|
||||||
height: 1.1875rem;
|
|
||||||
}
|
|
||||||
.h-18 {
|
|
||||||
height: 4.5rem;
|
|
||||||
}
|
|
||||||
.h-238 {
|
|
||||||
height: 14.875rem;
|
|
||||||
}
|
|
||||||
.top-158 {
|
|
||||||
top: 9.875rem;
|
|
||||||
}
|
|
||||||
.top-254 {
|
|
||||||
top: 15.875rem;
|
|
||||||
}
|
|
||||||
.top-284 {
|
|
||||||
top: 17.75rem;
|
|
||||||
}
|
|
||||||
.top-360 {
|
|
||||||
top: 22.5rem;
|
|
||||||
}
|
|
||||||
.left-297 {
|
|
||||||
left: 18.5625rem;
|
|
||||||
}
|
|
||||||
.left-34 {
|
|
||||||
left: 2.125rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@layer utilities {
|
|
||||||
.text-balance {
|
|
||||||
text-wrap: balance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
:root {
|
:root {
|
||||||
--background: 0 0% 100%;
|
--background: 0 0% 99.6%; /* #FEFEFE */
|
||||||
--foreground: 240 10% 3.9%;
|
--foreground: 240 10% 3.9%;
|
||||||
--card: 0 0% 100%;
|
--card: 0 0% 100%;
|
||||||
--card-foreground: 240 10% 3.9%;
|
--card-foreground: 240 10% 3.9%;
|
||||||
@@ -61,8 +16,8 @@
|
|||||||
--secondary-foreground: 240 5.9% 10%;
|
--secondary-foreground: 240 5.9% 10%;
|
||||||
--muted: 240 4.8% 95.9%;
|
--muted: 240 4.8% 95.9%;
|
||||||
--muted-foreground: 240 3.8% 46.1%;
|
--muted-foreground: 240 3.8% 46.1%;
|
||||||
--accent: 240 4.8% 95.9%;
|
--accent: 262 83% 58%;
|
||||||
--accent-foreground: 240 5.9% 10%;
|
--accent-foreground: 0 0% 100%;
|
||||||
--destructive: 0 84.2% 60.2%;
|
--destructive: 0 84.2% 60.2%;
|
||||||
--destructive-foreground: 0 0% 98%;
|
--destructive-foreground: 0 0% 98%;
|
||||||
--border: 240 5.9% 90%;
|
--border: 240 5.9% 90%;
|
||||||
@@ -102,9 +57,7 @@
|
|||||||
--chart-4: 280 65% 60%;
|
--chart-4: 280 65% 60%;
|
||||||
--chart-5: 340 75% 55%;
|
--chart-5: 340 75% 55%;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@layer base {
|
|
||||||
* {
|
* {
|
||||||
@apply border-border;
|
@apply border-border;
|
||||||
}
|
}
|
||||||
@@ -112,6 +65,14 @@
|
|||||||
@apply bg-background text-foreground;
|
@apply bg-background text-foreground;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.font-neue {
|
||||||
|
font-family: "PP Neue Montreal TT", sans-serif;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* *** AutoGPT Design Components *** */
|
||||||
|
|
||||||
|
@layer components {
|
||||||
.agpt-border-input {
|
.agpt-border-input {
|
||||||
@apply border border-input focus-visible:border-gray-400 focus-visible:outline-none;
|
@apply border border-input focus-visible:border-gray-400 focus-visible:outline-none;
|
||||||
}
|
}
|
||||||
@@ -119,4 +80,67 @@
|
|||||||
.agpt-shadow-input {
|
.agpt-shadow-input {
|
||||||
@apply shadow-sm focus-visible:shadow-md;
|
@apply shadow-sm focus-visible:shadow-md;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.agpt-rounded-card {
|
||||||
|
@apply rounded-2xl;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agpt-rounded-box {
|
||||||
|
@apply rounded-3xl;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agpt-card {
|
||||||
|
@apply agpt-rounded-card border border-zinc-300 bg-white p-[1px];
|
||||||
|
}
|
||||||
|
|
||||||
|
.agpt-box {
|
||||||
|
@apply agpt-card agpt-rounded-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agpt-div {
|
||||||
|
@apply border-zinc-200 p-5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer utilities {
|
||||||
|
.agpt-card-selected {
|
||||||
|
@apply border-2 border-accent bg-violet-50/50 p-0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer utilities {
|
||||||
|
/* TODO: 1. remove unused utility classes */
|
||||||
|
/* TODO: 2. fix naming of numbered dimensions so that the number is 4*dimension */
|
||||||
|
/* TODO: 3. move to tailwind.config.ts spacing config */
|
||||||
|
.h-7\.5 {
|
||||||
|
height: 1.1875rem;
|
||||||
|
}
|
||||||
|
.h-18 {
|
||||||
|
height: 4.5rem;
|
||||||
|
}
|
||||||
|
.h-238 {
|
||||||
|
height: 14.875rem;
|
||||||
|
}
|
||||||
|
.top-158 {
|
||||||
|
top: 9.875rem;
|
||||||
|
}
|
||||||
|
.top-254 {
|
||||||
|
top: 15.875rem;
|
||||||
|
}
|
||||||
|
.top-284 {
|
||||||
|
top: 17.75rem;
|
||||||
|
}
|
||||||
|
.top-360 {
|
||||||
|
top: 22.5rem;
|
||||||
|
}
|
||||||
|
.left-297 {
|
||||||
|
left: 18.5625rem;
|
||||||
|
}
|
||||||
|
.left-34 {
|
||||||
|
left: 2.125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-balance {
|
||||||
|
text-wrap: balance;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import React, { useCallback, useEffect, useState } from "react";
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
GraphExecution,
|
GraphExecutionMeta,
|
||||||
Schedule,
|
Schedule,
|
||||||
LibraryAgent,
|
LibraryAgent,
|
||||||
} from "@/lib/autogpt-server-api";
|
} from "@/lib/autogpt-server-api";
|
||||||
@@ -20,10 +20,12 @@ import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
|||||||
|
|
||||||
const Monitor = () => {
|
const Monitor = () => {
|
||||||
const [flows, setFlows] = useState<LibraryAgent[]>([]);
|
const [flows, setFlows] = useState<LibraryAgent[]>([]);
|
||||||
const [executions, setExecutions] = useState<GraphExecution[]>([]);
|
const [executions, setExecutions] = useState<GraphExecutionMeta[]>([]);
|
||||||
const [schedules, setSchedules] = useState<Schedule[]>([]);
|
const [schedules, setSchedules] = useState<Schedule[]>([]);
|
||||||
const [selectedFlow, setSelectedFlow] = useState<LibraryAgent | null>(null);
|
const [selectedFlow, setSelectedFlow] = useState<LibraryAgent | null>(null);
|
||||||
const [selectedRun, setSelectedRun] = useState<GraphExecution | null>(null);
|
const [selectedRun, setSelectedRun] = useState<GraphExecutionMeta | null>(
|
||||||
|
null,
|
||||||
|
);
|
||||||
const [sortColumn, setSortColumn] = useState<keyof Schedule>("id");
|
const [sortColumn, setSortColumn] = useState<keyof Schedule>("id");
|
||||||
const [sortDirection, setSortDirection] = useState<"asc" | "desc">("asc");
|
const [sortDirection, setSortDirection] = useState<"asc" | "desc">("asc");
|
||||||
const api = useBackendAPI();
|
const api = useBackendAPI();
|
||||||
|
|||||||
@@ -260,7 +260,6 @@ export default function CreditsPage() {
|
|||||||
</p>
|
</p>
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
variant="default"
|
|
||||||
className="w-full"
|
className="w-full"
|
||||||
onClick={() => openBillingPortal()}
|
onClick={() => openBillingPortal()}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -84,8 +84,6 @@ export default function Page({}: {}) {
|
|||||||
<PublishAgentPopout
|
<PublishAgentPopout
|
||||||
trigger={
|
trigger={
|
||||||
<Button
|
<Button
|
||||||
variant="default"
|
|
||||||
size="sm"
|
|
||||||
onClick={onOpenPopout}
|
onClick={onOpenPopout}
|
||||||
className="h-9 rounded-full bg-black px-4 text-sm font-medium text-white hover:bg-neutral-700 dark:hover:bg-neutral-600"
|
className="h-9 rounded-full bg-black px-4 text-sm font-medium text-white hover:bg-neutral-700 dark:hover:bg-neutral-600"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -0,0 +1,214 @@
|
|||||||
|
"use client";
|
||||||
|
import React, { useCallback, useMemo } from "react";
|
||||||
|
import moment from "moment";
|
||||||
|
|
||||||
|
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
||||||
|
import {
|
||||||
|
BlockIOSubType,
|
||||||
|
GraphExecution,
|
||||||
|
GraphExecutionMeta,
|
||||||
|
GraphMeta,
|
||||||
|
} from "@/lib/autogpt-server-api";
|
||||||
|
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
import { Button } from "@/components/agptui/Button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
|
||||||
|
import {
|
||||||
|
AgentRunStatus,
|
||||||
|
agentRunStatusMap,
|
||||||
|
} from "@/components/agents/agent-run-status-chip";
|
||||||
|
|
||||||
|
export default function AgentRunDetailsView({
|
||||||
|
agent,
|
||||||
|
run,
|
||||||
|
agentActions,
|
||||||
|
}: {
|
||||||
|
agent: GraphMeta;
|
||||||
|
run: GraphExecution | GraphExecutionMeta;
|
||||||
|
agentActions: { label: string; callback: () => void }[];
|
||||||
|
}): React.ReactNode {
|
||||||
|
const api = useBackendAPI();
|
||||||
|
|
||||||
|
const selectedRunStatus: AgentRunStatus = useMemo(
|
||||||
|
() => agentRunStatusMap[run.status],
|
||||||
|
[run],
|
||||||
|
);
|
||||||
|
|
||||||
|
const infoStats: { label: string; value: React.ReactNode }[] = useMemo(() => {
|
||||||
|
if (!run) return [];
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: "Status",
|
||||||
|
value:
|
||||||
|
selectedRunStatus.charAt(0).toUpperCase() +
|
||||||
|
selectedRunStatus.slice(1),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Started",
|
||||||
|
value: `${moment(run.started_at).fromNow()}, ${moment(run.started_at).format("HH:mm")}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Duration",
|
||||||
|
value: `${moment.duration(run.duration, "seconds").humanize()}`,
|
||||||
|
},
|
||||||
|
// { label: "Cost", value: selectedRun.cost }, // TODO: implement cost - https://github.com/Significant-Gravitas/AutoGPT/issues/9181
|
||||||
|
];
|
||||||
|
}, [run, selectedRunStatus]);
|
||||||
|
|
||||||
|
const agentRunInputs:
|
||||||
|
| Record<string, { title?: string; /* type: BlockIOSubType; */ value: any }>
|
||||||
|
| undefined = useMemo(() => {
|
||||||
|
if (!("inputs" in run)) return undefined;
|
||||||
|
// TODO: show (link to) preset - https://github.com/Significant-Gravitas/AutoGPT/issues/9168
|
||||||
|
|
||||||
|
// Add type info from agent input schema
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(run.inputs).map(([k, v]) => [
|
||||||
|
k,
|
||||||
|
{
|
||||||
|
title: agent.input_schema.properties[k].title,
|
||||||
|
// type: agent.input_schema.properties[k].type, // TODO: implement typed graph inputs
|
||||||
|
value: v,
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}, [agent, run]);
|
||||||
|
|
||||||
|
const runAgain = useCallback(
|
||||||
|
() =>
|
||||||
|
agentRunInputs &&
|
||||||
|
api.executeGraph(
|
||||||
|
agent.id,
|
||||||
|
agent.version,
|
||||||
|
Object.fromEntries(
|
||||||
|
Object.entries(agentRunInputs).map(([k, v]) => [k, v.value]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
[api, agent, agentRunInputs],
|
||||||
|
);
|
||||||
|
|
||||||
|
const agentRunOutputs:
|
||||||
|
| Record<
|
||||||
|
string,
|
||||||
|
{ title?: string; /* type: BlockIOSubType; */ values: Array<any> }
|
||||||
|
>
|
||||||
|
| null
|
||||||
|
| undefined = useMemo(() => {
|
||||||
|
if (!("outputs" in run)) return undefined;
|
||||||
|
if (!["running", "success", "failed"].includes(selectedRunStatus))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Add type info from agent input schema
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(run.outputs).map(([k, v]) => [
|
||||||
|
k,
|
||||||
|
{
|
||||||
|
title: agent.output_schema.properties[k].title,
|
||||||
|
/* type: agent.output_schema.properties[k].type */
|
||||||
|
values: v,
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}, [agent, run, selectedRunStatus]);
|
||||||
|
|
||||||
|
const runActions: { label: string; callback: () => void }[] = useMemo(
|
||||||
|
() => [{ label: "Run again", callback: () => runAgain() }],
|
||||||
|
[runAgain],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="agpt-div flex gap-6">
|
||||||
|
<div className="flex flex-1 flex-col gap-4">
|
||||||
|
<Card className="agpt-box">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="font-poppins text-lg">Info</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
|
||||||
|
<CardContent>
|
||||||
|
<div className="flex justify-stretch gap-4">
|
||||||
|
{infoStats.map(({ label, value }) => (
|
||||||
|
<div key={label} className="flex-1">
|
||||||
|
<p className="text-sm font-medium text-black">{label}</p>
|
||||||
|
<p className="text-sm text-neutral-600">{value}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{agentRunOutputs !== null && (
|
||||||
|
<Card className="agpt-box">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="font-poppins text-lg">Output</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex flex-col gap-4">
|
||||||
|
{agentRunOutputs !== undefined ? (
|
||||||
|
Object.entries(agentRunOutputs).map(
|
||||||
|
([key, { title, values }]) => (
|
||||||
|
<div key={key} className="flex flex-col gap-1.5">
|
||||||
|
<label className="text-sm font-medium">
|
||||||
|
{title || key}
|
||||||
|
</label>
|
||||||
|
{values.map((value, i) => (
|
||||||
|
<pre key={i}>{value}</pre>
|
||||||
|
))}
|
||||||
|
{/* TODO: pretty type-dependent rendering */}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<p>Loading...</p>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Card className="agpt-box">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="font-poppins text-lg">Input</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex flex-col gap-4">
|
||||||
|
{agentRunInputs !== undefined ? (
|
||||||
|
Object.entries(agentRunInputs).map(([key, { title, value }]) => (
|
||||||
|
<div key={key} className="flex flex-col gap-1.5">
|
||||||
|
<label className="text-sm font-medium">{title || key}</label>
|
||||||
|
<Input
|
||||||
|
defaultValue={value}
|
||||||
|
className="rounded-full"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<p>Loading...</p>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Run / Agent Actions */}
|
||||||
|
<aside className="w-48 xl:w-56">
|
||||||
|
<div className="flex flex-col gap-8">
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<h3 className="text-sm font-medium">Run actions</h3>
|
||||||
|
{runActions.map((action, i) => (
|
||||||
|
<Button key={i} variant="outline" onClick={action.callback}>
|
||||||
|
{action.label}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<h3 className="text-sm font-medium">Agent actions</h3>
|
||||||
|
{agentActions.map((action, i) => (
|
||||||
|
<Button key={i} variant="outline" onClick={action.callback}>
|
||||||
|
{action.label}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
"use client";
|
||||||
|
import React, { useCallback, useMemo, useState } from "react";
|
||||||
|
|
||||||
|
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
||||||
|
import { GraphMeta } from "@/lib/autogpt-server-api";
|
||||||
|
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
import { Button, ButtonProps } from "@/components/agptui/Button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
|
||||||
|
export default function AgentRunDraftView({
|
||||||
|
agent,
|
||||||
|
onRun,
|
||||||
|
agentActions,
|
||||||
|
}: {
|
||||||
|
agent: GraphMeta;
|
||||||
|
onRun: (runID: string) => void;
|
||||||
|
agentActions: { label: string; callback: () => void }[];
|
||||||
|
}): React.ReactNode {
|
||||||
|
const api = useBackendAPI();
|
||||||
|
|
||||||
|
const agentInputs = agent.input_schema.properties;
|
||||||
|
const [inputValues, setInputValues] = useState<Record<string, any>>({});
|
||||||
|
|
||||||
|
const doRun = useCallback(
|
||||||
|
() =>
|
||||||
|
api
|
||||||
|
.executeGraph(agent.id, agent.version, inputValues)
|
||||||
|
.then((newRun) => onRun(newRun.graph_exec_id)),
|
||||||
|
[api, agent, inputValues, onRun],
|
||||||
|
);
|
||||||
|
|
||||||
|
const runActions: {
|
||||||
|
label: string;
|
||||||
|
variant?: ButtonProps["variant"];
|
||||||
|
callback: () => void;
|
||||||
|
}[] = useMemo(
|
||||||
|
() => [{ label: "Run", variant: "accent", callback: () => doRun() }],
|
||||||
|
[doRun],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="agpt-div flex gap-6">
|
||||||
|
<div className="flex flex-1 flex-col gap-4">
|
||||||
|
<Card className="agpt-box">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="font-poppins text-lg">Input</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex flex-col gap-4">
|
||||||
|
{Object.entries(agentInputs).map(([key, inputSubSchema]) => (
|
||||||
|
<div key={key} className="flex flex-col gap-1.5">
|
||||||
|
<label className="text-sm font-medium">
|
||||||
|
{inputSubSchema.title || key}
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
// TODO: render specific inputs based on input types
|
||||||
|
defaultValue={
|
||||||
|
"default" in inputSubSchema ? inputSubSchema.default : ""
|
||||||
|
}
|
||||||
|
className="rounded-full"
|
||||||
|
onChange={(e) =>
|
||||||
|
setInputValues((obj) => ({ ...obj, [key]: e.target.value }))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Actions */}
|
||||||
|
<aside className="w-48 xl:w-56">
|
||||||
|
<div className="flex flex-col gap-8">
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<h3 className="text-sm font-medium">Run actions</h3>
|
||||||
|
{runActions.map((action, i) => (
|
||||||
|
<Button
|
||||||
|
key={i}
|
||||||
|
variant={action.variant ?? "outline"}
|
||||||
|
onClick={action.callback}
|
||||||
|
>
|
||||||
|
{action.label}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<h3 className="text-sm font-medium">Agent actions</h3>
|
||||||
|
{agentActions.map((action, i) => (
|
||||||
|
<Button key={i} variant="outline" onClick={action.callback}>
|
||||||
|
{action.label}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
|
||||||
|
import { GraphExecutionMeta } from "@/lib/autogpt-server-api/types";
|
||||||
|
|
||||||
|
export type AgentRunStatus =
|
||||||
|
| "success"
|
||||||
|
| "failed"
|
||||||
|
| "queued"
|
||||||
|
| "running"
|
||||||
|
| "stopped"
|
||||||
|
| "scheduled"
|
||||||
|
| "draft";
|
||||||
|
|
||||||
|
export const agentRunStatusMap: Record<
|
||||||
|
GraphExecutionMeta["status"],
|
||||||
|
AgentRunStatus
|
||||||
|
> = {
|
||||||
|
COMPLETED: "success",
|
||||||
|
FAILED: "failed",
|
||||||
|
QUEUED: "queued",
|
||||||
|
RUNNING: "running",
|
||||||
|
TERMINATED: "stopped",
|
||||||
|
// TODO: implement "draft" - https://github.com/Significant-Gravitas/AutoGPT/issues/9168
|
||||||
|
};
|
||||||
|
|
||||||
|
const statusData: Record<
|
||||||
|
AgentRunStatus,
|
||||||
|
{ label: string; variant: keyof typeof statusStyles }
|
||||||
|
> = {
|
||||||
|
success: { label: "Success", variant: "success" },
|
||||||
|
running: { label: "Running", variant: "info" },
|
||||||
|
failed: { label: "Failed", variant: "destructive" },
|
||||||
|
queued: { label: "Queued", variant: "warning" },
|
||||||
|
draft: { label: "Draft", variant: "secondary" },
|
||||||
|
stopped: { label: "Stopped", variant: "secondary" },
|
||||||
|
scheduled: { label: "Scheduled", variant: "secondary" },
|
||||||
|
};
|
||||||
|
|
||||||
|
const statusStyles = {
|
||||||
|
success:
|
||||||
|
"bg-green-100 text-green-800 hover:bg-green-100 hover:text-green-800",
|
||||||
|
destructive: "bg-red-100 text-red-800 hover:bg-red-100 hover:text-red-800",
|
||||||
|
warning:
|
||||||
|
"bg-yellow-100 text-yellow-800 hover:bg-yellow-100 hover:text-yellow-800",
|
||||||
|
info: "bg-blue-100 text-blue-800 hover:bg-blue-100 hover:text-blue-800",
|
||||||
|
secondary:
|
||||||
|
"bg-slate-100 text-slate-800 hover:bg-slate-100 hover:text-slate-800",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function AgentRunStatusChip({
|
||||||
|
status,
|
||||||
|
}: {
|
||||||
|
status: AgentRunStatus;
|
||||||
|
}): React.ReactElement {
|
||||||
|
return (
|
||||||
|
<Badge
|
||||||
|
variant="secondary"
|
||||||
|
className={`text-xs font-medium ${statusStyles[statusData[status].variant]} rounded-[45px] px-[9px] py-[3px]`}
|
||||||
|
>
|
||||||
|
{statusData[status].label}
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import React from "react";
|
||||||
|
import moment from "moment";
|
||||||
|
import { MoreVertical } from "lucide-react";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
|
||||||
|
import AgentRunStatusChip, {
|
||||||
|
AgentRunStatus,
|
||||||
|
} from "@/components/agents/agent-run-status-chip";
|
||||||
|
|
||||||
|
export type AgentRunSummaryProps = {
|
||||||
|
agentID: string;
|
||||||
|
agentRunID: string;
|
||||||
|
status: AgentRunStatus;
|
||||||
|
title: string;
|
||||||
|
timestamp: number | Date;
|
||||||
|
selected?: boolean;
|
||||||
|
onClick?: () => void;
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function AgentRunSummaryCard({
|
||||||
|
agentID,
|
||||||
|
agentRunID,
|
||||||
|
status,
|
||||||
|
title,
|
||||||
|
timestamp,
|
||||||
|
selected = false,
|
||||||
|
onClick,
|
||||||
|
className,
|
||||||
|
}: AgentRunSummaryProps): React.ReactElement {
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
className={cn(
|
||||||
|
"agpt-rounded-card cursor-pointer border-zinc-300",
|
||||||
|
selected ? "agpt-card-selected" : "",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
<CardContent className="relative p-2.5 lg:p-4">
|
||||||
|
<AgentRunStatusChip status={status} />
|
||||||
|
|
||||||
|
<div className="mt-5 flex items-center justify-between">
|
||||||
|
<h3 className="truncate pr-2 text-base font-medium text-neutral-900">
|
||||||
|
{title}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
{/* <DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant="ghost" className="h-5 w-5 p-0">
|
||||||
|
<MoreVertical className="h-5 w-5" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent>
|
||||||
|
<DropdownMenuItem
|
||||||
|
// TODO: implement
|
||||||
|
>
|
||||||
|
Pin into a template
|
||||||
|
</DropdownMenuItem>
|
||||||
|
|
||||||
|
<DropdownMenuItem
|
||||||
|
// TODO: implement
|
||||||
|
>
|
||||||
|
Rename
|
||||||
|
</DropdownMenuItem>
|
||||||
|
|
||||||
|
<DropdownMenuItem
|
||||||
|
// TODO: implement
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu> */}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p
|
||||||
|
className="mt-1 text-sm font-normal text-neutral-500"
|
||||||
|
title={moment(timestamp).toString()}
|
||||||
|
>
|
||||||
|
Ran {moment(timestamp).fromNow()}
|
||||||
|
</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
"use client";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { Plus } from "lucide-react";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import {
|
||||||
|
GraphExecutionMeta,
|
||||||
|
GraphMeta,
|
||||||
|
Schedule,
|
||||||
|
} from "@/lib/autogpt-server-api";
|
||||||
|
|
||||||
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
|
import { Button } from "@/components/agptui/Button";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
|
||||||
|
import { agentRunStatusMap } from "@/components/agents/agent-run-status-chip";
|
||||||
|
import AgentRunSummaryCard from "@/components/agents/agent-run-summary-card";
|
||||||
|
|
||||||
|
interface AgentRunsSelectorListProps {
|
||||||
|
agent: GraphMeta;
|
||||||
|
agentRuns: GraphExecutionMeta[];
|
||||||
|
schedules: Schedule[];
|
||||||
|
selectedView: { type: "run" | "schedule"; id?: string };
|
||||||
|
onSelectRun: (id: string) => void;
|
||||||
|
onSelectSchedule: (schedule: Schedule) => void;
|
||||||
|
onDraftNewRun: () => void;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AgentRunsSelectorList({
|
||||||
|
agent,
|
||||||
|
agentRuns,
|
||||||
|
schedules,
|
||||||
|
selectedView,
|
||||||
|
onSelectRun,
|
||||||
|
onSelectSchedule,
|
||||||
|
onDraftNewRun,
|
||||||
|
className,
|
||||||
|
}: AgentRunsSelectorListProps): React.ReactElement {
|
||||||
|
const [activeListTab, setActiveListTab] = useState<"runs" | "scheduled">(
|
||||||
|
"runs",
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<aside className={cn("flex flex-col gap-4", className)}>
|
||||||
|
<Button
|
||||||
|
size="card"
|
||||||
|
className={
|
||||||
|
"mb-4 hidden h-16 w-72 items-center gap-2 py-6 lg:flex xl:w-80 " +
|
||||||
|
(selectedView.type == "run" && !selectedView.id
|
||||||
|
? "agpt-card-selected text-accent"
|
||||||
|
: "")
|
||||||
|
}
|
||||||
|
onClick={onDraftNewRun}
|
||||||
|
>
|
||||||
|
<Plus className="h-6 w-6" />
|
||||||
|
<span>New run</span>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Badge
|
||||||
|
variant={activeListTab === "runs" ? "secondary" : "outline"}
|
||||||
|
className="cursor-pointer gap-2 rounded-full text-base"
|
||||||
|
onClick={() => setActiveListTab("runs")}
|
||||||
|
>
|
||||||
|
<span>Runs</span>
|
||||||
|
<span className="text-neutral-600">{agentRuns.length}</span>
|
||||||
|
</Badge>
|
||||||
|
|
||||||
|
<Badge
|
||||||
|
variant={activeListTab === "scheduled" ? "secondary" : "outline"}
|
||||||
|
className="cursor-pointer gap-2 rounded-full text-base"
|
||||||
|
onClick={() => setActiveListTab("scheduled")}
|
||||||
|
>
|
||||||
|
<span>Scheduled</span>
|
||||||
|
<span className="text-neutral-600">
|
||||||
|
{schedules.filter((s) => s.graph_id === agent.id).length}
|
||||||
|
</span>
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Runs / Schedules list */}
|
||||||
|
<ScrollArea className="lg:h-[calc(100vh-200px)]">
|
||||||
|
<div className="flex gap-2 lg:flex-col">
|
||||||
|
{/* New Run button - only in small layouts */}
|
||||||
|
<Button
|
||||||
|
size="card"
|
||||||
|
className={
|
||||||
|
"flex h-28 w-40 items-center gap-2 py-6 lg:hidden " +
|
||||||
|
(selectedView.type == "run" && !selectedView.id
|
||||||
|
? "agpt-card-selected text-accent"
|
||||||
|
: "")
|
||||||
|
}
|
||||||
|
onClick={onDraftNewRun}
|
||||||
|
>
|
||||||
|
<Plus className="h-6 w-6" />
|
||||||
|
<span>New run</span>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{activeListTab === "runs"
|
||||||
|
? agentRuns.map((run, i) => (
|
||||||
|
<AgentRunSummaryCard
|
||||||
|
className="h-28 w-72 lg:h-32 xl:w-80"
|
||||||
|
key={i}
|
||||||
|
agentID={run.graph_id}
|
||||||
|
agentRunID={run.execution_id}
|
||||||
|
status={agentRunStatusMap[run.status]}
|
||||||
|
title={agent.name}
|
||||||
|
timestamp={run.started_at}
|
||||||
|
selected={selectedView.id === run.execution_id}
|
||||||
|
onClick={() => onSelectRun(run.execution_id)}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
: schedules
|
||||||
|
.filter((schedule) => schedule.graph_id === agent.id)
|
||||||
|
.map((schedule, i) => (
|
||||||
|
<AgentRunSummaryCard
|
||||||
|
className="h-28 w-72 lg:h-32 xl:w-80"
|
||||||
|
key={i}
|
||||||
|
agentID={schedule.graph_id}
|
||||||
|
agentRunID={schedule.id}
|
||||||
|
status="scheduled"
|
||||||
|
title={schedule.name}
|
||||||
|
timestamp={schedule.next_run_time}
|
||||||
|
selected={selectedView.id === schedule.id}
|
||||||
|
onClick={() => onSelectSchedule(schedule)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</ScrollArea>
|
||||||
|
</aside>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
"use client";
|
||||||
|
import React, { useCallback, useMemo } from "react";
|
||||||
|
|
||||||
|
import { BlockIOSubType, GraphMeta, Schedule } from "@/lib/autogpt-server-api";
|
||||||
|
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
||||||
|
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
import { AgentRunStatus } from "@/components/agents/agent-run-status-chip";
|
||||||
|
import { Button } from "@/components/agptui/Button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
|
||||||
|
export default function AgentScheduleDetailsView({
|
||||||
|
agent,
|
||||||
|
schedule,
|
||||||
|
onForcedRun,
|
||||||
|
agentActions,
|
||||||
|
}: {
|
||||||
|
agent: GraphMeta;
|
||||||
|
schedule: Schedule;
|
||||||
|
onForcedRun: (runID: string) => void;
|
||||||
|
agentActions: { label: string; callback: () => void }[];
|
||||||
|
}): React.ReactNode {
|
||||||
|
const api = useBackendAPI();
|
||||||
|
|
||||||
|
const selectedRunStatus: AgentRunStatus = "scheduled";
|
||||||
|
|
||||||
|
const infoStats: { label: string; value: React.ReactNode }[] = useMemo(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: "Status",
|
||||||
|
value:
|
||||||
|
selectedRunStatus.charAt(0).toUpperCase() +
|
||||||
|
selectedRunStatus.slice(1),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Scheduled for",
|
||||||
|
value: schedule.next_run_time.toLocaleString(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}, [schedule, selectedRunStatus]);
|
||||||
|
|
||||||
|
const agentRunInputs: Record<
|
||||||
|
string,
|
||||||
|
{ title?: string; /* type: BlockIOSubType; */ value: any }
|
||||||
|
> = useMemo(() => {
|
||||||
|
// TODO: show (link to) preset - https://github.com/Significant-Gravitas/AutoGPT/issues/9168
|
||||||
|
|
||||||
|
// Add type info from agent input schema
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(schedule.input_data).map(([k, v]) => [
|
||||||
|
k,
|
||||||
|
{
|
||||||
|
title: agent.input_schema.properties[k].title,
|
||||||
|
/* TODO: type: agent.input_schema.properties[k].type */
|
||||||
|
value: v,
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}, [agent, schedule]);
|
||||||
|
|
||||||
|
const runNow = useCallback(
|
||||||
|
() =>
|
||||||
|
api
|
||||||
|
.executeGraph(agent.id, agent.version, schedule.input_data)
|
||||||
|
.then((run) => onForcedRun(run.graph_exec_id)),
|
||||||
|
[api, agent, schedule, onForcedRun],
|
||||||
|
);
|
||||||
|
|
||||||
|
const runActions: { label: string; callback: () => void }[] = useMemo(
|
||||||
|
() => [{ label: "Run now", callback: () => runNow() }],
|
||||||
|
[runNow],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="agpt-div flex gap-6">
|
||||||
|
<div className="flex flex-1 flex-col gap-4">
|
||||||
|
<Card className="agpt-box">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="font-poppins text-lg">Info</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
|
||||||
|
<CardContent>
|
||||||
|
<div className="flex justify-stretch gap-4">
|
||||||
|
{infoStats.map(({ label, value }) => (
|
||||||
|
<div key={label} className="flex-1">
|
||||||
|
<p className="text-sm font-medium text-black">{label}</p>
|
||||||
|
<p className="text-sm text-neutral-600">{value}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card className="agpt-box">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="font-poppins text-lg">Input</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex flex-col gap-4">
|
||||||
|
{agentRunInputs !== undefined ? (
|
||||||
|
Object.entries(agentRunInputs).map(([key, { title, value }]) => (
|
||||||
|
<div key={key} className="flex flex-col gap-1.5">
|
||||||
|
<label className="text-sm font-medium">{title || key}</label>
|
||||||
|
<Input
|
||||||
|
defaultValue={value}
|
||||||
|
className="rounded-full"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<p>Loading...</p>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Run / Agent Actions */}
|
||||||
|
<aside className="w-48 xl:w-56">
|
||||||
|
<div className="flex flex-col gap-8">
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<h3 className="text-sm font-medium">Run actions</h3>
|
||||||
|
{runActions.map((action, i) => (
|
||||||
|
<Button key={i} variant="outline" onClick={action.callback}>
|
||||||
|
{action.label}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<h3 className="text-sm font-medium">Agent actions</h3>
|
||||||
|
{agentActions.map((action, i) => (
|
||||||
|
<Button key={i} variant="outline" onClick={action.callback}>
|
||||||
|
{action.label}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -92,7 +92,6 @@ export const AgentImageItem: React.FC<AgentImageItemProps> = React.memo(
|
|||||||
{isVideoFile && playingVideoIndex !== index && (
|
{isVideoFile && playingVideoIndex !== index && (
|
||||||
<div className="absolute bottom-2 left-2 sm:bottom-3 sm:left-3 md:bottom-4 md:left-4 lg:bottom-[1.25rem] lg:left-[1.25rem]">
|
<div className="absolute bottom-2 left-2 sm:bottom-3 sm:left-3 md:bottom-4 md:left-4 lg:bottom-[1.25rem] lg:left-[1.25rem]">
|
||||||
<Button
|
<Button
|
||||||
variant="default"
|
|
||||||
size="default"
|
size="default"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (videoRef.current) {
|
if (videoRef.current) {
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ export const AgentInfo: React.FC<AgentInfoProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className="w-full max-w-[396px] px-4 sm:px-6 lg:w-[396px] lg:px-0">
|
<div className="w-full max-w-[396px] px-4 sm:px-6 lg:w-[396px] lg:px-0">
|
||||||
{/* Title */}
|
{/* Title */}
|
||||||
<div className="font-poppins mb-3 w-full text-2xl font-medium leading-normal text-neutral-900 dark:text-neutral-100 sm:text-3xl lg:mb-4 lg:text-[35px] lg:leading-10">
|
<div className="mb-3 w-full font-poppins text-2xl font-medium leading-normal text-neutral-900 dark:text-neutral-100 sm:text-3xl lg:mb-4 lg:text-[35px] lg:leading-10">
|
||||||
{name}
|
{name}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -26,13 +26,13 @@ export const BecomeACreator: React.FC<BecomeACreatorProps> = ({
|
|||||||
<div className="left-0 top-0 h-px w-full bg-gray-200 dark:bg-gray-700" />
|
<div className="left-0 top-0 h-px w-full bg-gray-200 dark:bg-gray-700" />
|
||||||
|
|
||||||
{/* Title */}
|
{/* Title */}
|
||||||
<h2 className="font-poppins underline-from-font decoration-skip-ink-none mb-[77px] mt-[25px] text-left text-[18px] font-semibold leading-[28px] text-neutral-800 dark:text-neutral-200">
|
<h2 className="underline-from-font decoration-skip-ink-none mb-[77px] mt-[25px] text-left font-poppins text-[18px] font-semibold leading-[28px] text-neutral-800 dark:text-neutral-200">
|
||||||
{title}
|
{title}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{/* Content Container */}
|
{/* Content Container */}
|
||||||
<div className="absolute left-1/2 top-1/2 w-full max-w-[900px] -translate-x-1/2 -translate-y-1/2 px-4 pt-16 text-center md:px-6 lg:px-0">
|
<div className="absolute left-1/2 top-1/2 w-full max-w-[900px] -translate-x-1/2 -translate-y-1/2 px-4 pt-16 text-center md:px-6 lg:px-0">
|
||||||
<h2 className="font-poppins underline-from-font decoration-skip-ink-none mb-6 text-center text-[48px] font-semibold leading-[54px] tracking-[-0.012em] text-neutral-950 dark:text-neutral-50 md:mb-8 lg:mb-12">
|
<h2 className="underline-from-font decoration-skip-ink-none mb-6 text-center font-poppins text-[48px] font-semibold leading-[54px] tracking-[-0.012em] text-neutral-950 dark:text-neutral-50 md:mb-8 lg:mb-12">
|
||||||
Build AI agents and share
|
Build AI agents and share
|
||||||
<br />
|
<br />
|
||||||
<span className="text-violet-600 dark:text-violet-400">
|
<span className="text-violet-600 dark:text-violet-400">
|
||||||
@@ -51,7 +51,7 @@ export const BecomeACreator: React.FC<BecomeACreatorProps> = ({
|
|||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
className="inline-flex h-[48px] cursor-pointer items-center justify-center rounded-[38px] bg-neutral-800 px-8 py-3 transition-colors hover:bg-neutral-700 dark:bg-neutral-700 dark:hover:bg-neutral-600 md:h-[56px] md:px-10 md:py-4 lg:h-[68px] lg:px-12 lg:py-5"
|
className="inline-flex h-[48px] cursor-pointer items-center justify-center rounded-[38px] bg-neutral-800 px-8 py-3 transition-colors hover:bg-neutral-700 dark:bg-neutral-700 dark:hover:bg-neutral-600 md:h-[56px] md:px-10 md:py-4 lg:h-[68px] lg:px-12 lg:py-5"
|
||||||
>
|
>
|
||||||
<span className="font-poppins whitespace-nowrap text-base font-medium leading-normal text-neutral-50 md:text-lg md:leading-relaxed lg:text-xl lg:leading-7">
|
<span className="whitespace-nowrap font-poppins text-base font-medium leading-normal text-neutral-50 md:text-lg md:leading-relaxed lg:text-xl lg:leading-7">
|
||||||
{buttonText}
|
{buttonText}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -65,15 +65,12 @@ export const Interactive: Story = {
|
|||||||
export const Variants: Story = {
|
export const Variants: Story = {
|
||||||
render: (args) => (
|
render: (args) => (
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
<Button {...args} variant="default">
|
<Button {...args} variant="outline">
|
||||||
Default
|
Outline (default)
|
||||||
</Button>
|
</Button>
|
||||||
<Button {...args} variant="destructive">
|
<Button {...args} variant="destructive">
|
||||||
Destructive
|
Destructive
|
||||||
</Button>
|
</Button>
|
||||||
<Button {...args} variant="outline">
|
|
||||||
Outline
|
|
||||||
</Button>
|
|
||||||
<Button {...args} variant="secondary">
|
<Button {...args} variant="secondary">
|
||||||
Secondary
|
Secondary
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -5,16 +5,15 @@ import { cva, type VariantProps } from "class-variance-authority";
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
"inline-flex items-center justify-center whitespace-nowrap rounded-[80px] text-xl font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-neutral-950 disabled:pointer-events-none disabled:opacity-50 dark:focus-visible:ring-neutral-300 font-neue leading-9 tracking-tight",
|
"inline-flex items-center whitespace-nowrap font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-neutral-950 disabled:pointer-events-none disabled:opacity-50 dark:focus-visible:ring-neutral-300 font-neue leading-9 tracking-tight",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default:
|
|
||||||
"bg-white border border-black/50 text-[#272727] hover:bg-neutral-100 dark:bg-neutral-800 dark:text-neutral-100 dark:hover:bg-neutral-700",
|
|
||||||
destructive:
|
destructive:
|
||||||
"bg-red-600 text-neutral-50 border border-red-500/50 hover:bg-red-500/90 dark:bg-red-700 dark:text-neutral-50 dark:hover:bg-red-600",
|
"bg-red-600 text-neutral-50 border border-red-500/50 hover:bg-red-500/90 dark:bg-red-700 dark:text-neutral-50 dark:hover:bg-red-600",
|
||||||
|
accent: "bg-accent text-accent-foreground hover:bg-violet-500",
|
||||||
outline:
|
outline:
|
||||||
"bg-white border border-black/50 text-[#272727] hover:bg-neutral-100 dark:bg-neutral-800 dark:text-neutral-100 dark:hover:bg-neutral-700",
|
"border border-black/50 text-[#272727] hover:bg-neutral-100 dark:bg-neutral-800 dark:text-neutral-100 dark:hover:bg-neutral-700",
|
||||||
secondary:
|
secondary:
|
||||||
"bg-neutral-100 text-[#272727] border border-neutral-200 hover:bg-neutral-100/80 dark:bg-neutral-700 dark:text-neutral-100 dark:border-neutral-600 dark:hover:bg-neutral-600",
|
"bg-neutral-100 text-[#272727] border border-neutral-200 hover:bg-neutral-100/80 dark:bg-neutral-700 dark:text-neutral-100 dark:border-neutral-600 dark:hover:bg-neutral-600",
|
||||||
ghost:
|
ghost:
|
||||||
@@ -22,17 +21,17 @@ const buttonVariants = cva(
|
|||||||
link: "text-[#272727] underline-offset-4 hover:underline dark:text-neutral-100",
|
link: "text-[#272727] underline-offset-4 hover:underline dark:text-neutral-100",
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default:
|
default: "h-10 px-4 py-2 rounded-full text-sm",
|
||||||
"h-10 px-4 py-2 text-sm sm:h-12 sm:px-5 sm:py-2.5 sm:text-base md:h-14 md:px-6 md:py-3 md:text-lg lg:h-[4.375rem] lg:px-[1.625rem] lg:py-[0.4375rem] lg:text-xl",
|
sm: "h-8 px-3 py-1.5 rounded-full text-xs",
|
||||||
sm: "h-8 px-3 py-1.5 text-xs sm:h-9 sm:px-3.5 sm:py-2 sm:text-sm md:h-10 md:px-4 md:py-2 md:text-base lg:h-[3.125rem] lg:px-[1.25rem] lg:py-[0.3125rem] lg:text-sm",
|
lg: "h-12 px-5 py-2.5 rounded-full text-lg",
|
||||||
lg: "h-12 px-5 py-2.5 text-lg sm:h-14 sm:px-6 sm:py-3 sm:text-xl md:h-16 md:px-7 md:py-3.5 md:text-2xl lg:h-[5.625rem] lg:px-[2rem] lg:py-[0.5625rem] lg:text-2xl",
|
|
||||||
primary:
|
primary:
|
||||||
"h-10 w-28 sm:h-12 sm:w-32 md:h-[4.375rem] md:w-[11rem] lg:h-[3.125rem] lg:w-[7rem]",
|
"h-10 w-28 rounded-full sm:h-12 sm:w-32 md:h-[4.375rem] md:w-[11rem] lg:h-[3.125rem] lg:w-[7rem]",
|
||||||
icon: "h-10 w-10 sm:h-12 sm:w-12 md:h-14 md:w-14 lg:h-[4.375rem] lg:w-[4.375rem]",
|
icon: "h-10 w-10",
|
||||||
|
card: "h-12 p-5 agpt-rounded-card justify-center text-lg",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
variant: "default",
|
variant: "outline",
|
||||||
size: "default",
|
size: "default",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -43,13 +42,13 @@ export interface ButtonProps
|
|||||||
VariantProps<typeof buttonVariants> {
|
VariantProps<typeof buttonVariants> {
|
||||||
asChild?: boolean;
|
asChild?: boolean;
|
||||||
variant?:
|
variant?:
|
||||||
| "default"
|
|
||||||
| "destructive"
|
| "destructive"
|
||||||
|
| "accent"
|
||||||
| "outline"
|
| "outline"
|
||||||
| "secondary"
|
| "secondary"
|
||||||
| "ghost"
|
| "ghost"
|
||||||
| "link";
|
| "link";
|
||||||
size?: "default" | "sm" | "lg" | "primary" | "icon";
|
size?: "default" | "sm" | "lg" | "primary" | "icon" | "card";
|
||||||
}
|
}
|
||||||
|
|
||||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export const CreatorInfoCard: React.FC<CreatorInfoCardProps> = ({
|
|||||||
</AvatarFallback>
|
</AvatarFallback>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<div className="flex w-full flex-col items-start justify-start gap-1.5">
|
<div className="flex w-full flex-col items-start justify-start gap-1.5">
|
||||||
<div className="font-poppins w-full text-[35px] font-medium leading-10 text-neutral-900 dark:text-neutral-100 sm:text-[35px] sm:leading-10">
|
<div className="w-full font-poppins text-[35px] font-medium leading-10 text-neutral-900 dark:text-neutral-100 sm:text-[35px] sm:leading-10">
|
||||||
{username}
|
{username}
|
||||||
</div>
|
</div>
|
||||||
<div className="font-geist w-full text-lg font-normal leading-6 text-neutral-800 dark:text-neutral-200 sm:text-xl sm:leading-7">
|
<div className="font-geist w-full text-lg font-normal leading-6 text-neutral-800 dark:text-neutral-200 sm:text-xl sm:leading-7">
|
||||||
|
|||||||
@@ -72,7 +72,6 @@ export const Navbar = async ({ links, menuItemGroups }: NavbarProps) => {
|
|||||||
) : (
|
) : (
|
||||||
<Link href="/login">
|
<Link href="/login">
|
||||||
<Button
|
<Button
|
||||||
variant="default"
|
|
||||||
size="sm"
|
size="sm"
|
||||||
className="flex items-center justify-end space-x-2"
|
className="flex items-center justify-end space-x-2"
|
||||||
>
|
>
|
||||||
@@ -119,11 +118,7 @@ export const Navbar = async ({ links, menuItemGroups }: NavbarProps) => {
|
|||||||
href="/login"
|
href="/login"
|
||||||
className="fixed right-4 top-4 z-50 mt-4 inline-flex h-8 items-center justify-end rounded-lg pr-4 md:hidden"
|
className="fixed right-4 top-4 z-50 mt-4 inline-flex h-8 items-center justify-end rounded-lg pr-4 md:hidden"
|
||||||
>
|
>
|
||||||
<Button
|
<Button size="sm" className="flex items-center space-x-2">
|
||||||
variant="default"
|
|
||||||
size="sm"
|
|
||||||
className="flex items-center space-x-2"
|
|
||||||
>
|
|
||||||
<IconLogIn className="h-5 w-5" />
|
<IconLogIn className="h-5 w-5" />
|
||||||
<span>Log In</span>
|
<span>Log In</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export const NavbarLink = ({ name, href }: NavbarLinkProps) => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div
|
<div
|
||||||
className={`font-poppins text-[20px] font-medium leading-[28px] ${
|
className={`hidden font-poppins text-[20px] font-medium leading-[28px] lg:block ${
|
||||||
activeLink === href
|
activeLink === href
|
||||||
? "text-neutral-50 dark:text-neutral-900"
|
? "text-neutral-50 dark:text-neutral-900"
|
||||||
: "text-neutral-900 dark:text-neutral-50"
|
: "text-neutral-900 dark:text-neutral-50"
|
||||||
|
|||||||
@@ -256,7 +256,6 @@ export const ProfileInfoForm = ({ profile }: { profile: CreatorDetails }) => {
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
variant="default"
|
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
className="font-circular h-[50px] rounded-[35px] bg-neutral-800 px-6 py-3 text-base font-medium text-white transition-colors hover:bg-neutral-900 dark:bg-neutral-200 dark:text-neutral-900 dark:hover:bg-neutral-100"
|
className="font-circular h-[50px] rounded-[35px] bg-neutral-800 px-6 py-3 text-base font-medium text-white transition-colors hover:bg-neutral-900 dark:bg-neutral-200 dark:text-neutral-900 dark:hover:bg-neutral-100"
|
||||||
onClick={submitForm}
|
onClick={submitForm}
|
||||||
|
|||||||
@@ -100,14 +100,12 @@ export const PublishAgentAwaitingReview: React.FC<
|
|||||||
<div className="flex w-full flex-col items-center justify-center gap-4 border-t border-slate-200 p-6 dark:border-slate-700 sm:flex-row">
|
<div className="flex w-full flex-col items-center justify-center gap-4 border-t border-slate-200 p-6 dark:border-slate-700 sm:flex-row">
|
||||||
<Button
|
<Button
|
||||||
onClick={onDone}
|
onClick={onDone}
|
||||||
variant="outline"
|
|
||||||
className="h-12 w-full rounded-[59px] sm:flex-1"
|
className="h-12 w-full rounded-[59px] sm:flex-1"
|
||||||
>
|
>
|
||||||
Done
|
Done
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={onViewProgress}
|
onClick={onViewProgress}
|
||||||
variant="default"
|
|
||||||
className="h-12 w-full rounded-[59px] bg-neutral-800 text-white hover:bg-neutral-900 dark:bg-neutral-700 dark:text-neutral-100 dark:hover:bg-neutral-600 sm:flex-1"
|
className="h-12 w-full rounded-[59px] bg-neutral-800 text-white hover:bg-neutral-900 dark:bg-neutral-700 dark:text-neutral-100 dark:hover:bg-neutral-600 sm:flex-1"
|
||||||
>
|
>
|
||||||
View progress
|
View progress
|
||||||
|
|||||||
@@ -84,7 +84,6 @@ export const PublishAgentSelect: React.FC<PublishAgentSelectProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
onClick={onOpenBuilder}
|
onClick={onOpenBuilder}
|
||||||
variant="default"
|
|
||||||
size="lg"
|
size="lg"
|
||||||
className="bg-neutral-800 text-white hover:bg-neutral-900"
|
className="bg-neutral-800 text-white hover:bg-neutral-900"
|
||||||
>
|
>
|
||||||
@@ -150,12 +149,7 @@ export const PublishAgentSelect: React.FC<PublishAgentSelectProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-between gap-4 border-t border-slate-200 p-4 dark:border-slate-700 sm:p-6">
|
<div className="flex justify-between gap-4 border-t border-slate-200 p-4 dark:border-slate-700 sm:p-6">
|
||||||
<Button
|
<Button onClick={onCancel} size="lg" className="w-full sm:flex-1">
|
||||||
onClick={onCancel}
|
|
||||||
variant="outline"
|
|
||||||
size="default"
|
|
||||||
className="w-full sm:flex-1"
|
|
||||||
>
|
|
||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
@@ -165,8 +159,7 @@ export const PublishAgentSelect: React.FC<PublishAgentSelectProps> = ({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
disabled={!selectedAgentId || !selectedAgentVersion}
|
disabled={!selectedAgentId || !selectedAgentVersion}
|
||||||
variant="default"
|
size="lg"
|
||||||
size="default"
|
|
||||||
className="w-full bg-neutral-800 text-white hover:bg-neutral-900 sm:flex-1"
|
className="w-full bg-neutral-800 text-white hover:bg-neutral-900 sm:flex-1"
|
||||||
>
|
>
|
||||||
Next
|
Next
|
||||||
|
|||||||
@@ -344,8 +344,6 @@ export const PublishAgentInfo: React.FC<PublishAgentInfoProps> = ({
|
|||||||
You can use AI to generate a cover image for you
|
You can use AI to generate a cover image for you
|
||||||
</p>
|
</p>
|
||||||
<Button
|
<Button
|
||||||
variant="default"
|
|
||||||
size="sm"
|
|
||||||
className={`bg-neutral-800 text-white hover:bg-neutral-900 dark:bg-neutral-600 dark:hover:bg-neutral-500 ${
|
className={`bg-neutral-800 text-white hover:bg-neutral-900 dark:bg-neutral-600 dark:hover:bg-neutral-500 ${
|
||||||
images.length >= 5 ? "cursor-not-allowed opacity-50" : ""
|
images.length >= 5 ? "cursor-not-allowed opacity-50" : ""
|
||||||
}`}
|
}`}
|
||||||
@@ -426,16 +424,14 @@ export const PublishAgentInfo: React.FC<PublishAgentInfoProps> = ({
|
|||||||
<div className="flex justify-between gap-4 border-t border-slate-200 p-6 dark:border-slate-700">
|
<div className="flex justify-between gap-4 border-t border-slate-200 p-6 dark:border-slate-700">
|
||||||
<Button
|
<Button
|
||||||
onClick={onBack}
|
onClick={onBack}
|
||||||
variant="outline"
|
size="lg"
|
||||||
size="default"
|
|
||||||
className="w-full dark:border-slate-700 dark:text-slate-300 sm:flex-1"
|
className="w-full dark:border-slate-700 dark:text-slate-300 sm:flex-1"
|
||||||
>
|
>
|
||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
variant="default"
|
size="lg"
|
||||||
size="default"
|
|
||||||
className="w-full bg-neutral-800 text-white hover:bg-neutral-900 dark:bg-neutral-600 dark:hover:bg-neutral-500 sm:flex-1"
|
className="w-full bg-neutral-800 text-white hover:bg-neutral-900 dark:bg-neutral-600 dark:hover:bg-neutral-500 sm:flex-1"
|
||||||
>
|
>
|
||||||
Submit for review
|
Submit for review
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ export const StoreCard: React.FC<StoreCardProps> = ({
|
|||||||
{/* Content Section */}
|
{/* Content Section */}
|
||||||
<div className="w-full px-2 py-4">
|
<div className="w-full px-2 py-4">
|
||||||
{/* Title and Creator */}
|
{/* Title and Creator */}
|
||||||
<h3 className="font-poppins mb-0.5 text-2xl font-semibold leading-tight text-[#272727] dark:text-neutral-100">
|
<h3 className="mb-0.5 font-poppins text-2xl font-semibold leading-tight text-[#272727] dark:text-neutral-100">
|
||||||
{agentName}
|
{agentName}
|
||||||
</h3>
|
</h3>
|
||||||
{!hideAvatar && creatorName && (
|
{!hideAvatar && creatorName && (
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export const AgentsSection: React.FC<AgentsSectionProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center justify-center py-4 lg:py-8">
|
<div className="flex flex-col items-center justify-center py-4 lg:py-8">
|
||||||
<div className="w-full max-w-[1360px]">
|
<div className="w-full max-w-[1360px]">
|
||||||
<div className="font-poppins decoration-skip-ink-none mb-8 text-left text-[18px] font-[600] leading-7 text-[#282828] underline-offset-[from-font] dark:text-neutral-200">
|
<div className="decoration-skip-ink-none mb-8 text-left font-poppins text-[18px] font-[600] leading-7 text-[#282828] underline-offset-[from-font] dark:text-neutral-200">
|
||||||
{sectionTitle}
|
{sectionTitle}
|
||||||
</div>
|
</div>
|
||||||
{!displayedAgents || displayedAgents.length === 0 ? (
|
{!displayedAgents || displayedAgents.length === 0 ? (
|
||||||
@@ -65,7 +65,7 @@ export const AgentsSection: React.FC<AgentsSectionProps> = ({
|
|||||||
>
|
>
|
||||||
<CarouselContent>
|
<CarouselContent>
|
||||||
{displayedAgents.map((agent, index) => (
|
{displayedAgents.map((agent, index) => (
|
||||||
<CarouselItem key={index} className="min-w-64 max-w-68">
|
<CarouselItem key={index} className="min-w-64 max-w-71">
|
||||||
<StoreCard
|
<StoreCard
|
||||||
agentName={agent.agent_name}
|
agentName={agent.agent_name}
|
||||||
agentImage={agent.agent_image}
|
agentImage={agent.agent_image}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export const FeaturedCreators: React.FC<FeaturedCreatorsProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className="flex w-full flex-col items-center justify-center py-16">
|
<div className="flex w-full flex-col items-center justify-center py-16">
|
||||||
<div className="w-full max-w-[1360px]">
|
<div className="w-full max-w-[1360px]">
|
||||||
<h2 className="font-poppins mb-8 text-2xl font-semibold leading-7 text-neutral-800 dark:text-neutral-200">
|
<h2 className="mb-8 font-poppins text-2xl font-semibold leading-7 text-neutral-800 dark:text-neutral-200">
|
||||||
{title}
|
{title}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export const FeaturedSection: React.FC<FeaturedSectionProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className="flex w-full flex-col items-center justify-center">
|
<div className="flex w-full flex-col items-center justify-center">
|
||||||
<div className="w-[99vw]">
|
<div className="w-[99vw]">
|
||||||
<h2 className="font-poppins mx-auto mb-8 max-w-[1360px] px-4 text-2xl font-semibold leading-7 text-neutral-800 dark:text-neutral-200">
|
<h2 className="mx-auto mb-8 max-w-[1360px] px-4 font-poppins text-2xl font-semibold leading-7 text-neutral-800 dark:text-neutral-200">
|
||||||
Featured agents
|
Featured agents
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
|
|||||||
@@ -18,17 +18,17 @@ export const HeroSection: React.FC = () => {
|
|||||||
<div className="w-full max-w-3xl lg:max-w-4xl xl:max-w-5xl">
|
<div className="w-full max-w-3xl lg:max-w-4xl xl:max-w-5xl">
|
||||||
<div className="mb-4 text-center md:mb-8">
|
<div className="mb-4 text-center md:mb-8">
|
||||||
<h1 className="text-center">
|
<h1 className="text-center">
|
||||||
<span className="font-poppin text-[48px] font-semibold leading-[54px] text-neutral-950 dark:text-neutral-50">
|
<span className="font-poppins text-[48px] font-semibold leading-[54px] text-neutral-950 dark:text-neutral-50">
|
||||||
Explore AI agents built for{" "}
|
Explore AI agents built for{" "}
|
||||||
</span>
|
</span>
|
||||||
<span className="font-poppin text-[48px] font-semibold leading-[54px] text-violet-600">
|
<span className="font-poppins text-[48px] font-semibold leading-[54px] text-violet-600">
|
||||||
you
|
you
|
||||||
</span>
|
</span>
|
||||||
<br />
|
<br />
|
||||||
<span className="font-poppin text-[48px] font-semibold leading-[54px] text-neutral-950 dark:text-neutral-50">
|
<span className="font-poppins text-[48px] font-semibold leading-[54px] text-neutral-950 dark:text-neutral-50">
|
||||||
by the{" "}
|
by the{" "}
|
||||||
</span>
|
</span>
|
||||||
<span className="font-poppin text-[48px] font-semibold leading-[54px] text-blue-500">
|
<span className="font-poppins text-[48px] font-semibold leading-[54px] text-blue-500">
|
||||||
community
|
community
|
||||||
</span>
|
</span>
|
||||||
</h1>
|
</h1>
|
||||||
|
|||||||
@@ -284,7 +284,7 @@ export const PublishAgentPopout: React.FC<PublishAgentPopoutProps> = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
{trigger || <Button variant="default">Publish Agent</Button>}
|
{trigger || <Button>Publish Agent</Button>}
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverAnchor asChild>
|
<PopoverAnchor asChild>
|
||||||
<div className="fixed left-0 top-0 hidden h-screen w-screen items-center justify-center"></div>
|
<div className="fixed left-0 top-0 hidden h-screen w-screen items-center justify-center"></div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { GraphExecution, LibraryAgent } from "@/lib/autogpt-server-api";
|
import { GraphExecutionMeta, LibraryAgent } from "@/lib/autogpt-server-api";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -37,7 +37,7 @@ export const AgentFlowList = ({
|
|||||||
className,
|
className,
|
||||||
}: {
|
}: {
|
||||||
flows: LibraryAgent[];
|
flows: LibraryAgent[];
|
||||||
executions?: GraphExecution[];
|
executions?: GraphExecutionMeta[];
|
||||||
selectedFlow: LibraryAgent | null;
|
selectedFlow: LibraryAgent | null;
|
||||||
onSelectFlow: (f: LibraryAgent) => void;
|
onSelectFlow: (f: LibraryAgent) => void;
|
||||||
className?: string;
|
className?: string;
|
||||||
@@ -106,7 +106,7 @@ export const AgentFlowList = ({
|
|||||||
{flows
|
{flows
|
||||||
.map((flow) => {
|
.map((flow) => {
|
||||||
let runCount = 0,
|
let runCount = 0,
|
||||||
lastRun: GraphExecution | null = null;
|
lastRun: GraphExecutionMeta | null = null;
|
||||||
if (executions) {
|
if (executions) {
|
||||||
const _flowRuns = executions.filter(
|
const _flowRuns = executions.filter(
|
||||||
(r) => r.graph_id == flow.agent_id,
|
(r) => r.graph_id == flow.agent_id,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useEffect, useState, useCallback } from "react";
|
import React, { useEffect, useState, useCallback } from "react";
|
||||||
import {
|
import {
|
||||||
GraphExecution,
|
GraphExecutionMeta,
|
||||||
Graph,
|
Graph,
|
||||||
safeCopyGraph,
|
safeCopyGraph,
|
||||||
BlockUIType,
|
BlockUIType,
|
||||||
@@ -32,7 +32,6 @@ import {
|
|||||||
DialogFooter,
|
DialogFooter,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { useToast } from "@/components/ui/use-toast";
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
import { CronScheduler } from "@/components/cronScheduler";
|
|
||||||
import RunnerInputUI from "@/components/runner-ui/RunnerInputUI";
|
import RunnerInputUI from "@/components/runner-ui/RunnerInputUI";
|
||||||
import useAgentGraph from "@/hooks/useAgentGraph";
|
import useAgentGraph from "@/hooks/useAgentGraph";
|
||||||
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
||||||
@@ -40,7 +39,7 @@ import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
|||||||
export const FlowInfo: React.FC<
|
export const FlowInfo: React.FC<
|
||||||
React.HTMLAttributes<HTMLDivElement> & {
|
React.HTMLAttributes<HTMLDivElement> & {
|
||||||
flow: LibraryAgent;
|
flow: LibraryAgent;
|
||||||
executions: GraphExecution[];
|
executions: GraphExecutionMeta[];
|
||||||
flowVersion?: number | "all";
|
flowVersion?: number | "all";
|
||||||
refresh: () => void;
|
refresh: () => void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useCallback, useEffect, useState } from "react";
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
GraphExecution,
|
GraphExecutionMeta,
|
||||||
LibraryAgent,
|
LibraryAgent,
|
||||||
NodeExecutionResult,
|
NodeExecutionResult,
|
||||||
SpecialBlockID,
|
SpecialBlockID,
|
||||||
@@ -18,7 +18,7 @@ import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
|||||||
export const FlowRunInfo: React.FC<
|
export const FlowRunInfo: React.FC<
|
||||||
React.HTMLAttributes<HTMLDivElement> & {
|
React.HTMLAttributes<HTMLDivElement> & {
|
||||||
flow: LibraryAgent;
|
flow: LibraryAgent;
|
||||||
execution: GraphExecution;
|
execution: GraphExecutionMeta;
|
||||||
}
|
}
|
||||||
> = ({ flow, execution, ...props }) => {
|
> = ({ flow, execution, ...props }) => {
|
||||||
const [isOutputOpen, setIsOutputOpen] = useState(false);
|
const [isOutputOpen, setIsOutputOpen] = useState(false);
|
||||||
@@ -26,10 +26,9 @@ export const FlowRunInfo: React.FC<
|
|||||||
const api = useBackendAPI();
|
const api = useBackendAPI();
|
||||||
|
|
||||||
const fetchBlockResults = useCallback(async () => {
|
const fetchBlockResults = useCallback(async () => {
|
||||||
const executionResults = await api.getGraphExecutionInfo(
|
const executionResults = (
|
||||||
flow.agent_id,
|
await api.getGraphExecutionInfo(flow.agent_id, execution.execution_id)
|
||||||
execution.execution_id,
|
).node_executions;
|
||||||
);
|
|
||||||
|
|
||||||
// Create a map of the latest COMPLETED execution results of output nodes by node_id
|
// Create a map of the latest COMPLETED execution results of output nodes by node_id
|
||||||
const latestCompletedResults = executionResults
|
const latestCompletedResults = executionResults
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { GraphExecution } from "@/lib/autogpt-server-api";
|
import { GraphExecutionMeta } from "@/lib/autogpt-server-api";
|
||||||
|
|
||||||
export const FlowRunStatusBadge: React.FC<{
|
export const FlowRunStatusBadge: React.FC<{
|
||||||
status: GraphExecution["status"];
|
status: GraphExecutionMeta["status"];
|
||||||
className?: string;
|
className?: string;
|
||||||
}> = ({ status, className }) => (
|
}> = ({ status, className }) => (
|
||||||
<Badge
|
<Badge
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { GraphExecution, LibraryAgent } from "@/lib/autogpt-server-api";
|
import { GraphExecutionMeta, LibraryAgent } from "@/lib/autogpt-server-api";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
@@ -15,10 +15,10 @@ import { TextRenderer } from "../ui/render";
|
|||||||
|
|
||||||
export const FlowRunsList: React.FC<{
|
export const FlowRunsList: React.FC<{
|
||||||
flows: LibraryAgent[];
|
flows: LibraryAgent[];
|
||||||
executions: GraphExecution[];
|
executions: GraphExecutionMeta[];
|
||||||
className?: string;
|
className?: string;
|
||||||
selectedRun?: GraphExecution | null;
|
selectedRun?: GraphExecutionMeta | null;
|
||||||
onSelectRun: (r: GraphExecution) => void;
|
onSelectRun: (r: GraphExecutionMeta) => void;
|
||||||
}> = ({ flows, executions, selectedRun, onSelectRun, className }) => (
|
}> = ({ flows, executions, selectedRun, onSelectRun, className }) => (
|
||||||
<Card className={className}>
|
<Card className={className}>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { GraphExecution, LibraryAgent } from "@/lib/autogpt-server-api";
|
import { GraphExecutionMeta, LibraryAgent } from "@/lib/autogpt-server-api";
|
||||||
import { CardTitle } from "@/components/ui/card";
|
import { CardTitle } from "@/components/ui/card";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
@@ -12,7 +12,7 @@ import { FlowRunsTimeline } from "@/components/monitor/FlowRunsTimeline";
|
|||||||
|
|
||||||
export const FlowRunsStatus: React.FC<{
|
export const FlowRunsStatus: React.FC<{
|
||||||
flows: LibraryAgent[];
|
flows: LibraryAgent[];
|
||||||
executions: GraphExecution[];
|
executions: GraphExecutionMeta[];
|
||||||
title?: string;
|
title?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
}> = ({ flows, executions: executions, title, className }) => {
|
}> = ({ flows, executions: executions, title, className }) => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { GraphExecution, LibraryAgent } from "@/lib/autogpt-server-api";
|
import { GraphExecutionMeta, LibraryAgent } from "@/lib/autogpt-server-api";
|
||||||
import {
|
import {
|
||||||
ComposedChart,
|
ComposedChart,
|
||||||
DefaultLegendContentProps,
|
DefaultLegendContentProps,
|
||||||
@@ -23,7 +23,7 @@ export const FlowRunsTimeline = ({
|
|||||||
className,
|
className,
|
||||||
}: {
|
}: {
|
||||||
flows: LibraryAgent[];
|
flows: LibraryAgent[];
|
||||||
executions: GraphExecution[];
|
executions: GraphExecutionMeta[];
|
||||||
dataMin: "dataMin" | number;
|
dataMin: "dataMin" | number;
|
||||||
className?: string;
|
className?: string;
|
||||||
}) => (
|
}) => (
|
||||||
@@ -60,8 +60,10 @@ export const FlowRunsTimeline = ({
|
|||||||
<Tooltip
|
<Tooltip
|
||||||
content={({ payload, label }) => {
|
content={({ payload, label }) => {
|
||||||
if (payload && payload.length) {
|
if (payload && payload.length) {
|
||||||
const data: GraphExecution & { time: number; _duration: number } =
|
const data: GraphExecutionMeta & {
|
||||||
payload[0].payload;
|
time: number;
|
||||||
|
_duration: number;
|
||||||
|
} = payload[0].payload;
|
||||||
const flow = flows.find((f) => f.agent_id === data.graph_id);
|
const flow = flows.find((f) => f.agent_id === data.graph_id);
|
||||||
return (
|
return (
|
||||||
<Card className="p-2 text-xs leading-normal">
|
<Card className="p-2 text-xs leading-normal">
|
||||||
|
|||||||
@@ -242,7 +242,7 @@ export const SchedulesTable = ({
|
|||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>{schedule.graph_version}</TableCell>
|
<TableCell>{schedule.graph_version}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{new Date(schedule.next_run_time).toLocaleString()}
|
{schedule.next_run_time.toLocaleString()}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Badge variant="secondary">
|
<Badge variant="secondary">
|
||||||
|
|||||||
@@ -8,10 +8,7 @@ const Card = React.forwardRef<
|
|||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn("agpt-card text-neutral-950", className)}
|
||||||
"rounded-xl border border-gray-300 bg-white text-neutral-950 shadow",
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -606,8 +606,11 @@ export default function useAgentGraph(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const fetchExecutions = async () => {
|
const fetchExecutions = async () => {
|
||||||
const results = await api.getGraphExecutionInfo(flowID, flowExecutionID);
|
const execution = await api.getGraphExecutionInfo(
|
||||||
setUpdateQueue((prev) => [...prev, ...results]);
|
flowID,
|
||||||
|
flowExecutionID,
|
||||||
|
);
|
||||||
|
setUpdateQueue((prev) => [...prev, ...execution.node_executions]);
|
||||||
|
|
||||||
// Track execution until completed
|
// Track execution until completed
|
||||||
const pendingNodeExecutions: Set<string> = new Set();
|
const pendingNodeExecutions: Set<string> = new Set();
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
CredentialsDeleteResponse,
|
CredentialsDeleteResponse,
|
||||||
CredentialsMetaResponse,
|
CredentialsMetaResponse,
|
||||||
GraphExecution,
|
GraphExecution,
|
||||||
|
GraphExecutionMeta,
|
||||||
Graph,
|
Graph,
|
||||||
GraphCreatable,
|
GraphCreatable,
|
||||||
GraphMeta,
|
GraphMeta,
|
||||||
@@ -161,10 +162,6 @@ export default class BackendAPI {
|
|||||||
return this._get(`/graphs`);
|
return this._get(`/graphs`);
|
||||||
}
|
}
|
||||||
|
|
||||||
getExecutions(): Promise<GraphExecution[]> {
|
|
||||||
return this._get(`/executions`);
|
|
||||||
}
|
|
||||||
|
|
||||||
getGraph(
|
getGraph(
|
||||||
id: string,
|
id: string,
|
||||||
version?: number,
|
version?: number,
|
||||||
@@ -212,22 +209,37 @@ export default class BackendAPI {
|
|||||||
return this._request("POST", `/graphs/${id}/execute/${version}`, inputData);
|
return this._request("POST", `/graphs/${id}/execute/${version}`, inputData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getExecutions(): Promise<GraphExecutionMeta[]> {
|
||||||
|
return this._get(`/executions`);
|
||||||
|
}
|
||||||
|
|
||||||
|
getGraphExecutions(graphID: string): Promise<GraphExecutionMeta[]> {
|
||||||
|
return this._get(`/graphs/${graphID}/executions`);
|
||||||
|
}
|
||||||
|
|
||||||
async getGraphExecutionInfo(
|
async getGraphExecutionInfo(
|
||||||
graphID: string,
|
graphID: string,
|
||||||
runID: string,
|
runID: string,
|
||||||
): Promise<NodeExecutionResult[]> {
|
): Promise<GraphExecution> {
|
||||||
return (await this._get(`/graphs/${graphID}/executions/${runID}`)).map(
|
const result = await this._get(`/graphs/${graphID}/executions/${runID}`);
|
||||||
|
result.node_executions = result.node_executions.map(
|
||||||
parseNodeExecutionResultTimestamps,
|
parseNodeExecutionResultTimestamps,
|
||||||
);
|
);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async stopGraphExecution(
|
async stopGraphExecution(
|
||||||
graphID: string,
|
graphID: string,
|
||||||
runID: string,
|
runID: string,
|
||||||
): Promise<NodeExecutionResult[]> {
|
): Promise<GraphExecution> {
|
||||||
return (
|
const result = await this._request(
|
||||||
await this._request("POST", `/graphs/${graphID}/executions/${runID}/stop`)
|
"POST",
|
||||||
).map(parseNodeExecutionResultTimestamps);
|
`/graphs/${graphID}/executions/${runID}/stop`,
|
||||||
|
);
|
||||||
|
result.node_executions = result.node_executions.map(
|
||||||
|
parseNodeExecutionResultTimestamps,
|
||||||
|
);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
oAuthLogin(
|
oAuthLogin(
|
||||||
@@ -484,15 +496,19 @@ export default class BackendAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async createSchedule(schedule: ScheduleCreatable): Promise<Schedule> {
|
async createSchedule(schedule: ScheduleCreatable): Promise<Schedule> {
|
||||||
return this._request("POST", `/schedules`, schedule);
|
return this._request("POST", `/schedules`, schedule).then(
|
||||||
|
parseScheduleTimestamp,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteSchedule(scheduleId: string): Promise<Schedule> {
|
async deleteSchedule(scheduleId: string): Promise<{ id: string }> {
|
||||||
return this._request("DELETE", `/schedules/${scheduleId}`);
|
return this._request("DELETE", `/schedules/${scheduleId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async listSchedules(): Promise<Schedule[]> {
|
async listSchedules(): Promise<Schedule[]> {
|
||||||
return this._get(`/schedules`);
|
return this._get(`/schedules`).then((schedules) =>
|
||||||
|
schedules.map(parseScheduleTimestamp),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _uploadFile(path: string, file: File): Promise<string> {
|
private async _uploadFile(path: string, file: File): Promise<string> {
|
||||||
@@ -824,3 +840,10 @@ function parseNodeExecutionResultTimestamps(result: any): NodeExecutionResult {
|
|||||||
end_time: result.end_time ? new Date(result.end_time) : undefined,
|
end_time: result.end_time ? new Date(result.end_time) : undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseScheduleTimestamp(result: any): Schedule {
|
||||||
|
return {
|
||||||
|
...result,
|
||||||
|
next_run_time: new Date(result.next_run_time),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ export type BlockIOSubSchema =
|
|||||||
| BlockIOSimpleTypeSubSchema
|
| BlockIOSimpleTypeSubSchema
|
||||||
| BlockIOCombinedTypeSubSchema;
|
| BlockIOCombinedTypeSubSchema;
|
||||||
|
|
||||||
|
export type BlockIOSubType = BlockIOSimpleTypeSubSchema["type"];
|
||||||
|
|
||||||
export type BlockIOSimpleTypeSubSchema =
|
export type BlockIOSimpleTypeSubSchema =
|
||||||
| BlockIOObjectSubSchema
|
| BlockIOObjectSubSchema
|
||||||
| BlockIOCredentialsSubSchema
|
| BlockIOCredentialsSubSchema
|
||||||
@@ -152,8 +154,7 @@ export const PROVIDER_NAMES = {
|
|||||||
export type CredentialsProviderName =
|
export type CredentialsProviderName =
|
||||||
(typeof PROVIDER_NAMES)[keyof typeof PROVIDER_NAMES];
|
(typeof PROVIDER_NAMES)[keyof typeof PROVIDER_NAMES];
|
||||||
|
|
||||||
export type BlockIOCredentialsSubSchema = BlockIOSubSchemaMeta & {
|
export type BlockIOCredentialsSubSchema = BlockIOObjectSubSchema & {
|
||||||
type: "object";
|
|
||||||
/* Mirror of backend/data/model.py:CredentialsFieldSchemaExtra */
|
/* Mirror of backend/data/model.py:CredentialsFieldSchemaExtra */
|
||||||
credentials_provider: CredentialsProviderName[];
|
credentials_provider: CredentialsProviderName[];
|
||||||
credentials_scopes?: string[];
|
credentials_scopes?: string[];
|
||||||
@@ -170,21 +171,17 @@ export type BlockIONullSubSchema = BlockIOSubSchemaMeta & {
|
|||||||
|
|
||||||
// At the time of writing, combined schemas only occur on the first nested level in a
|
// At the time of writing, combined schemas only occur on the first nested level in a
|
||||||
// block schema. It is typed this way to make the use of these objects less tedious.
|
// block schema. It is typed this way to make the use of these objects less tedious.
|
||||||
type BlockIOCombinedTypeSubSchema = BlockIOSubSchemaMeta &
|
type BlockIOCombinedTypeSubSchema = BlockIOSubSchemaMeta & { type: never } & (
|
||||||
(
|
|
||||||
| {
|
| {
|
||||||
type: "allOf";
|
|
||||||
allOf: [BlockIOSimpleTypeSubSchema];
|
allOf: [BlockIOSimpleTypeSubSchema];
|
||||||
secret?: boolean;
|
secret?: boolean;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: "anyOf";
|
|
||||||
anyOf: BlockIOSimpleTypeSubSchema[];
|
anyOf: BlockIOSimpleTypeSubSchema[];
|
||||||
default?: string | number | boolean | null;
|
default?: string | number | boolean | null;
|
||||||
secret?: boolean;
|
secret?: boolean;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: "oneOf";
|
|
||||||
oneOf: BlockIOSimpleTypeSubSchema[];
|
oneOf: BlockIOSimpleTypeSubSchema[];
|
||||||
default?: string | number | boolean | null;
|
default?: string | number | boolean | null;
|
||||||
secret?: boolean;
|
secret?: boolean;
|
||||||
@@ -219,8 +216,8 @@ export type LinkCreatable = Omit<Link, "id" | "is_static"> & {
|
|||||||
id?: string;
|
id?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Mirror of backend/data/graph.py:GraphExecution */
|
/* Mirror of backend/data/graph.py:GraphExecutionMeta */
|
||||||
export type GraphExecution = {
|
export type GraphExecutionMeta = {
|
||||||
execution_id: string;
|
execution_id: string;
|
||||||
started_at: number;
|
started_at: number;
|
||||||
ended_at: number;
|
ended_at: number;
|
||||||
@@ -229,6 +226,14 @@ export type GraphExecution = {
|
|||||||
status: "QUEUED" | "RUNNING" | "COMPLETED" | "TERMINATED" | "FAILED";
|
status: "QUEUED" | "RUNNING" | "COMPLETED" | "TERMINATED" | "FAILED";
|
||||||
graph_id: GraphID;
|
graph_id: GraphID;
|
||||||
graph_version: number;
|
graph_version: number;
|
||||||
|
preset_id?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Mirror of backend/data/graph.py:GraphExecution */
|
||||||
|
export type GraphExecution = GraphExecutionMeta & {
|
||||||
|
inputs: Record<string, any>;
|
||||||
|
outputs: Record<string, Array<any>>;
|
||||||
|
node_executions: NodeExecutionResult[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GraphMeta = {
|
export type GraphMeta = {
|
||||||
@@ -237,12 +242,27 @@ export type GraphMeta = {
|
|||||||
is_active: boolean;
|
is_active: boolean;
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
input_schema: BlockIOObjectSubSchema;
|
input_schema: GraphIOSchema;
|
||||||
output_schema: BlockIOObjectSubSchema;
|
output_schema: GraphIOSchema;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GraphID = Brand<string, "GraphID">;
|
export type GraphID = Brand<string, "GraphID">;
|
||||||
|
|
||||||
|
/* Derived from backend/data/graph.py:Graph._generate_schema() */
|
||||||
|
export type GraphIOSchema = {
|
||||||
|
type: "object";
|
||||||
|
properties: { [key: string]: GraphIOSubSchema };
|
||||||
|
required: (keyof BlockIORootSchema["properties"])[];
|
||||||
|
};
|
||||||
|
export type GraphIOSubSchema = Omit<
|
||||||
|
BlockIOSubSchemaMeta,
|
||||||
|
"placeholder" | "depends_on" | "hidden"
|
||||||
|
> & {
|
||||||
|
type: never; // bodge to avoid type checking hell; doesn't exist at runtime
|
||||||
|
default?: string;
|
||||||
|
secret: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
/* Mirror of backend/data/graph.py:Graph */
|
/* Mirror of backend/data/graph.py:Graph */
|
||||||
export type Graph = GraphMeta & {
|
export type Graph = GraphMeta & {
|
||||||
nodes: Array<Node>;
|
nodes: Array<Node>;
|
||||||
@@ -256,8 +276,8 @@ export type GraphUpdateable = Omit<
|
|||||||
version?: number;
|
version?: number;
|
||||||
is_active?: boolean;
|
is_active?: boolean;
|
||||||
links: Array<LinkCreatable>;
|
links: Array<LinkCreatable>;
|
||||||
input_schema?: BlockIOObjectSubSchema;
|
input_schema?: GraphIOSchema;
|
||||||
output_schema?: BlockIOObjectSubSchema;
|
output_schema?: GraphIOSchema;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GraphCreatable = Omit<GraphUpdateable, "id"> & { id?: string };
|
export type GraphCreatable = Omit<GraphUpdateable, "id"> & { id?: string };
|
||||||
@@ -553,7 +573,7 @@ export type Schedule = {
|
|||||||
graph_id: GraphID;
|
graph_id: GraphID;
|
||||||
graph_version: number;
|
graph_version: number;
|
||||||
input_data: { [key: string]: any };
|
input_data: { [key: string]: any };
|
||||||
next_run_time: string;
|
next_run_time: Date;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ScheduleCreatable = {
|
export type ScheduleCreatable = {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ const config = {
|
|||||||
mono: ["var(--font-geist-mono)"],
|
mono: ["var(--font-geist-mono)"],
|
||||||
// Include the custom font family
|
// Include the custom font family
|
||||||
neue: ['"PP Neue Montreal TT"', "sans-serif"],
|
neue: ['"PP Neue Montreal TT"', "sans-serif"],
|
||||||
poppin: ["var(--font-poppins)"],
|
poppins: ["var(--font-poppins)"],
|
||||||
inter: ["var(--font-inter)"],
|
inter: ["var(--font-inter)"],
|
||||||
},
|
},
|
||||||
colors: {
|
colors: {
|
||||||
@@ -95,26 +95,20 @@ const config = {
|
|||||||
28: "7rem",
|
28: "7rem",
|
||||||
32: "8rem",
|
32: "8rem",
|
||||||
36: "9rem",
|
36: "9rem",
|
||||||
39: "9.875rem",
|
|
||||||
40: "10rem",
|
40: "10rem",
|
||||||
44: "11rem",
|
44: "11rem",
|
||||||
48: "12rem",
|
48: "12rem",
|
||||||
52: "13rem",
|
52: "13rem",
|
||||||
56: "14rem",
|
56: "14rem",
|
||||||
69: "14.875rem",
|
|
||||||
60: "15rem",
|
60: "15rem",
|
||||||
63: "15.875rem",
|
|
||||||
64: "16rem",
|
64: "16rem",
|
||||||
68: "17.75rem",
|
68: "17rem",
|
||||||
|
70: "17.5rem",
|
||||||
|
71: "17.75rem",
|
||||||
72: "18rem",
|
72: "18rem",
|
||||||
77: "18.5625rem",
|
76: "19rem",
|
||||||
80: "20rem",
|
80: "20rem",
|
||||||
89: "22.5rem",
|
|
||||||
96: "24rem",
|
96: "24rem",
|
||||||
110: "27.5rem",
|
|
||||||
139: "37.1875rem",
|
|
||||||
167: "41.6875rem",
|
|
||||||
225: "56.25rem",
|
|
||||||
},
|
},
|
||||||
borderRadius: {
|
borderRadius: {
|
||||||
lg: "var(--radius)",
|
lg: "var(--radius)",
|
||||||
|
|||||||
Reference in New Issue
Block a user