mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-01-08 22:58:01 -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).)*[/.]
|
||||
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
|
||||
# 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.
|
||||
@@ -221,6 +231,16 @@ repos:
|
||||
language: system
|
||||
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
|
||||
hooks:
|
||||
- id: pytest
|
||||
|
||||
@@ -13,7 +13,6 @@ from typing_extensions import ParamSpec
|
||||
from .config import SETTINGS
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
P = ParamSpec("P")
|
||||
T = TypeVar("T")
|
||||
|
||||
@@ -23,12 +23,15 @@ from backend.util import type
|
||||
|
||||
from .block import BlockInput, BlockType, get_block, get_blocks
|
||||
from .db import BaseDbModel, transaction
|
||||
from .execution import ExecutionStatus
|
||||
from .execution import ExecutionResult, ExecutionStatus
|
||||
from .includes import AGENT_GRAPH_INCLUDE, AGENT_NODE_INCLUDE
|
||||
from .integrations import Webhook
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_INPUT_BLOCK_ID = AgentInputBlock().id
|
||||
_OUTPUT_BLOCK_ID = AgentOutputBlock().id
|
||||
|
||||
|
||||
class Link(BaseDbModel):
|
||||
source_id: str
|
||||
@@ -105,7 +108,7 @@ class NodeModel(Node):
|
||||
Webhook.model_rebuild()
|
||||
|
||||
|
||||
class GraphExecution(BaseDbModel):
|
||||
class GraphExecutionMeta(BaseDbModel):
|
||||
execution_id: str
|
||||
started_at: datetime
|
||||
ended_at: datetime
|
||||
@@ -114,33 +117,83 @@ class GraphExecution(BaseDbModel):
|
||||
status: ExecutionStatus
|
||||
graph_id: str
|
||||
graph_version: int
|
||||
preset_id: Optional[str]
|
||||
|
||||
@staticmethod
|
||||
def from_db(execution: AgentGraphExecution):
|
||||
def from_db(_graph_exec: AgentGraphExecution):
|
||||
now = datetime.now(timezone.utc)
|
||||
start_time = execution.startedAt or execution.createdAt
|
||||
end_time = execution.updatedAt or now
|
||||
start_time = _graph_exec.startedAt or _graph_exec.createdAt
|
||||
end_time = _graph_exec.updatedAt or now
|
||||
duration = (end_time - start_time).total_seconds()
|
||||
total_run_time = duration
|
||||
|
||||
try:
|
||||
stats = type.convert(execution.stats or {}, dict[str, Any])
|
||||
stats = type.convert(_graph_exec.stats or {}, dict[str, Any])
|
||||
except ValueError:
|
||||
stats = {}
|
||||
|
||||
duration = stats.get("walltime", duration)
|
||||
total_run_time = stats.get("nodes_walltime", total_run_time)
|
||||
|
||||
return GraphExecution(
|
||||
id=execution.id,
|
||||
execution_id=execution.id,
|
||||
return GraphExecutionMeta(
|
||||
id=_graph_exec.id,
|
||||
execution_id=_graph_exec.id,
|
||||
started_at=start_time,
|
||||
ended_at=end_time,
|
||||
duration=duration,
|
||||
total_run_time=total_run_time,
|
||||
status=ExecutionStatus(execution.executionStatus),
|
||||
graph_id=execution.agentGraphId,
|
||||
graph_version=execution.agentGraphVersion,
|
||||
status=ExecutionStatus(_graph_exec.executionStatus),
|
||||
graph_id=_graph_exec.agentGraphId,
|
||||
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
|
||||
|
||||
|
||||
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(
|
||||
where={"userId": user_id},
|
||||
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:
|
||||
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
|
||||
|
||||
@@ -629,7 +710,7 @@ async def create_graph(graph: Graph, user_id: str) -> GraphModel:
|
||||
await __create_graph(tx, graph, user_id)
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -155,7 +155,7 @@ class AgentServer(backend.util.service.AppProcess):
|
||||
|
||||
@staticmethod
|
||||
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
|
||||
)
|
||||
if not execution:
|
||||
@@ -163,10 +163,10 @@ class AgentServer(backend.util.service.AppProcess):
|
||||
return execution.status
|
||||
|
||||
@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
|
||||
):
|
||||
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
|
||||
)
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ import backend.data.block
|
||||
import backend.server.integrations.router
|
||||
import backend.server.routers.analytics
|
||||
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.api_key import (
|
||||
APIKeyError,
|
||||
@@ -546,7 +545,7 @@ async def set_graph_active_version(
|
||||
)
|
||||
def execute_graph(
|
||||
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)],
|
||||
graph_version: Optional[int] = None,
|
||||
) -> ExecuteGraphResponse:
|
||||
@@ -567,8 +566,10 @@ def execute_graph(
|
||||
)
|
||||
async def stop_graph_run(
|
||||
graph_exec_id: str, user_id: Annotated[str, Depends(get_user_id)]
|
||||
) -> Sequence[execution_db.ExecutionResult]:
|
||||
if not await graph_db.get_execution(user_id=user_id, execution_id=graph_exec_id):
|
||||
) -> graph_db.GraphExecution:
|
||||
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")
|
||||
|
||||
await asyncio.to_thread(
|
||||
@@ -576,7 +577,13 @@ async def stop_graph_run(
|
||||
)
|
||||
|
||||
# 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(
|
||||
@@ -584,10 +591,22 @@ async def stop_graph_run(
|
||||
tags=["graphs"],
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
)
|
||||
async def get_executions(
|
||||
async def get_graphs_executions(
|
||||
user_id: Annotated[str, Depends(get_user_id)],
|
||||
) -> list[graph_db.GraphExecution]:
|
||||
return await graph_db.get_executions(user_id=user_id)
|
||||
) -> list[graph_db.GraphExecutionMeta]:
|
||||
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(
|
||||
@@ -595,16 +614,20 @@ async def get_executions(
|
||||
tags=["graphs"],
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
)
|
||||
async def get_graph_run_node_execution_results(
|
||||
async def get_graph_execution(
|
||||
graph_id: str,
|
||||
graph_exec_id: str,
|
||||
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)
|
||||
if not graph:
|
||||
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,
|
||||
)
|
||||
return submission
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
logger.error(f"Could not create store submission review: {e}")
|
||||
raise fastapi.HTTPException(
|
||||
status_code=500,
|
||||
detail="An error occurred while creating the store submission review",
|
||||
|
||||
@@ -78,9 +78,10 @@ async def wait_execution(
|
||||
# Wait for the executions to complete
|
||||
for i in range(timeout):
|
||||
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
|
||||
)
|
||||
return graph_exec.node_executions
|
||||
time.sleep(1)
|
||||
|
||||
assert False, "Execution did not complete in time."
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
import logging
|
||||
import os
|
||||
|
||||
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
|
||||
# Set up logging
|
||||
configure_logging()
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Create console handler with formatting
|
||||
ch = logging.StreamHandler()
|
||||
ch.setLevel(logging.INFO)
|
||||
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
||||
ch.setFormatter(formatter)
|
||||
logger.addHandler(ch)
|
||||
# Reduce Prisma log spam unless PRISMA_DEBUG is set
|
||||
if not os.getenv("PRISMA_DEBUG"):
|
||||
prisma_logger = logging.getLogger("prisma")
|
||||
prisma_logger.setLevel(logging.INFO)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
async def server():
|
||||
from backend.util.test import SpinTestServer
|
||||
|
||||
async with SpinTestServer() as server:
|
||||
yield server
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ async def assert_sample_graph_executions(
|
||||
graph_exec_id: str,
|
||||
):
|
||||
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,
|
||||
graph_exec_id,
|
||||
test_user.id,
|
||||
@@ -77,7 +77,7 @@ async def assert_sample_graph_executions(
|
||||
]
|
||||
|
||||
# Executing StoreValueBlock
|
||||
exec = executions[0]
|
||||
exec = graph_run.node_executions[0]
|
||||
logger.info(f"Checking first StoreValueBlock execution: {exec}")
|
||||
assert exec.status == execution.ExecutionStatus.COMPLETED
|
||||
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]
|
||||
|
||||
# Executing StoreValueBlock
|
||||
exec = executions[1]
|
||||
exec = graph_run.node_executions[1]
|
||||
logger.info(f"Checking second StoreValueBlock execution: {exec}")
|
||||
assert exec.status == execution.ExecutionStatus.COMPLETED
|
||||
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]
|
||||
|
||||
# Executing FillTextTemplateBlock
|
||||
exec = executions[2]
|
||||
exec = graph_run.node_executions[2]
|
||||
logger.info(f"Checking FillTextTemplateBlock execution: {exec}")
|
||||
assert exec.status == execution.ExecutionStatus.COMPLETED
|
||||
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
|
||||
|
||||
# Executing PrintToConsoleBlock
|
||||
exec = executions[3]
|
||||
exec = graph_run.node_executions[3]
|
||||
logger.info(f"Checking PrintToConsoleBlock execution: {exec}")
|
||||
assert exec.status == execution.ExecutionStatus.COMPLETED
|
||||
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")
|
||||
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
|
||||
)
|
||||
assert len(executions) == 3
|
||||
assert len(graph_exec.node_executions) == 3
|
||||
# FindInDictionaryBlock should wait for the input pin to be provided,
|
||||
# Hence executing extraction of "key" from {"key1": "value1", "key2": "value2"}
|
||||
assert executions[2].status == execution.ExecutionStatus.COMPLETED
|
||||
assert executions[2].output_data == {"output": ["value2"]}
|
||||
assert graph_exec.node_executions[2].status == execution.ExecutionStatus.COMPLETED
|
||||
assert graph_exec.node_executions[2].output_data == {"output": ["value2"]}
|
||||
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
|
||||
)
|
||||
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
|
||||
)
|
||||
assert len(executions) == 8
|
||||
assert len(graph_exec.node_executions) == 8
|
||||
# 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}")
|
||||
assert exec_data.status == execution.ExecutionStatus.COMPLETED
|
||||
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 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 {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--background: 0 0% 99.6%; /* #FEFEFE */
|
||||
--foreground: 240 10% 3.9%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 240 10% 3.9%;
|
||||
@@ -61,8 +16,8 @@
|
||||
--secondary-foreground: 240 5.9% 10%;
|
||||
--muted: 240 4.8% 95.9%;
|
||||
--muted-foreground: 240 3.8% 46.1%;
|
||||
--accent: 240 4.8% 95.9%;
|
||||
--accent-foreground: 240 5.9% 10%;
|
||||
--accent: 262 83% 58%;
|
||||
--accent-foreground: 0 0% 100%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 240 5.9% 90%;
|
||||
@@ -102,9 +57,7 @@
|
||||
--chart-4: 280 65% 60%;
|
||||
--chart-5: 340 75% 55%;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
@@ -112,6 +65,14 @@
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
|
||||
.font-neue {
|
||||
font-family: "PP Neue Montreal TT", sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
/* *** AutoGPT Design Components *** */
|
||||
|
||||
@layer components {
|
||||
.agpt-border-input {
|
||||
@apply border border-input focus-visible:border-gray-400 focus-visible:outline-none;
|
||||
}
|
||||
@@ -119,4 +80,67 @@
|
||||
.agpt-shadow-input {
|
||||
@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 {
|
||||
GraphExecution,
|
||||
GraphExecutionMeta,
|
||||
Schedule,
|
||||
LibraryAgent,
|
||||
} from "@/lib/autogpt-server-api";
|
||||
@@ -20,10 +20,12 @@ import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
||||
|
||||
const Monitor = () => {
|
||||
const [flows, setFlows] = useState<LibraryAgent[]>([]);
|
||||
const [executions, setExecutions] = useState<GraphExecution[]>([]);
|
||||
const [executions, setExecutions] = useState<GraphExecutionMeta[]>([]);
|
||||
const [schedules, setSchedules] = useState<Schedule[]>([]);
|
||||
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 [sortDirection, setSortDirection] = useState<"asc" | "desc">("asc");
|
||||
const api = useBackendAPI();
|
||||
|
||||
@@ -260,7 +260,6 @@ export default function CreditsPage() {
|
||||
</p>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="default"
|
||||
className="w-full"
|
||||
onClick={() => openBillingPortal()}
|
||||
>
|
||||
|
||||
@@ -84,8 +84,6 @@ export default function Page({}: {}) {
|
||||
<PublishAgentPopout
|
||||
trigger={
|
||||
<Button
|
||||
variant="default"
|
||||
size="sm"
|
||||
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"
|
||||
>
|
||||
|
||||
@@ -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 && (
|
||||
<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
|
||||
variant="default"
|
||||
size="default"
|
||||
onClick={() => {
|
||||
if (videoRef.current) {
|
||||
|
||||
@@ -95,7 +95,7 @@ export const AgentInfo: React.FC<AgentInfoProps> = ({
|
||||
return (
|
||||
<div className="w-full max-w-[396px] px-4 sm:px-6 lg:w-[396px] lg:px-0">
|
||||
{/* 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}
|
||||
</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" />
|
||||
|
||||
{/* 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}
|
||||
</h2>
|
||||
|
||||
{/* 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">
|
||||
<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
|
||||
<br />
|
||||
<span className="text-violet-600 dark:text-violet-400">
|
||||
@@ -51,7 +51,7 @@ export const BecomeACreator: React.FC<BecomeACreatorProps> = ({
|
||||
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"
|
||||
>
|
||||
<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}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
@@ -65,15 +65,12 @@ export const Interactive: Story = {
|
||||
export const Variants: Story = {
|
||||
render: (args) => (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button {...args} variant="default">
|
||||
Default
|
||||
<Button {...args} variant="outline">
|
||||
Outline (default)
|
||||
</Button>
|
||||
<Button {...args} variant="destructive">
|
||||
Destructive
|
||||
</Button>
|
||||
<Button {...args} variant="outline">
|
||||
Outline
|
||||
</Button>
|
||||
<Button {...args} variant="secondary">
|
||||
Secondary
|
||||
</Button>
|
||||
|
||||
@@ -5,16 +5,15 @@ import { cva, type VariantProps } from "class-variance-authority";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
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: {
|
||||
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:
|
||||
"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:
|
||||
"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:
|
||||
"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:
|
||||
@@ -22,17 +21,17 @@ const buttonVariants = cva(
|
||||
link: "text-[#272727] underline-offset-4 hover:underline dark:text-neutral-100",
|
||||
},
|
||||
size: {
|
||||
default:
|
||||
"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 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 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",
|
||||
default: "h-10 px-4 py-2 rounded-full text-sm",
|
||||
sm: "h-8 px-3 py-1.5 rounded-full text-xs",
|
||||
lg: "h-12 px-5 py-2.5 rounded-full text-lg",
|
||||
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]",
|
||||
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]",
|
||||
"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",
|
||||
card: "h-12 p-5 agpt-rounded-card justify-center text-lg",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
variant: "outline",
|
||||
size: "default",
|
||||
},
|
||||
},
|
||||
@@ -43,13 +42,13 @@ export interface ButtonProps
|
||||
VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean;
|
||||
variant?:
|
||||
| "default"
|
||||
| "destructive"
|
||||
| "accent"
|
||||
| "outline"
|
||||
| "secondary"
|
||||
| "ghost"
|
||||
| "link";
|
||||
size?: "default" | "sm" | "lg" | "primary" | "icon";
|
||||
size?: "default" | "sm" | "lg" | "primary" | "icon" | "card";
|
||||
}
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
|
||||
@@ -33,7 +33,7 @@ export const CreatorInfoCard: React.FC<CreatorInfoCardProps> = ({
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<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}
|
||||
</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">
|
||||
|
||||
@@ -72,7 +72,6 @@ export const Navbar = async ({ links, menuItemGroups }: NavbarProps) => {
|
||||
) : (
|
||||
<Link href="/login">
|
||||
<Button
|
||||
variant="default"
|
||||
size="sm"
|
||||
className="flex items-center justify-end space-x-2"
|
||||
>
|
||||
@@ -119,11 +118,7 @@ export const Navbar = async ({ links, menuItemGroups }: NavbarProps) => {
|
||||
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"
|
||||
>
|
||||
<Button
|
||||
variant="default"
|
||||
size="sm"
|
||||
className="flex items-center space-x-2"
|
||||
>
|
||||
<Button size="sm" className="flex items-center space-x-2">
|
||||
<IconLogIn className="h-5 w-5" />
|
||||
<span>Log In</span>
|
||||
</Button>
|
||||
|
||||
@@ -53,7 +53,7 @@ export const NavbarLink = ({ name, href }: NavbarLinkProps) => {
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className={`font-poppins text-[20px] font-medium leading-[28px] ${
|
||||
className={`hidden font-poppins text-[20px] font-medium leading-[28px] lg:block ${
|
||||
activeLink === href
|
||||
? "text-neutral-50 dark:text-neutral-900"
|
||||
: "text-neutral-900 dark:text-neutral-50"
|
||||
|
||||
@@ -256,7 +256,6 @@ export const ProfileInfoForm = ({ profile }: { profile: CreatorDetails }) => {
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="default"
|
||||
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"
|
||||
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">
|
||||
<Button
|
||||
onClick={onDone}
|
||||
variant="outline"
|
||||
className="h-12 w-full rounded-[59px] sm:flex-1"
|
||||
>
|
||||
Done
|
||||
</Button>
|
||||
<Button
|
||||
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"
|
||||
>
|
||||
View progress
|
||||
|
||||
@@ -84,7 +84,6 @@ export const PublishAgentSelect: React.FC<PublishAgentSelectProps> = ({
|
||||
</div>
|
||||
<Button
|
||||
onClick={onOpenBuilder}
|
||||
variant="default"
|
||||
size="lg"
|
||||
className="bg-neutral-800 text-white hover:bg-neutral-900"
|
||||
>
|
||||
@@ -150,12 +149,7 @@ export const PublishAgentSelect: React.FC<PublishAgentSelectProps> = ({
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between gap-4 border-t border-slate-200 p-4 dark:border-slate-700 sm:p-6">
|
||||
<Button
|
||||
onClick={onCancel}
|
||||
variant="outline"
|
||||
size="default"
|
||||
className="w-full sm:flex-1"
|
||||
>
|
||||
<Button onClick={onCancel} size="lg" className="w-full sm:flex-1">
|
||||
Back
|
||||
</Button>
|
||||
<Button
|
||||
@@ -165,8 +159,7 @@ export const PublishAgentSelect: React.FC<PublishAgentSelectProps> = ({
|
||||
}
|
||||
}}
|
||||
disabled={!selectedAgentId || !selectedAgentVersion}
|
||||
variant="default"
|
||||
size="default"
|
||||
size="lg"
|
||||
className="w-full bg-neutral-800 text-white hover:bg-neutral-900 sm:flex-1"
|
||||
>
|
||||
Next
|
||||
|
||||
@@ -344,8 +344,6 @@ export const PublishAgentInfo: React.FC<PublishAgentInfoProps> = ({
|
||||
You can use AI to generate a cover image for you
|
||||
</p>
|
||||
<Button
|
||||
variant="default"
|
||||
size="sm"
|
||||
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" : ""
|
||||
}`}
|
||||
@@ -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">
|
||||
<Button
|
||||
onClick={onBack}
|
||||
variant="outline"
|
||||
size="default"
|
||||
size="lg"
|
||||
className="w-full dark:border-slate-700 dark:text-slate-300 sm:flex-1"
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
variant="default"
|
||||
size="default"
|
||||
size="lg"
|
||||
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
|
||||
|
||||
@@ -71,7 +71,7 @@ export const StoreCard: React.FC<StoreCardProps> = ({
|
||||
{/* Content Section */}
|
||||
<div className="w-full px-2 py-4">
|
||||
{/* 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}
|
||||
</h3>
|
||||
{!hideAvatar && creatorName && (
|
||||
|
||||
@@ -47,7 +47,7 @@ export const AgentsSection: React.FC<AgentsSectionProps> = ({
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center py-4 lg:py-8">
|
||||
<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}
|
||||
</div>
|
||||
{!displayedAgents || displayedAgents.length === 0 ? (
|
||||
@@ -65,7 +65,7 @@ export const AgentsSection: React.FC<AgentsSectionProps> = ({
|
||||
>
|
||||
<CarouselContent>
|
||||
{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
|
||||
agentName={agent.agent_name}
|
||||
agentImage={agent.agent_image}
|
||||
|
||||
@@ -33,7 +33,7 @@ export const FeaturedCreators: React.FC<FeaturedCreatorsProps> = ({
|
||||
return (
|
||||
<div className="flex w-full flex-col items-center justify-center py-16">
|
||||
<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}
|
||||
</h2>
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ export const FeaturedSection: React.FC<FeaturedSectionProps> = ({
|
||||
return (
|
||||
<div className="flex w-full flex-col items-center justify-center">
|
||||
<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
|
||||
</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="mb-4 text-center md:mb-8">
|
||||
<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{" "}
|
||||
</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
|
||||
</span>
|
||||
<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{" "}
|
||||
</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
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
@@ -284,7 +284,7 @@ export const PublishAgentPopout: React.FC<PublishAgentPopoutProps> = ({
|
||||
}}
|
||||
>
|
||||
<PopoverTrigger asChild>
|
||||
{trigger || <Button variant="default">Publish Agent</Button>}
|
||||
{trigger || <Button>Publish Agent</Button>}
|
||||
</PopoverTrigger>
|
||||
<PopoverAnchor asChild>
|
||||
<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 { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -37,7 +37,7 @@ export const AgentFlowList = ({
|
||||
className,
|
||||
}: {
|
||||
flows: LibraryAgent[];
|
||||
executions?: GraphExecution[];
|
||||
executions?: GraphExecutionMeta[];
|
||||
selectedFlow: LibraryAgent | null;
|
||||
onSelectFlow: (f: LibraryAgent) => void;
|
||||
className?: string;
|
||||
@@ -106,7 +106,7 @@ export const AgentFlowList = ({
|
||||
{flows
|
||||
.map((flow) => {
|
||||
let runCount = 0,
|
||||
lastRun: GraphExecution | null = null;
|
||||
lastRun: GraphExecutionMeta | null = null;
|
||||
if (executions) {
|
||||
const _flowRuns = executions.filter(
|
||||
(r) => r.graph_id == flow.agent_id,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useState, useCallback } from "react";
|
||||
import {
|
||||
GraphExecution,
|
||||
GraphExecutionMeta,
|
||||
Graph,
|
||||
safeCopyGraph,
|
||||
BlockUIType,
|
||||
@@ -32,7 +32,6 @@ import {
|
||||
DialogFooter,
|
||||
} from "@/components/ui/dialog";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import { CronScheduler } from "@/components/cronScheduler";
|
||||
import RunnerInputUI from "@/components/runner-ui/RunnerInputUI";
|
||||
import useAgentGraph from "@/hooks/useAgentGraph";
|
||||
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<
|
||||
React.HTMLAttributes<HTMLDivElement> & {
|
||||
flow: LibraryAgent;
|
||||
executions: GraphExecution[];
|
||||
executions: GraphExecutionMeta[];
|
||||
flowVersion?: number | "all";
|
||||
refresh: () => void;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import {
|
||||
GraphExecution,
|
||||
GraphExecutionMeta,
|
||||
LibraryAgent,
|
||||
NodeExecutionResult,
|
||||
SpecialBlockID,
|
||||
@@ -18,7 +18,7 @@ import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
||||
export const FlowRunInfo: React.FC<
|
||||
React.HTMLAttributes<HTMLDivElement> & {
|
||||
flow: LibraryAgent;
|
||||
execution: GraphExecution;
|
||||
execution: GraphExecutionMeta;
|
||||
}
|
||||
> = ({ flow, execution, ...props }) => {
|
||||
const [isOutputOpen, setIsOutputOpen] = useState(false);
|
||||
@@ -26,10 +26,9 @@ export const FlowRunInfo: React.FC<
|
||||
const api = useBackendAPI();
|
||||
|
||||
const fetchBlockResults = useCallback(async () => {
|
||||
const executionResults = await api.getGraphExecutionInfo(
|
||||
flow.agent_id,
|
||||
execution.execution_id,
|
||||
);
|
||||
const executionResults = (
|
||||
await api.getGraphExecutionInfo(flow.agent_id, execution.execution_id)
|
||||
).node_executions;
|
||||
|
||||
// Create a map of the latest COMPLETED execution results of output nodes by node_id
|
||||
const latestCompletedResults = executionResults
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React from "react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { GraphExecution } from "@/lib/autogpt-server-api";
|
||||
import { GraphExecutionMeta } from "@/lib/autogpt-server-api";
|
||||
|
||||
export const FlowRunStatusBadge: React.FC<{
|
||||
status: GraphExecution["status"];
|
||||
status: GraphExecutionMeta["status"];
|
||||
className?: string;
|
||||
}> = ({ status, className }) => (
|
||||
<Badge
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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 {
|
||||
Table,
|
||||
@@ -15,10 +15,10 @@ import { TextRenderer } from "../ui/render";
|
||||
|
||||
export const FlowRunsList: React.FC<{
|
||||
flows: LibraryAgent[];
|
||||
executions: GraphExecution[];
|
||||
executions: GraphExecutionMeta[];
|
||||
className?: string;
|
||||
selectedRun?: GraphExecution | null;
|
||||
onSelectRun: (r: GraphExecution) => void;
|
||||
selectedRun?: GraphExecutionMeta | null;
|
||||
onSelectRun: (r: GraphExecutionMeta) => void;
|
||||
}> = ({ flows, executions, selectedRun, onSelectRun, className }) => (
|
||||
<Card className={className}>
|
||||
<CardHeader>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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 { Button } from "@/components/ui/button";
|
||||
import {
|
||||
@@ -12,7 +12,7 @@ import { FlowRunsTimeline } from "@/components/monitor/FlowRunsTimeline";
|
||||
|
||||
export const FlowRunsStatus: React.FC<{
|
||||
flows: LibraryAgent[];
|
||||
executions: GraphExecution[];
|
||||
executions: GraphExecutionMeta[];
|
||||
title?: string;
|
||||
className?: string;
|
||||
}> = ({ 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 {
|
||||
ComposedChart,
|
||||
DefaultLegendContentProps,
|
||||
@@ -23,7 +23,7 @@ export const FlowRunsTimeline = ({
|
||||
className,
|
||||
}: {
|
||||
flows: LibraryAgent[];
|
||||
executions: GraphExecution[];
|
||||
executions: GraphExecutionMeta[];
|
||||
dataMin: "dataMin" | number;
|
||||
className?: string;
|
||||
}) => (
|
||||
@@ -60,8 +60,10 @@ export const FlowRunsTimeline = ({
|
||||
<Tooltip
|
||||
content={({ payload, label }) => {
|
||||
if (payload && payload.length) {
|
||||
const data: GraphExecution & { time: number; _duration: number } =
|
||||
payload[0].payload;
|
||||
const data: GraphExecutionMeta & {
|
||||
time: number;
|
||||
_duration: number;
|
||||
} = payload[0].payload;
|
||||
const flow = flows.find((f) => f.agent_id === data.graph_id);
|
||||
return (
|
||||
<Card className="p-2 text-xs leading-normal">
|
||||
|
||||
@@ -242,7 +242,7 @@ export const SchedulesTable = ({
|
||||
</TableCell>
|
||||
<TableCell>{schedule.graph_version}</TableCell>
|
||||
<TableCell>
|
||||
{new Date(schedule.next_run_time).toLocaleString()}
|
||||
{schedule.next_run_time.toLocaleString()}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant="secondary">
|
||||
|
||||
@@ -8,10 +8,7 @@ const Card = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"rounded-xl border border-gray-300 bg-white text-neutral-950 shadow",
|
||||
className,
|
||||
)}
|
||||
className={cn("agpt-card text-neutral-950", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
||||
@@ -606,8 +606,11 @@ export default function useAgentGraph(
|
||||
}
|
||||
|
||||
const fetchExecutions = async () => {
|
||||
const results = await api.getGraphExecutionInfo(flowID, flowExecutionID);
|
||||
setUpdateQueue((prev) => [...prev, ...results]);
|
||||
const execution = await api.getGraphExecutionInfo(
|
||||
flowID,
|
||||
flowExecutionID,
|
||||
);
|
||||
setUpdateQueue((prev) => [...prev, ...execution.node_executions]);
|
||||
|
||||
// Track execution until completed
|
||||
const pendingNodeExecutions: Set<string> = new Set();
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
CredentialsDeleteResponse,
|
||||
CredentialsMetaResponse,
|
||||
GraphExecution,
|
||||
GraphExecutionMeta,
|
||||
Graph,
|
||||
GraphCreatable,
|
||||
GraphMeta,
|
||||
@@ -161,10 +162,6 @@ export default class BackendAPI {
|
||||
return this._get(`/graphs`);
|
||||
}
|
||||
|
||||
getExecutions(): Promise<GraphExecution[]> {
|
||||
return this._get(`/executions`);
|
||||
}
|
||||
|
||||
getGraph(
|
||||
id: string,
|
||||
version?: number,
|
||||
@@ -212,22 +209,37 @@ export default class BackendAPI {
|
||||
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(
|
||||
graphID: string,
|
||||
runID: string,
|
||||
): Promise<NodeExecutionResult[]> {
|
||||
return (await this._get(`/graphs/${graphID}/executions/${runID}`)).map(
|
||||
): Promise<GraphExecution> {
|
||||
const result = await this._get(`/graphs/${graphID}/executions/${runID}`);
|
||||
result.node_executions = result.node_executions.map(
|
||||
parseNodeExecutionResultTimestamps,
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
async stopGraphExecution(
|
||||
graphID: string,
|
||||
runID: string,
|
||||
): Promise<NodeExecutionResult[]> {
|
||||
return (
|
||||
await this._request("POST", `/graphs/${graphID}/executions/${runID}/stop`)
|
||||
).map(parseNodeExecutionResultTimestamps);
|
||||
): Promise<GraphExecution> {
|
||||
const result = await this._request(
|
||||
"POST",
|
||||
`/graphs/${graphID}/executions/${runID}/stop`,
|
||||
);
|
||||
result.node_executions = result.node_executions.map(
|
||||
parseNodeExecutionResultTimestamps,
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
oAuthLogin(
|
||||
@@ -484,15 +496,19 @@ export default class BackendAPI {
|
||||
}
|
||||
|
||||
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}`);
|
||||
}
|
||||
|
||||
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> {
|
||||
@@ -824,3 +840,10 @@ function parseNodeExecutionResultTimestamps(result: any): NodeExecutionResult {
|
||||
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
|
||||
| BlockIOCombinedTypeSubSchema;
|
||||
|
||||
export type BlockIOSubType = BlockIOSimpleTypeSubSchema["type"];
|
||||
|
||||
export type BlockIOSimpleTypeSubSchema =
|
||||
| BlockIOObjectSubSchema
|
||||
| BlockIOCredentialsSubSchema
|
||||
@@ -152,8 +154,7 @@ export const PROVIDER_NAMES = {
|
||||
export type CredentialsProviderName =
|
||||
(typeof PROVIDER_NAMES)[keyof typeof PROVIDER_NAMES];
|
||||
|
||||
export type BlockIOCredentialsSubSchema = BlockIOSubSchemaMeta & {
|
||||
type: "object";
|
||||
export type BlockIOCredentialsSubSchema = BlockIOObjectSubSchema & {
|
||||
/* Mirror of backend/data/model.py:CredentialsFieldSchemaExtra */
|
||||
credentials_provider: CredentialsProviderName[];
|
||||
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
|
||||
// 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];
|
||||
secret?: boolean;
|
||||
}
|
||||
| {
|
||||
type: "anyOf";
|
||||
anyOf: BlockIOSimpleTypeSubSchema[];
|
||||
default?: string | number | boolean | null;
|
||||
secret?: boolean;
|
||||
}
|
||||
| {
|
||||
type: "oneOf";
|
||||
oneOf: BlockIOSimpleTypeSubSchema[];
|
||||
default?: string | number | boolean | null;
|
||||
secret?: boolean;
|
||||
@@ -219,8 +216,8 @@ export type LinkCreatable = Omit<Link, "id" | "is_static"> & {
|
||||
id?: string;
|
||||
};
|
||||
|
||||
/* Mirror of backend/data/graph.py:GraphExecution */
|
||||
export type GraphExecution = {
|
||||
/* Mirror of backend/data/graph.py:GraphExecutionMeta */
|
||||
export type GraphExecutionMeta = {
|
||||
execution_id: string;
|
||||
started_at: number;
|
||||
ended_at: number;
|
||||
@@ -229,6 +226,14 @@ export type GraphExecution = {
|
||||
status: "QUEUED" | "RUNNING" | "COMPLETED" | "TERMINATED" | "FAILED";
|
||||
graph_id: GraphID;
|
||||
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 = {
|
||||
@@ -237,12 +242,27 @@ export type GraphMeta = {
|
||||
is_active: boolean;
|
||||
name: string;
|
||||
description: string;
|
||||
input_schema: BlockIOObjectSubSchema;
|
||||
output_schema: BlockIOObjectSubSchema;
|
||||
input_schema: GraphIOSchema;
|
||||
output_schema: GraphIOSchema;
|
||||
};
|
||||
|
||||
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 */
|
||||
export type Graph = GraphMeta & {
|
||||
nodes: Array<Node>;
|
||||
@@ -256,8 +276,8 @@ export type GraphUpdateable = Omit<
|
||||
version?: number;
|
||||
is_active?: boolean;
|
||||
links: Array<LinkCreatable>;
|
||||
input_schema?: BlockIOObjectSubSchema;
|
||||
output_schema?: BlockIOObjectSubSchema;
|
||||
input_schema?: GraphIOSchema;
|
||||
output_schema?: GraphIOSchema;
|
||||
};
|
||||
|
||||
export type GraphCreatable = Omit<GraphUpdateable, "id"> & { id?: string };
|
||||
@@ -553,7 +573,7 @@ export type Schedule = {
|
||||
graph_id: GraphID;
|
||||
graph_version: number;
|
||||
input_data: { [key: string]: any };
|
||||
next_run_time: string;
|
||||
next_run_time: Date;
|
||||
};
|
||||
|
||||
export type ScheduleCreatable = {
|
||||
|
||||
@@ -18,7 +18,7 @@ const config = {
|
||||
mono: ["var(--font-geist-mono)"],
|
||||
// Include the custom font family
|
||||
neue: ['"PP Neue Montreal TT"', "sans-serif"],
|
||||
poppin: ["var(--font-poppins)"],
|
||||
poppins: ["var(--font-poppins)"],
|
||||
inter: ["var(--font-inter)"],
|
||||
},
|
||||
colors: {
|
||||
@@ -95,26 +95,20 @@ const config = {
|
||||
28: "7rem",
|
||||
32: "8rem",
|
||||
36: "9rem",
|
||||
39: "9.875rem",
|
||||
40: "10rem",
|
||||
44: "11rem",
|
||||
48: "12rem",
|
||||
52: "13rem",
|
||||
56: "14rem",
|
||||
69: "14.875rem",
|
||||
60: "15rem",
|
||||
63: "15.875rem",
|
||||
64: "16rem",
|
||||
68: "17.75rem",
|
||||
68: "17rem",
|
||||
70: "17.5rem",
|
||||
71: "17.75rem",
|
||||
72: "18rem",
|
||||
77: "18.5625rem",
|
||||
76: "19rem",
|
||||
80: "20rem",
|
||||
89: "22.5rem",
|
||||
96: "24rem",
|
||||
110: "27.5rem",
|
||||
139: "37.1875rem",
|
||||
167: "41.6875rem",
|
||||
225: "56.25rem",
|
||||
},
|
||||
borderRadius: {
|
||||
lg: "var(--radius)",
|
||||
|
||||
Reference in New Issue
Block a user