Merge branch 'swiftyos/secrt-1646-review-chat-logic-and-route-models-in-chatpy-and-modelspy' of github.com:Significant-Gravitas/AutoGPT into swiftyos/secrt-1646-review-chat-logic-and-route-models-in-chatpy-and-modelspy

This commit is contained in:
Swifty
2025-10-29 11:51:56 +01:00
61 changed files with 1065 additions and 799 deletions

View File

@@ -4,7 +4,7 @@ from typing import Any, Literal, Optional
from e2b_code_interpreter import AsyncSandbox
from e2b_code_interpreter import Result as E2BExecutionResult
from e2b_code_interpreter.charts import Chart as E2BExecutionResultChart
from pydantic import BaseModel, JsonValue, SecretStr
from pydantic import BaseModel, Field, JsonValue, SecretStr
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
from backend.data.model import (
@@ -61,7 +61,7 @@ class MainCodeExecutionResult(BaseModel):
jpeg: Optional[str] = None
pdf: Optional[str] = None
latex: Optional[str] = None
json: Optional[JsonValue] = None # type: ignore (reportIncompatibleMethodOverride)
json_data: Optional[JsonValue] = Field(None, alias="json")
javascript: Optional[str] = None
data: Optional[dict] = None
chart: Optional[Chart] = None

View File

@@ -104,8 +104,6 @@ class LlmModel(str, Enum, metaclass=LlmModelMeta):
CLAUDE_4_5_SONNET = "claude-sonnet-4-5-20250929"
CLAUDE_4_5_HAIKU = "claude-haiku-4-5-20251001"
CLAUDE_3_7_SONNET = "claude-3-7-sonnet-20250219"
CLAUDE_3_5_SONNET = "claude-3-5-sonnet-latest"
CLAUDE_3_5_HAIKU = "claude-3-5-haiku-latest"
CLAUDE_3_HAIKU = "claude-3-haiku-20240307"
# AI/ML API models
AIML_API_QWEN2_5_72B = "Qwen/Qwen2.5-72B-Instruct-Turbo"
@@ -224,12 +222,6 @@ MODEL_METADATA = {
LlmModel.CLAUDE_3_7_SONNET: ModelMetadata(
"anthropic", 200000, 64000
), # claude-3-7-sonnet-20250219
LlmModel.CLAUDE_3_5_SONNET: ModelMetadata(
"anthropic", 200000, 8192
), # claude-3-5-sonnet-20241022
LlmModel.CLAUDE_3_5_HAIKU: ModelMetadata(
"anthropic", 200000, 8192
), # claude-3-5-haiku-20241022
LlmModel.CLAUDE_3_HAIKU: ModelMetadata(
"anthropic", 200000, 4096
), # claude-3-haiku-20240307
@@ -1562,7 +1554,9 @@ class AIConversationBlock(AIBlockBase):
("prompt", list),
],
test_mock={
"llm_call": lambda *args, **kwargs: "The 2020 World Series was played at Globe Life Field in Arlington, Texas."
"llm_call": lambda *args, **kwargs: dict(
response="The 2020 World Series was played at Globe Life Field in Arlington, Texas."
)
},
)
@@ -1591,7 +1585,7 @@ class AIConversationBlock(AIBlockBase):
),
credentials=credentials,
)
yield "response", response
yield "response", response["response"]
yield "prompt", self.prompt

View File

@@ -28,11 +28,7 @@ from backend.sdk import (
)
# Suppress false positive cleanup warning of litellm (a dependency of stagehand)
warnings.filterwarnings(
"ignore",
message="coroutine 'close_litellm_async_clients' was never awaited",
category=RuntimeWarning,
)
warnings.filterwarnings("ignore", module="litellm.llms.custom_httpx")
# Store the original method
original_register_signal_handlers = stagehand.main.Stagehand._register_signal_handlers

View File

@@ -362,7 +362,7 @@ class TestLLMStatsTracking:
assert block.execution_stats.llm_call_count == 1
# Check output
assert outputs["response"] == {"response": "AI response to conversation"}
assert outputs["response"] == "AI response to conversation"
@pytest.mark.asyncio
async def test_ai_list_generator_with_retries(self):

View File

@@ -593,11 +593,6 @@ def is_block_auth_configured(
f"Block {block_cls.__name__} has only optional credential inputs"
" - will work without credentials configured"
)
if len(credential_inputs) > 1:
logger.warning(
f"Block {block_cls.__name__} has multiple credential inputs: "
f"{', '.join(credential_inputs.keys())}"
)
# Check if the credential inputs for this block are correctly configured
for field_name, field_info in credential_inputs.items():

View File

@@ -76,8 +76,6 @@ MODEL_COST: dict[LlmModel, int] = {
LlmModel.CLAUDE_4_5_HAIKU: 4,
LlmModel.CLAUDE_4_5_SONNET: 9,
LlmModel.CLAUDE_3_7_SONNET: 5,
LlmModel.CLAUDE_3_5_SONNET: 4,
LlmModel.CLAUDE_3_5_HAIKU: 1, # $0.80 / $4.00
LlmModel.CLAUDE_3_HAIKU: 1,
LlmModel.AIML_API_QWEN2_5_72B: 1,
LlmModel.AIML_API_LLAMA3_1_70B: 1,

View File

@@ -1,6 +1,7 @@
import logging
import os
from contextlib import asynccontextmanager
from datetime import timedelta
from urllib.parse import parse_qsl, urlencode, urlparse, urlunparse
from uuid import uuid4
@@ -82,17 +83,19 @@ async def disconnect():
raise ConnectionError("Failed to disconnect from Prisma.")
# Transaction timeout constant (in milliseconds)
TRANSACTION_TIMEOUT = 30000 # 30 seconds - Increased from 15s to prevent timeout errors during graph creation under load
# Transaction timeout constant:
# increased from 15s to prevent timeout errors during graph creation under load.
TRANSACTION_TIMEOUT = timedelta(seconds=30)
@asynccontextmanager
async def transaction(timeout: int = TRANSACTION_TIMEOUT):
async def transaction(timeout: timedelta = TRANSACTION_TIMEOUT):
"""
Create a database transaction with optional timeout.
Args:
timeout: Transaction timeout in milliseconds. If None, uses TRANSACTION_TIMEOUT (15s).
timeout: Transaction timeout as a timedelta.
Defaults to `TRANSACTION_TIMEOUT` (30s).
"""
async with prisma.tx(timeout=timeout) as tx:
yield tx

View File

@@ -369,7 +369,7 @@ async def _enqueue_next_nodes(
# Incomplete input data, skip queueing the execution.
if not next_node_input:
log_metadata.warning(f"Skipped queueing {suffix}")
log_metadata.info(f"Skipped queueing {suffix}")
return enqueued_executions
# Input is complete, enqueue the execution.

View File

@@ -44,7 +44,11 @@ from backend.server.external.api import external_app
from backend.server.middleware.security import SecurityHeadersMiddleware
from backend.util import json
from backend.util.cloud_storage import shutdown_cloud_storage_handler
from backend.util.exceptions import NotAuthorizedError, NotFoundError
from backend.util.exceptions import (
MissingConfigError,
NotAuthorizedError,
NotFoundError,
)
from backend.util.feature_flag import initialize_launchdarkly, shutdown_launchdarkly
from backend.util.service import UnhealthyServiceError
@@ -187,6 +191,7 @@ def handle_internal_http_error(status_code: int = 500, log_error: bool = True):
request.method,
request.url.path,
exc,
exc_info=exc,
)
hint = (
@@ -241,6 +246,7 @@ app.add_exception_handler(NotFoundError, handle_internal_http_error(404, False))
app.add_exception_handler(NotAuthorizedError, handle_internal_http_error(403, False))
app.add_exception_handler(RequestValidationError, validation_error_handler)
app.add_exception_handler(pydantic.ValidationError, validation_error_handler)
app.add_exception_handler(MissingConfigError, handle_internal_http_error(503))
app.add_exception_handler(ValueError, handle_internal_http_error(400))
app.add_exception_handler(Exception, handle_internal_http_error(500))
@@ -298,28 +304,27 @@ class AgentServer(backend.util.service.AppProcess):
allow_methods=["*"], # Allows all methods
allow_headers=["*"], # Allows all headers
)
config = backend.util.settings.Config()
# Configure uvicorn with performance optimizations from Kludex FastAPI tips
uvicorn_config = {
"app": server_app,
"host": config.agent_api_host,
"port": config.agent_api_port,
"log_config": None,
# Use httptools for HTTP parsing (if available)
"http": "httptools",
# Only use uvloop on Unix-like systems (not supported on Windows)
"loop": "uvloop" if platform.system() != "Windows" else "auto",
}
# Only add debug in local environment (not supported in all uvicorn versions)
if config.app_env == backend.util.settings.AppEnvironment.LOCAL:
if settings.config.app_env == backend.util.settings.AppEnvironment.LOCAL:
import os
# Enable asyncio debug mode via environment variable
os.environ["PYTHONASYNCIODEBUG"] = "1"
uvicorn.run(**uvicorn_config)
# Configure uvicorn with performance optimizations from Kludex FastAPI tips
uvicorn.run(
app=server_app,
host=settings.config.agent_api_host,
port=settings.config.agent_api_port,
log_config=None,
# Use httptools for HTTP parsing (if available)
http="httptools",
# Only use uvloop on Unix-like systems (not supported on Windows)
loop="uvloop" if platform.system() != "Windows" else "auto",
# Disable WebSockets since this service doesn't have any WebSocket endpoints
ws="none",
)
@staticmethod
async def test_execute_graph(

View File

@@ -1128,7 +1128,7 @@ async def disable_execution_sharing(
async def get_shared_execution(
share_token: Annotated[
str,
Path(regex=r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"),
Path(pattern=r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"),
],
) -> execution_db.SharedExecutionResponse:
"""Get a shared graph execution by share token (no auth required)."""

View File

@@ -73,6 +73,15 @@ async def get_store_agents(
f"Getting store agents. featured={featured}, creators={creators}, sorted_by={sorted_by}, search={search_query}, category={category}, page={page}"
)
sanitized_creators = []
if creators:
for c in creators:
sanitized_creators.append(sanitize_query(c))
sanitized_category = None
if category:
sanitized_category = sanitize_query(category)
try:
# If search_query is provided, use full-text search
if search_query:
@@ -98,10 +107,10 @@ async def get_store_agents(
if featured:
filter_conditions.append("featured = true")
if creators:
creator_list = "','".join(creators)
creator_list = "','".join(sanitized_creators)
filter_conditions.append(f"creator_username IN ('{creator_list}')")
if category:
filter_conditions.append(f"'{category}' = ANY(categories)")
filter_conditions.append(f"'{sanitized_category}' = ANY(categories)")
where_filter = (
" AND ".join(filter_conditions) if filter_conditions else "1=1"
@@ -191,9 +200,9 @@ async def get_store_agents(
if featured:
where_clause["featured"] = featured
if creators:
where_clause["creator_username"] = {"in": creators}
if category:
where_clause["categories"] = {"has": category}
where_clause["creator_username"] = {"in": sanitized_creators}
if sanitized_category:
where_clause["categories"] = {"has": sanitized_category}
order_by = []
if sorted_by == "rating":

View File

@@ -327,5 +327,6 @@ class WebsocketServer(AppProcess):
server_app,
host=Config().websocket_server_host,
port=Config().websocket_server_port,
ws="websockets-sansio",
log_config=None,
)

View File

@@ -66,7 +66,6 @@ class CloudStorageHandler:
connector=aiohttp.TCPConnector(
limit=100, # Connection pool limit
force_close=False, # Reuse connections
enable_cleanup_closed=True,
)
)

View File

@@ -175,10 +175,15 @@ async def validate_url(
f"for hostname {ascii_hostname} is not allowed."
)
# Reconstruct the netloc with IDNA-encoded hostname and preserve port
netloc = ascii_hostname
if parsed.port:
netloc = f"{ascii_hostname}:{parsed.port}"
return (
URL(
parsed.scheme,
ascii_hostname,
netloc,
quote(parsed.path, safe="/%:@"),
parsed.params,
parsed.query,

View File

@@ -0,0 +1,21 @@
-- Migrate Claude 3.5 models to Claude 4.5 models
-- This updates all AgentNode blocks that use deprecated Claude 3.5 models to the new 4.5 models
-- See: https://docs.anthropic.com/en/docs/about-claude/models/legacy-model-guide
-- Update Claude 3.5 Sonnet to Claude 4.5 Sonnet
UPDATE "AgentNode"
SET "constantInput" = JSONB_SET(
"constantInput"::jsonb,
'{model}',
'"claude-sonnet-4-5-20250929"'::jsonb
)
WHERE "constantInput"::jsonb->>'model' = 'claude-3-5-sonnet-latest';
-- Update Claude 3.5 Haiku to Claude 4.5 Haiku
UPDATE "AgentNode"
SET "constantInput" = JSONB_SET(
"constantInput"::jsonb,
'{model}',
'"claude-haiku-4-5-20251001"'::jsonb
)
WHERE "constantInput"::jsonb->>'model' = 'claude-3-5-haiku-latest';

View File

@@ -5823,14 +5823,14 @@ files = [
[[package]]
name = "sentry-sdk"
version = "2.33.2"
version = "2.42.1"
description = "Python client for Sentry (https://sentry.io)"
optional = false
python-versions = ">=3.6"
groups = ["main"]
files = [
{file = "sentry_sdk-2.33.2-py2.py3-none-any.whl", hash = "sha256:8d57a3b4861b243aa9d558fda75509ad487db14f488cbdb6c78c614979d77632"},
{file = "sentry_sdk-2.33.2.tar.gz", hash = "sha256:e85002234b7b8efac9b74c2d91dbd4f8f3970dc28da8798e39530e65cb740f94"},
{file = "sentry_sdk-2.42.1-py2.py3-none-any.whl", hash = "sha256:f8716b50c927d3beb41bc88439dc6bcd872237b596df5b14613e2ade104aee02"},
{file = "sentry_sdk-2.42.1.tar.gz", hash = "sha256:8598cc6edcfe74cb8074ba6a7c15338cdee93d63d3eb9b9943b4b568354ad5b6"},
]
[package.dependencies]
@@ -5858,13 +5858,16 @@ django = ["django (>=1.8)"]
falcon = ["falcon (>=1.4)"]
fastapi = ["fastapi (>=0.79.0)"]
flask = ["blinker (>=1.1)", "flask (>=0.11)", "markupsafe"]
google-genai = ["google-genai (>=1.29.0)"]
grpcio = ["grpcio (>=1.21.1)", "protobuf (>=3.8.0)"]
http2 = ["httpcore[http2] (==1.*)"]
httpx = ["httpx (>=0.16.0)"]
huey = ["huey (>=2)"]
huggingface-hub = ["huggingface_hub (>=0.22)"]
langchain = ["langchain (>=0.0.210)"]
langgraph = ["langgraph (>=0.6.6)"]
launchdarkly = ["launchdarkly-server-sdk (>=9.8.0)"]
litellm = ["litellm (>=1.77.5)"]
litestar = ["litestar (>=2.0.0)"]
loguru = ["loguru (>=0.5)"]
openai = ["openai (>=1.0.0)", "tiktoken (>=0.3.0)"]

View File

@@ -55,7 +55,7 @@
"@sentry/nextjs": "10.15.0",
"@supabase/ssr": "0.6.1",
"@supabase/supabase-js": "2.55.0",
"@tanstack/react-query": "5.85.3",
"@tanstack/react-query": "5.87.1",
"@tanstack/react-table": "8.21.3",
"@types/jaro-winkler": "0.2.4",
"@vercel/analytics": "1.5.0",
@@ -103,7 +103,7 @@
"shepherd.js": "14.5.1",
"sonner": "2.0.7",
"tailwind-merge": "2.6.0",
"tailwind-scrollbar": "4.0.2",
"tailwind-scrollbar": "3.1.0",
"tailwindcss-animate": "1.0.7",
"uuid": "11.1.0",
"vaul": "1.1.2",

View File

@@ -99,8 +99,8 @@ importers:
specifier: 2.55.0
version: 2.55.0
'@tanstack/react-query':
specifier: 5.85.3
version: 5.85.3(react@18.3.1)
specifier: 5.87.1
version: 5.87.1(react@18.3.1)
'@tanstack/react-table':
specifier: 8.21.3
version: 8.21.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -243,8 +243,8 @@ importers:
specifier: 2.6.0
version: 2.6.0
tailwind-scrollbar:
specifier: 4.0.2
version: 4.0.2(react@18.3.1)(tailwindcss@3.4.17)
specifier: 3.1.0
version: 3.1.0(tailwindcss@3.4.17)
tailwindcss-animate:
specifier: 1.0.7
version: 1.0.7(tailwindcss@3.4.17)
@@ -287,7 +287,7 @@ importers:
version: 5.86.0(eslint@8.57.1)(typescript@5.9.2)
'@tanstack/react-query-devtools':
specifier: 5.87.3
version: 5.87.3(@tanstack/react-query@5.85.3(react@18.3.1))(react@18.3.1)
version: 5.87.3(@tanstack/react-query@5.87.1(react@18.3.1))(react@18.3.1)
'@types/canvas-confetti':
specifier: 1.9.0
version: 1.9.0
@@ -947,10 +947,6 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
'@babel/runtime@7.28.3':
resolution: {integrity: sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==}
engines: {node: '>=6.9.0'}
'@babel/runtime@7.28.4':
resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==}
engines: {node: '>=6.9.0'}
@@ -985,9 +981,6 @@ packages:
'@emnapi/core@1.5.0':
resolution: {integrity: sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==}
'@emnapi/runtime@1.4.5':
resolution: {integrity: sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==}
'@emnapi/runtime@1.5.0':
resolution: {integrity: sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==}
@@ -1159,12 +1152,6 @@ packages:
cpu: [x64]
os: [win32]
'@eslint-community/eslint-utils@4.7.0':
resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
'@eslint-community/eslint-utils@4.9.0':
resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -2856,8 +2843,8 @@ packages:
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
'@tanstack/query-core@5.85.3':
resolution: {integrity: sha512-9Ne4USX83nHmRuEYs78LW+3lFEEO2hBDHu7mrdIgAFx5Zcrs7ker3n/i8p4kf6OgKExmaDN5oR0efRD7i2J0DQ==}
'@tanstack/query-core@5.87.1':
resolution: {integrity: sha512-HOFHVvhOCprrWvtccSzc7+RNqpnLlZ5R6lTmngb8aq7b4rc2/jDT0w+vLdQ4lD9bNtQ+/A4GsFXy030Gk4ollA==}
'@tanstack/query-devtools@5.87.3':
resolution: {integrity: sha512-LkzxzSr2HS1ALHTgDmJH5eGAVsSQiuwz//VhFW5OqNk0OQ+Fsqba0Tsf+NzWRtXYvpgUqwQr4b2zdFZwxHcGvg==}
@@ -2868,8 +2855,8 @@ packages:
'@tanstack/react-query': ^5.87.1
react: ^18 || ^19
'@tanstack/react-query@5.85.3':
resolution: {integrity: sha512-AqU8TvNh5GVIE8I+TUU0noryBRy7gOY0XhSayVXmOPll4UkZeLWKDwi0rtWOZbwLRCbyxorfJ5DIjDqE7GXpcQ==}
'@tanstack/react-query@5.87.1':
resolution: {integrity: sha512-YKauf8jfMowgAqcxj96AHs+Ux3m3bWT1oSVKamaRPXSnW2HqSznnTCEkAVqctF1e/W9R/mPcyzzINIgpOH94qg==}
peerDependencies:
react: ^18 || ^19
@@ -3045,9 +3032,6 @@ packages:
'@types/phoenix@1.6.6':
resolution: {integrity: sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==}
'@types/prismjs@1.26.5':
resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==}
'@types/prop-types@15.7.15':
resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==}
@@ -3740,9 +3724,6 @@ packages:
camelize@1.0.1:
resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==}
caniuse-lite@1.0.30001735:
resolution: {integrity: sha512-EV/laoX7Wq2J9TQlyIXRxTJqIw4sxfXS4OYgudGxBYRuTv0q7AM6yMEpU/Vo1I94thg9U6EZ2NfZx9GJq83u7w==}
caniuse-lite@1.0.30001741:
resolution: {integrity: sha512-QGUGitqsc8ARjLdgAfxETDhRbJ0REsP6O3I96TAth/mVjh2cYzN2u+3AzPP3aVSm2FehEItaJw1xd+IGBXWeSw==}
@@ -4108,15 +4089,6 @@ packages:
supports-color:
optional: true
debug@4.4.1:
resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
debug@4.4.3:
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
engines: {node: '>=6.0'}
@@ -6220,11 +6192,6 @@ packages:
resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==}
engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
prism-react-renderer@2.4.1:
resolution: {integrity: sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==}
peerDependencies:
react: '>=16.0.0'
process-nextick-args@2.0.1:
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
@@ -6949,11 +6916,11 @@ packages:
tailwind-merge@2.6.0:
resolution: {integrity: sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==}
tailwind-scrollbar@4.0.2:
resolution: {integrity: sha512-wAQiIxAPqk0MNTPptVe/xoyWi27y+NRGnTwvn4PQnbvB9kp8QUBiGl/wsfoVBHnQxTmhXJSNt9NHTmcz9EivFA==}
tailwind-scrollbar@3.1.0:
resolution: {integrity: sha512-pmrtDIZeHyu2idTejfV59SbaJyvp1VRjYxAjZBH0jnyrPRo6HL1kD5Glz8VPagasqr6oAx6M05+Tuw429Z8jxg==}
engines: {node: '>=12.13.0'}
peerDependencies:
tailwindcss: 4.x
tailwindcss: 3.x
tailwindcss-animate@1.0.7:
resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==}
@@ -7567,7 +7534,7 @@ snapshots:
'@babel/types': 7.28.4
'@jridgewell/remapping': 2.3.5
convert-source-map: 2.0.0
debug: 4.4.1
debug: 4.4.3
gensync: 1.0.0-beta.2
json5: 2.2.3
semver: 6.3.1
@@ -7619,7 +7586,7 @@ snapshots:
'@babel/core': 7.28.4
'@babel/helper-compilation-targets': 7.27.2
'@babel/helper-plugin-utils': 7.27.1
debug: 4.4.1
debug: 4.4.3
lodash.debounce: 4.0.8
resolve: 1.22.10
transitivePeerDependencies:
@@ -8270,8 +8237,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@babel/runtime@7.28.3': {}
'@babel/runtime@7.28.4': {}
'@babel/template@7.27.2':
@@ -8288,7 +8253,7 @@ snapshots:
'@babel/parser': 7.28.4
'@babel/template': 7.27.2
'@babel/types': 7.28.4
debug: 4.4.1
debug: 4.4.3
transitivePeerDependencies:
- supports-color
@@ -8325,11 +8290,6 @@ snapshots:
tslib: 2.8.1
optional: true
'@emnapi/runtime@1.4.5':
dependencies:
tslib: 2.8.1
optional: true
'@emnapi/runtime@1.5.0':
dependencies:
tslib: 2.8.1
@@ -8426,11 +8386,6 @@ snapshots:
'@esbuild/win32-x64@0.25.9':
optional: true
'@eslint-community/eslint-utils@4.7.0(eslint@8.57.1)':
dependencies:
eslint: 8.57.1
eslint-visitor-keys: 3.4.3
'@eslint-community/eslint-utils@4.9.0(eslint@8.57.1)':
dependencies:
eslint: 8.57.1
@@ -8441,7 +8396,7 @@ snapshots:
'@eslint/eslintrc@2.1.4':
dependencies:
ajv: 6.12.6
debug: 4.4.1
debug: 4.4.3
espree: 9.6.1
globals: 13.24.0
ignore: 5.3.2
@@ -8491,7 +8446,7 @@ snapshots:
'@humanwhocodes/config-array@0.13.0':
dependencies:
'@humanwhocodes/object-schema': 2.0.3
debug: 4.4.1
debug: 4.4.3
minimatch: 3.1.2
transitivePeerDependencies:
- supports-color
@@ -8592,7 +8547,7 @@ snapshots:
'@img/sharp-wasm32@0.34.3':
dependencies:
'@emnapi/runtime': 1.4.5
'@emnapi/runtime': 1.5.0
optional: true
'@img/sharp-win32-arm64@0.34.3':
@@ -9041,7 +8996,7 @@ snapshots:
ajv: 8.17.1
chalk: 4.1.2
compare-versions: 6.1.1
debug: 4.4.1
debug: 4.4.3
esbuild: 0.25.9
esutils: 2.0.3
fs-extra: 11.3.1
@@ -10373,7 +10328,7 @@ snapshots:
'@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0(typescript@5.9.2)(webpack@5.101.3(esbuild@0.25.9))':
dependencies:
debug: 4.4.1
debug: 4.4.3
endent: 2.1.0
find-cache-dir: 3.3.2
flat-cache: 3.2.0
@@ -10460,19 +10415,19 @@ snapshots:
- supports-color
- typescript
'@tanstack/query-core@5.85.3': {}
'@tanstack/query-core@5.87.1': {}
'@tanstack/query-devtools@5.87.3': {}
'@tanstack/react-query-devtools@5.87.3(@tanstack/react-query@5.85.3(react@18.3.1))(react@18.3.1)':
'@tanstack/react-query-devtools@5.87.3(@tanstack/react-query@5.87.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@tanstack/query-devtools': 5.87.3
'@tanstack/react-query': 5.85.3(react@18.3.1)
'@tanstack/react-query': 5.87.1(react@18.3.1)
react: 18.3.1
'@tanstack/react-query@5.85.3(react@18.3.1)':
'@tanstack/react-query@5.87.1(react@18.3.1)':
dependencies:
'@tanstack/query-core': 5.85.3
'@tanstack/query-core': 5.87.1
react: 18.3.1
'@tanstack/react-table@8.21.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
@@ -10664,8 +10619,6 @@ snapshots:
'@types/phoenix@1.6.6': {}
'@types/prismjs@1.26.5': {}
'@types/prop-types@15.7.15': {}
'@types/react-dom@18.3.5(@types/react@18.3.17)':
@@ -10734,7 +10687,7 @@ snapshots:
'@typescript-eslint/types': 8.43.0
'@typescript-eslint/typescript-estree': 8.43.0(typescript@5.9.2)
'@typescript-eslint/visitor-keys': 8.43.0
debug: 4.4.1
debug: 4.4.3
eslint: 8.57.1
typescript: 5.9.2
transitivePeerDependencies:
@@ -10744,7 +10697,7 @@ snapshots:
dependencies:
'@typescript-eslint/tsconfig-utils': 8.43.0(typescript@5.9.2)
'@typescript-eslint/types': 8.43.0
debug: 4.4.1
debug: 4.4.3
typescript: 5.9.2
transitivePeerDependencies:
- supports-color
@@ -10763,7 +10716,7 @@ snapshots:
'@typescript-eslint/types': 8.43.0
'@typescript-eslint/typescript-estree': 8.43.0(typescript@5.9.2)
'@typescript-eslint/utils': 8.43.0(eslint@8.57.1)(typescript@5.9.2)
debug: 4.4.1
debug: 4.4.3
eslint: 8.57.1
ts-api-utils: 2.1.0(typescript@5.9.2)
typescript: 5.9.2
@@ -10778,7 +10731,7 @@ snapshots:
'@typescript-eslint/tsconfig-utils': 8.43.0(typescript@5.9.2)
'@typescript-eslint/types': 8.43.0
'@typescript-eslint/visitor-keys': 8.43.0
debug: 4.4.1
debug: 4.4.3
fast-glob: 3.3.3
is-glob: 4.0.3
minimatch: 9.0.5
@@ -11395,8 +11348,6 @@ snapshots:
camelize@1.0.1: {}
caniuse-lite@1.0.30001735: {}
caniuse-lite@1.0.30001741: {}
case-sensitive-paths-webpack-plugin@2.4.0: {}
@@ -11598,7 +11549,7 @@ snapshots:
dependencies:
cipher-base: 1.0.6
inherits: 2.0.4
ripemd160: 2.0.1
ripemd160: 2.0.2
sha.js: 2.4.12
create-hash@1.2.0:
@@ -11612,9 +11563,9 @@ snapshots:
create-hmac@1.1.7:
dependencies:
cipher-base: 1.0.6
create-hash: 1.1.3
create-hash: 1.2.0
inherits: 2.0.4
ripemd160: 2.0.1
ripemd160: 2.0.2
safe-buffer: 5.2.1
sha.js: 2.4.12
@@ -11772,10 +11723,6 @@ snapshots:
dependencies:
ms: 2.1.3
debug@4.4.1:
dependencies:
ms: 2.1.3
debug@4.4.3:
dependencies:
ms: 2.1.3
@@ -12077,7 +12024,7 @@ snapshots:
esbuild-register@3.6.0(esbuild@0.25.9):
dependencies:
debug: 4.4.1
debug: 4.4.3
esbuild: 0.25.9
transitivePeerDependencies:
- supports-color
@@ -12148,7 +12095,7 @@ snapshots:
eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1):
dependencies:
'@nolyfill/is-core-module': 1.0.39
debug: 4.4.1
debug: 4.4.3
eslint: 8.57.1
get-tsconfig: 4.10.1
is-bun-module: 2.0.0
@@ -12270,7 +12217,7 @@ snapshots:
eslint@8.57.1:
dependencies:
'@eslint-community/eslint-utils': 4.7.0(eslint@8.57.1)
'@eslint-community/eslint-utils': 4.9.0(eslint@8.57.1)
'@eslint-community/regexpp': 4.12.1
'@eslint/eslintrc': 2.1.4
'@eslint/js': 8.57.1
@@ -12281,7 +12228,7 @@ snapshots:
ajv: 6.12.6
chalk: 4.1.2
cross-spawn: 7.0.6
debug: 4.4.1
debug: 4.4.3
doctrine: 3.0.0
escape-string-regexp: 4.0.0
eslint-scope: 7.2.2
@@ -13654,7 +13601,7 @@ snapshots:
micromark@4.0.2:
dependencies:
'@types/debug': 4.1.12
debug: 4.4.1
debug: 4.4.3
decode-named-character-reference: 1.2.0
devlop: 1.1.0
micromark-core-commonmark: 2.0.3
@@ -13790,7 +13737,7 @@ snapshots:
dependencies:
'@next/env': 15.4.7
'@swc/helpers': 0.5.15
caniuse-lite: 1.0.30001735
caniuse-lite: 1.0.30001741
postcss: 8.4.31
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
@@ -14311,12 +14258,6 @@ snapshots:
ansi-styles: 5.2.0
react-is: 17.0.2
prism-react-renderer@2.4.1(react@18.3.1):
dependencies:
'@types/prismjs': 1.26.5
clsx: 2.1.1
react: 18.3.1
process-nextick-args@2.0.1: {}
process@0.11.10: {}
@@ -14495,7 +14436,7 @@ snapshots:
react-window@1.8.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
'@babel/runtime': 7.28.3
'@babel/runtime': 7.28.4
memoize-one: 5.2.1
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
@@ -14716,7 +14657,7 @@ snapshots:
require-in-the-middle@7.5.2:
dependencies:
debug: 4.4.1
debug: 4.4.3
module-details-from-path: 1.0.4
resolve: 1.22.10
transitivePeerDependencies:
@@ -15259,12 +15200,9 @@ snapshots:
tailwind-merge@2.6.0: {}
tailwind-scrollbar@4.0.2(react@18.3.1)(tailwindcss@3.4.17):
tailwind-scrollbar@3.1.0(tailwindcss@3.4.17):
dependencies:
prism-react-renderer: 2.4.1(react@18.3.1)
tailwindcss: 3.4.17
transitivePeerDependencies:
- react
tailwindcss-animate@1.0.7(tailwindcss@3.4.17):
dependencies:

View File

@@ -10,9 +10,9 @@ import OnboardingAgentCard from "../components/OnboardingAgentCard";
import { useEffect, useState } from "react";
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
import { StoreAgentDetails } from "@/lib/autogpt-server-api";
import { finishOnboarding } from "../6-congrats/actions";
import { isEmptyOrWhitespace } from "@/lib/utils";
import { useOnboarding } from "../../../../providers/onboarding/onboarding-provider";
import { finishOnboarding } from "../6-congrats/actions";
export default function Page() {
const { state, updateState } = useOnboarding(4, "INTEGRATIONS");
@@ -24,6 +24,7 @@ export default function Page() {
if (agents.length < 2) {
finishOnboarding();
}
setAgents(agents);
});
}, [api, setAgents]);

View File

@@ -0,0 +1,62 @@
import { CredentialsInput } from "@/app/(platform)/library/agents/[id]/components/AgentRunsView/components/CredentialsInputs/CredentialsInputs";
import { CredentialsMetaInput } from "@/app/api/__generated__/models/credentialsMetaInput";
import { GraphMeta } from "@/app/api/__generated__/models/graphMeta";
import { useState } from "react";
import { getSchemaDefaultCredentials } from "../../helpers";
import { areAllCredentialsSet, getCredentialFields } from "./helpers";
type Credential = CredentialsMetaInput | undefined;
type Credentials = Record<string, Credential>;
type Props = {
agent: GraphMeta | null;
siblingInputs?: Record<string, any>;
onCredentialsChange: (
credentials: Record<string, CredentialsMetaInput>,
) => void;
onValidationChange: (isValid: boolean) => void;
onLoadingChange: (isLoading: boolean) => void;
};
export function AgentOnboardingCredentials(props: Props) {
const [inputCredentials, setInputCredentials] = useState<Credentials>({});
const fields = getCredentialFields(props.agent);
const required = Object.keys(fields || {}).length > 0;
if (!required) return null;
function handleSelectCredentials(key: string, value: Credential) {
const updated = { ...inputCredentials, [key]: value };
setInputCredentials(updated);
const sanitized: Record<string, CredentialsMetaInput> = {};
for (const [k, v] of Object.entries(updated)) {
if (v) sanitized[k] = v;
}
props.onCredentialsChange(sanitized);
const isValid = !required || areAllCredentialsSet(fields, updated);
props.onValidationChange(isValid);
}
return (
<>
{Object.entries(fields).map(([key, inputSubSchema]) => (
<div key={key} className="mt-4">
<CredentialsInput
schema={inputSubSchema}
selectedCredentials={
inputCredentials[key] ??
getSchemaDefaultCredentials(inputSubSchema)
}
onSelectCredentials={(value) => handleSelectCredentials(key, value)}
siblingInputs={props.siblingInputs}
onLoaded={(loaded) => props.onLoadingChange(!loaded)}
/>
</div>
))}
</>
);
}

View File

@@ -0,0 +1,32 @@
import { CredentialsMetaInput } from "@/app/api/__generated__/models/credentialsMetaInput";
import { GraphMeta } from "@/app/api/__generated__/models/graphMeta";
import { BlockIOCredentialsSubSchema } from "@/lib/autogpt-server-api/types";
export function getCredentialFields(
agent: GraphMeta | null,
): AgentCredentialsFields {
if (!agent) return {};
const hasNoInputs =
!agent.credentials_input_schema ||
typeof agent.credentials_input_schema !== "object" ||
!("properties" in agent.credentials_input_schema) ||
!agent.credentials_input_schema.properties;
if (hasNoInputs) return {};
return agent.credentials_input_schema.properties as AgentCredentialsFields;
}
export type AgentCredentialsFields = Record<
string,
BlockIOCredentialsSubSchema
>;
export function areAllCredentialsSet(
fields: AgentCredentialsFields,
inputs: Record<string, CredentialsMetaInput | undefined>,
) {
const required = Object.keys(fields || {});
return required.every((k) => Boolean(inputs[k]));
}

View File

@@ -0,0 +1,45 @@
import { cn } from "@/lib/utils";
import { OnboardingText } from "../../components/OnboardingText";
type RunAgentHintProps = {
handleNewRun: () => void;
};
export function RunAgentHint(props: RunAgentHintProps) {
return (
<div className="ml-[104px] w-[481px] pl-5">
<div className="flex flex-col">
<OnboardingText variant="header">Run your first agent</OnboardingText>
<span className="mt-9 text-base font-normal leading-normal text-zinc-600">
A &apos;run&apos; is when your agent starts working on a task
</span>
<span className="mt-4 text-base font-normal leading-normal text-zinc-600">
Click on <b>New Run</b> below to try it out
</span>
<div
onClick={props.handleNewRun}
className={cn(
"mt-16 flex h-[68px] w-[330px] items-center justify-center rounded-xl border-2 border-violet-700 bg-neutral-50",
"cursor-pointer transition-all duration-200 ease-in-out hover:bg-violet-50",
)}
>
<svg
width="38"
height="38"
viewBox="0 0 32 32"
xmlns="http://www.w3.org/2000/svg"
>
<g stroke="#6d28d9" strokeWidth="1.2" strokeLinecap="round">
<line x1="16" y1="8" x2="16" y2="24" />
<line x1="8" y1="16" x2="24" y2="16" />
</g>
</svg>
<span className="ml-3 font-sans text-[19px] font-medium leading-normal text-violet-700">
New run
</span>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,52 @@
import { StoreAgentDetails } from "@/app/api/__generated__/models/storeAgentDetails";
import StarRating from "../../components/StarRating";
import SmartImage from "@/components/__legacy__/SmartImage";
type Props = {
storeAgent: StoreAgentDetails | null;
};
export function SelectedAgentCard(props: Props) {
return (
<div className="fixed left-1/4 top-1/2 w-[481px] -translate-x-1/2 -translate-y-1/2">
<div className="h-[156px] w-[481px] rounded-xl bg-white px-6 pb-5 pt-4">
<span className="font-sans text-xs font-medium tracking-wide text-zinc-500">
SELECTED AGENT
</span>
{props.storeAgent ? (
<div className="mt-4 flex h-20 rounded-lg bg-violet-50 p-3">
{/* Left image */}
<SmartImage
src={props.storeAgent.agent_image[0]}
alt="Agent cover"
className="w-[350px] rounded-lg"
/>
{/* Right content */}
<div className="ml-3 flex flex-1 flex-col">
<div className="mb-2 flex flex-col items-start">
<span className="w-[292px] truncate font-sans text-[14px] font-medium leading-tight text-zinc-800">
{props.storeAgent.agent_name}
</span>
<span className="font-norma w-[292px] truncate font-sans text-xs text-zinc-600">
by {props.storeAgent.creator}
</span>
</div>
<div className="flex w-[292px] items-center justify-between">
<span className="truncate font-sans text-xs font-normal leading-tight text-zinc-600">
{props.storeAgent.runs.toLocaleString("en-US")} runs
</span>
<StarRating
className="font-sans text-xs font-normal leading-tight text-zinc-600"
starSize={12}
rating={props.storeAgent.rating || 0}
/>
</div>
</div>
</div>
) : (
<div className="mt-4 flex h-20 animate-pulse rounded-lg bg-gray-300 p-2" />
)}
</div>
</div>
);
}

View File

@@ -1,9 +1,9 @@
import type { GraphMeta } from "@/lib/autogpt-server-api";
import type {
BlockIOCredentialsSubSchema,
CredentialsMetaInput,
} from "@/lib/autogpt-server-api/types";
import type { InputValues } from "./types";
import { GraphMeta } from "@/app/api/__generated__/models/graphMeta";
export function computeInitialAgentInputs(
agent: GraphMeta | null,
@@ -21,7 +21,6 @@ export function computeInitialAgentInputs(
result[key] = existingInputs[key];
return;
}
// GraphIOSubSchema.default is typed as string, but server may return other primitives
const def = (subSchema as unknown as { default?: string | number }).default;
result[key] = def ?? "";
});
@@ -29,40 +28,16 @@ export function computeInitialAgentInputs(
return result;
}
export function getAgentCredentialsInputFields(agent: GraphMeta | null) {
const hasNoInputs =
!agent?.credentials_input_schema ||
typeof agent.credentials_input_schema !== "object" ||
!("properties" in agent.credentials_input_schema) ||
!agent.credentials_input_schema.properties;
if (hasNoInputs) return {};
return agent.credentials_input_schema.properties;
}
export function areAllCredentialsSet(
fields: Record<string, BlockIOCredentialsSubSchema>,
inputs: Record<string, CredentialsMetaInput | undefined>,
) {
const required = Object.keys(fields || {});
return required.every((k) => Boolean(inputs[k]));
}
type IsRunDisabledParams = {
agent: GraphMeta | null;
isRunning: boolean;
agentInputs: InputValues | null | undefined;
credentialsRequired: boolean;
credentialsSatisfied: boolean;
};
export function isRunDisabled({
agent,
isRunning,
agentInputs,
credentialsRequired,
credentialsSatisfied,
}: IsRunDisabledParams) {
const hasEmptyInput = Object.values(agentInputs || {}).some(
(value) => String(value).trim() === "",
@@ -71,7 +46,6 @@ export function isRunDisabled({
if (hasEmptyInput) return true;
if (!agent) return true;
if (isRunning) return true;
if (credentialsRequired && !credentialsSatisfied) return true;
return false;
}
@@ -81,13 +55,3 @@ export function getSchemaDefaultCredentials(
): CredentialsMetaInput | undefined {
return schema.default as CredentialsMetaInput | undefined;
}
export function sanitizeCredentials(
map: Record<string, CredentialsMetaInput | undefined>,
): Record<string, CredentialsMetaInput> {
const sanitized: Record<string, CredentialsMetaInput> = {};
for (const [key, value] of Object.entries(map)) {
if (value) sanitized[key] = value;
}
return sanitized;
}

View File

@@ -1,224 +1,63 @@
"use client";
import SmartImage from "@/components/__legacy__/SmartImage";
import { useOnboarding } from "../../../../providers/onboarding/onboarding-provider";
import OnboardingButton from "../components/OnboardingButton";
import { OnboardingHeader, OnboardingStep } from "../components/OnboardingStep";
import { OnboardingText } from "../components/OnboardingText";
import StarRating from "../components/StarRating";
import { RunAgentInputs } from "@/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunAgentInputs/RunAgentInputs";
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from "@/components/__legacy__/ui/card";
import { useToast } from "@/components/molecules/Toast/use-toast";
import { GraphMeta, StoreAgentDetails } from "@/lib/autogpt-server-api";
import type { InputValues } from "./types";
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
import { cn } from "@/lib/utils";
import { Play } from "lucide-react";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { RunAgentInputs } from "@/app/(platform)/library/agents/[id]/components/AgentRunsView/components/RunAgentInputs/RunAgentInputs";
import { ErrorCard } from "@/components/molecules/ErrorCard/ErrorCard";
import { InformationTooltip } from "@/components/molecules/InformationTooltip/InformationTooltip";
import { CredentialsInput } from "@/app/(platform)/library/agents/[id]/components/AgentRunsView/components/CredentialsInputs/CredentialsInputs";
import type { CredentialsMetaInput } from "@/lib/autogpt-server-api/types";
import {
areAllCredentialsSet,
computeInitialAgentInputs,
getAgentCredentialsInputFields,
isRunDisabled,
getSchemaDefaultCredentials,
sanitizeCredentials,
} from "./helpers";
import { CircleNotchIcon } from "@phosphor-icons/react/dist/ssr";
import { Play } from "lucide-react";
import OnboardingButton from "../components/OnboardingButton";
import { OnboardingHeader, OnboardingStep } from "../components/OnboardingStep";
import { OnboardingText } from "../components/OnboardingText";
import { AgentOnboardingCredentials } from "./components/AgentOnboardingCredentials/AgentOnboardingCredentials";
import { RunAgentHint } from "./components/RunAgentHint";
import { SelectedAgentCard } from "./components/SelectedAgentCard";
import { isRunDisabled } from "./helpers";
import type { InputValues } from "./types";
import { useOnboardingRunStep } from "./useOnboardingRunStep";
export default function Page() {
const { state, updateState, setStep } = useOnboarding(
undefined,
"AGENT_CHOICE",
);
const [showInput, setShowInput] = useState(false);
const [agent, setAgent] = useState<GraphMeta | null>(null);
const [storeAgent, setStoreAgent] = useState<StoreAgentDetails | null>(null);
const [runningAgent, setRunningAgent] = useState(false);
const [inputCredentials, setInputCredentials] = useState<
Record<string, CredentialsMetaInput | undefined>
>({});
const { toast } = useToast();
const router = useRouter();
const api = useBackendAPI();
const {
ready,
error,
showInput,
agent,
onboarding,
storeAgent,
runningAgent,
handleSetAgentInput,
handleRunAgent,
handleNewRun,
handleCredentialsChange,
handleCredentialsValidationChange,
handleCredentialsLoadingChange,
} = useOnboardingRunStep();
useEffect(() => {
setStep(5);
}, []);
useEffect(() => {
if (!state?.selectedStoreListingVersionId) {
return;
}
api
.getStoreAgentByVersionId(state?.selectedStoreListingVersionId)
.then((storeAgent) => {
setStoreAgent(storeAgent);
});
api
.getGraphMetaByStoreListingVersionID(state.selectedStoreListingVersionId)
.then((meta) => {
setAgent(meta);
const update = computeInitialAgentInputs(
meta,
(state.agentInput as unknown as InputValues) || null,
);
updateState({ agentInput: update });
});
}, [api, setAgent, updateState, state?.selectedStoreListingVersionId]);
const agentCredentialsInputFields = getAgentCredentialsInputFields(agent);
const credentialsRequired =
Object.keys(agentCredentialsInputFields || {}).length > 0;
const allCredentialsAreSet = areAllCredentialsSet(
agentCredentialsInputFields,
inputCredentials,
);
function setAgentInput(key: string, value: string) {
updateState({
agentInput: {
...state?.agentInput,
[key]: value,
},
});
if (error) {
return <ErrorCard responseError={error} />;
}
async function runAgent() {
if (!agent) {
return;
}
setRunningAgent(true);
try {
const libraryAgent = await api.addMarketplaceAgentToLibrary(
storeAgent?.store_listing_version_id || "",
);
const { id: runID } = await api.executeGraph(
libraryAgent.graph_id,
libraryAgent.graph_version,
state?.agentInput || {},
sanitizeCredentials(inputCredentials),
);
updateState({
onboardingAgentExecutionId: runID,
agentRuns: (state?.agentRuns || 0) + 1,
});
router.push("/onboarding/6-congrats");
} catch (error) {
console.error("Error running agent:", error);
toast({
title: "Error running agent",
description:
"There was an error running your agent. Please try again or try choosing a different agent if it still fails.",
variant: "destructive",
});
setRunningAgent(false);
}
}
const runYourAgent = (
<div className="ml-[104px] w-[481px] pl-5">
<div className="flex flex-col">
<OnboardingText variant="header">Run your first agent</OnboardingText>
<span className="mt-9 text-base font-normal leading-normal text-zinc-600">
A &apos;run&apos; is when your agent starts working on a task
</span>
<span className="mt-4 text-base font-normal leading-normal text-zinc-600">
Click on <b>New Run</b> below to try it out
</span>
<div
onClick={() => {
setShowInput(true);
setStep(6);
updateState({
completedSteps: [
...(state?.completedSteps || []),
"AGENT_NEW_RUN",
],
});
}}
className={cn(
"mt-16 flex h-[68px] w-[330px] items-center justify-center rounded-xl border-2 border-violet-700 bg-neutral-50",
"cursor-pointer transition-all duration-200 ease-in-out hover:bg-violet-50",
)}
>
<svg
width="38"
height="38"
viewBox="0 0 32 32"
xmlns="http://www.w3.org/2000/svg"
>
<g stroke="#6d28d9" strokeWidth="1.2" strokeLinecap="round">
<line x1="16" y1="8" x2="16" y2="24" />
<line x1="8" y1="16" x2="24" y2="16" />
</g>
</svg>
<span className="ml-3 font-sans text-[19px] font-medium leading-normal text-violet-700">
New run
</span>
</div>
if (!ready) {
return (
<div className="flex flex-col gap-8">
<CircleNotchIcon className="size-10 animate-spin" />
</div>
</div>
);
);
}
return (
<OnboardingStep dotted>
<OnboardingHeader backHref={"/onboarding/4-agent"} transparent />
{/* Agent card */}
<div className="fixed left-1/4 top-1/2 w-[481px] -translate-x-1/2 -translate-y-1/2">
<div className="h-[156px] w-[481px] rounded-xl bg-white px-6 pb-5 pt-4">
<span className="font-sans text-xs font-medium tracking-wide text-zinc-500">
SELECTED AGENT
</span>
{storeAgent ? (
<div className="mt-4 flex h-20 rounded-lg bg-violet-50 p-2">
{/* Left image */}
<SmartImage
src={storeAgent?.agent_image[0]}
alt="Agent cover"
imageContain
className="w-[350px] rounded-lg"
/>
{/* Right content */}
<div className="ml-2 flex flex-1 flex-col">
<span className="w-[292px] truncate font-sans text-[14px] font-medium leading-normal text-zinc-800">
{storeAgent?.agent_name}
</span>
<span className="mt-[5px] w-[292px] truncate font-sans text-xs font-normal leading-tight text-zinc-600">
by {storeAgent?.creator}
</span>
<div className="mt-auto flex w-[292px] justify-between">
<span className="mt-1 truncate font-sans text-xs font-normal leading-tight text-zinc-600">
{storeAgent?.runs.toLocaleString("en-US")} runs
</span>
<StarRating
className="font-sans text-xs font-normal leading-tight text-zinc-600"
starSize={12}
rating={storeAgent?.rating || 0}
/>
</div>
</div>
</div>
) : (
<div className="mt-4 flex h-20 animate-pulse rounded-lg bg-gray-300 p-2" />
)}
</div>
</div>
<div className="flex min-h-[80vh] items-center justify-center">
{/* Left side */}
<SelectedAgentCard storeAgent={storeAgent} />
<div className="w-[481px]" />
{/* Right side */}
{!showInput ? (
runYourAgent
<RunAgentHint handleNewRun={handleNewRun} />
) : (
<div className="ml-[104px] w-[481px] pl-5">
<div className="flex flex-col">
@@ -232,30 +71,7 @@ export default function Page() {
<span className="mt-4 text-base font-normal leading-normal text-zinc-600">
When you&apos;re done, click <b>Run Agent</b>.
</span>
{Object.entries(agentCredentialsInputFields || {}).map(
([key, inputSubSchema]) => (
<div key={key} className="mt-4">
<CredentialsInput
schema={inputSubSchema}
selectedCredentials={
inputCredentials[key] ??
getSchemaDefaultCredentials(inputSubSchema)
}
onSelectCredentials={(value) =>
setInputCredentials((prev) => ({
...prev,
[key]: value,
}))
}
siblingInputs={
(state?.agentInput || undefined) as
| Record<string, any>
| undefined
}
/>
</div>
),
)}
<Card className="agpt-box mt-4">
<CardHeader>
<CardTitle className="font-poppins text-lg">Input</CardTitle>
@@ -272,13 +88,23 @@ export default function Page() {
</label>
<RunAgentInputs
schema={inputSubSchema}
value={state?.agentInput?.[key]}
value={onboarding.state?.agentInput?.[key]}
placeholder={inputSubSchema.description}
onChange={(value) => setAgentInput(key, value)}
onChange={(value) => handleSetAgentInput(key, value)}
/>
</div>
),
)}
<AgentOnboardingCredentials
agent={agent}
siblingInputs={
(onboarding.state?.agentInput as Record<string, any>) ||
undefined
}
onCredentialsChange={handleCredentialsChange}
onValidationChange={handleCredentialsValidationChange}
onLoadingChange={handleCredentialsLoadingChange}
/>
</CardContent>
</Card>
<OnboardingButton
@@ -289,11 +115,10 @@ export default function Page() {
agent,
isRunning: runningAgent,
agentInputs:
(state?.agentInput as unknown as InputValues) || null,
credentialsRequired,
credentialsSatisfied: allCredentialsAreSet,
(onboarding.state?.agentInput as unknown as InputValues) ||
null,
})}
onClick={runAgent}
onClick={handleRunAgent}
icon={<Play className="mr-2" size={18} />}
>
Run agent

View File

@@ -0,0 +1,162 @@
import { CredentialsMetaInput } from "@/app/api/__generated__/models/credentialsMetaInput";
import { GraphMeta } from "@/app/api/__generated__/models/graphMeta";
import { StoreAgentDetails } from "@/app/api/__generated__/models/storeAgentDetails";
import { useToast } from "@/components/molecules/Toast/use-toast";
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
import { useOnboarding } from "@/providers/onboarding/onboarding-provider";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { computeInitialAgentInputs } from "./helpers";
import { InputValues } from "./types";
import {
useGetV2GetAgentByVersion,
useGetV2GetAgentGraph,
} from "@/app/api/__generated__/endpoints/store/store";
export function useOnboardingRunStep() {
const onboarding = useOnboarding(undefined, "AGENT_CHOICE");
const [showInput, setShowInput] = useState(false);
const [agent, setAgent] = useState<GraphMeta | null>(null);
const [storeAgent, setStoreAgent] = useState<StoreAgentDetails | null>(null);
const [runningAgent, setRunningAgent] = useState(false);
const [inputCredentials, setInputCredentials] = useState<
Record<string, CredentialsMetaInput>
>({});
const [credentialsValid, setCredentialsValid] = useState(true);
const [credentialsLoaded, setCredentialsLoaded] = useState(false);
const { toast } = useToast();
const router = useRouter();
const api = useBackendAPI();
const currentAgentVersion =
onboarding.state?.selectedStoreListingVersionId ?? "";
const storeAgentQuery = useGetV2GetAgentByVersion(currentAgentVersion, {
query: { enabled: !!currentAgentVersion },
});
const graphMetaQuery = useGetV2GetAgentGraph(currentAgentVersion, {
query: { enabled: !!currentAgentVersion },
});
useEffect(() => {
onboarding.setStep(5);
}, []);
useEffect(() => {
if (storeAgentQuery.data && storeAgentQuery.data.status === 200) {
setStoreAgent(storeAgentQuery.data.data);
}
}, [storeAgentQuery.data]);
useEffect(() => {
if (
graphMetaQuery.data &&
graphMetaQuery.data.status === 200 &&
onboarding.state
) {
const graphMeta = graphMetaQuery.data.data as GraphMeta;
setAgent(graphMeta);
const update = computeInitialAgentInputs(
graphMeta,
(onboarding.state.agentInput as unknown as InputValues) || null,
);
onboarding.updateState({ agentInput: update });
}
}, [graphMetaQuery.data]);
function handleNewRun() {
if (!onboarding.state) return;
setShowInput(true);
onboarding.setStep(6);
onboarding.updateState({
completedSteps: [
...(onboarding.state.completedSteps || []),
"AGENT_NEW_RUN",
],
});
}
function handleSetAgentInput(key: string, value: string) {
if (!onboarding.state) return;
onboarding.updateState({
agentInput: {
...onboarding.state.agentInput,
[key]: value,
},
});
}
async function handleRunAgent() {
if (!agent || !storeAgent || !onboarding.state) {
toast({
title: "Error getting agent",
description:
"Either the agent is not available or there was an error getting it.",
variant: "destructive",
});
return;
}
setRunningAgent(true);
try {
const libraryAgent = await api.addMarketplaceAgentToLibrary(
storeAgent?.store_listing_version_id || "",
);
const { id: runID } = await api.executeGraph(
libraryAgent.graph_id,
libraryAgent.graph_version,
onboarding.state.agentInput || {},
inputCredentials,
);
onboarding.updateState({
onboardingAgentExecutionId: runID,
agentRuns: (onboarding.state.agentRuns || 0) + 1,
});
router.push("/onboarding/6-congrats");
} catch (error) {
console.error("Error running agent:", error);
toast({
title: "Error running agent",
description:
"There was an error running your agent. Please try again or try choosing a different agent if it still fails.",
variant: "destructive",
});
setRunningAgent(false);
}
}
return {
ready: graphMetaQuery.isSuccess && storeAgentQuery.isSuccess,
error: graphMetaQuery.error || storeAgentQuery.error,
agent,
onboarding,
showInput,
storeAgent,
runningAgent,
credentialsValid,
credentialsLoaded,
handleSetAgentInput,
handleRunAgent,
handleNewRun,
handleCredentialsChange: setInputCredentials,
handleCredentialsValidationChange: setCredentialsValid,
handleCredentialsLoadingChange: (v: boolean) => setCredentialsLoaded(!v),
};
}

View File

@@ -46,7 +46,7 @@ export default function StarRating({
)}
>
{/* Display numerical rating */}
<span className="mr-1 mt-1">{roundedRating}</span>
<span className="mr-1 mt-0.5">{roundedRating}</span>
{/* Display stars */}
{stars.map((starType, index) => {

View File

@@ -44,10 +44,7 @@ export default function AuthErrorPage() {
return (
<div className="flex h-screen items-center justify-center">
<Card className="w-full max-w-md p-8">
<WaitlistErrorContent
onClose={() => router.push("/login")}
closeButtonText="Back to Login"
/>
<WaitlistErrorContent onBackToLogin={() => router.push("/login")} />
</Card>
</div>
);

View File

@@ -23,6 +23,7 @@ import {
import {
beautifyString,
cn,
fillObjectDefaultsFromSchema,
getValue,
hasNonNullNonObjectValue,
isObject,
@@ -158,37 +159,6 @@ export const CustomNode = React.memo(
setIsAnyModalOpen?.(isModalOpen || isOutputModalOpen);
}, [isModalOpen, isOutputModalOpen, data, setIsAnyModalOpen]);
const fillDefaults = useCallback((obj: any, schema: any) => {
// Iterate over the schema properties
for (const key in schema.properties) {
if (schema.properties.hasOwnProperty(key)) {
const propertySchema = schema.properties[key];
// If the property is not in the object, initialize it with the default value
if (!obj.hasOwnProperty(key)) {
if (propertySchema.default !== undefined) {
obj[key] = propertySchema.default;
} else if (propertySchema.type === "object") {
// Recursively fill defaults for nested objects
obj[key] = fillDefaults({}, propertySchema);
} else if (propertySchema.type === "array") {
// Recursively fill defaults for arrays
obj[key] = fillDefaults([], propertySchema);
}
} else {
// If the property exists, recursively fill defaults for nested objects/arrays
if (propertySchema.type === "object") {
obj[key] = fillDefaults(obj[key], propertySchema);
} else if (propertySchema.type === "array") {
obj[key] = fillDefaults(obj[key], propertySchema);
}
}
}
}
return obj;
}, []);
const setHardcodedValues = useCallback(
(values: any) => {
updateNodeData(id, { hardcodedValues: values });
@@ -231,17 +201,19 @@ export const CustomNode = React.memo(
useEffect(() => {
isInitialSetup.current = false;
if (data.backend_id) return; // don't auto-modify existing nodes
if (data.uiType === BlockUIType.AGENT) {
setHardcodedValues({
...data.hardcodedValues,
inputs: fillDefaults(
inputs: fillObjectDefaultsFromSchema(
data.hardcodedValues.inputs ?? {},
data.inputSchema,
),
});
} else {
setHardcodedValues(
fillDefaults(data.hardcodedValues, data.inputSchema),
fillObjectDefaultsFromSchema(data.hardcodedValues, data.inputSchema),
);
}
}, []);
@@ -857,7 +829,9 @@ export const CustomNode = React.memo(
</button>
)}
</div>
<span className="text-xs text-gray-500">#{id.split("-")[0]}</span>
<span className="text-xs text-gray-500">
#{(data.backend_id || id).split("-")[0]}
</span>
<div className="w-auto grow" />

View File

@@ -64,7 +64,7 @@ import { useRouter, usePathname, useSearchParams } from "next/navigation";
import RunnerUIWrapper, { RunnerUIWrapperRef } from "../RunnerUIWrapper";
import OttoChatWidget from "@/app/(platform)/build/components/legacy-builder/OttoChatWidget";
import { useToast } from "@/components/molecules/Toast/use-toast";
import { useCopyPaste } from "../../../../../../hooks/useCopyPaste";
import { useCopyPaste } from "../useCopyPaste";
import NewControlPanel from "@/app/(platform)/build/components/NewControlPanel/NewControlPanel";
import { Flag, useGetFlag } from "@/services/feature-flags/use-get-flag";
import { BuildActionBar } from "../BuildActionBar";

View File

@@ -105,6 +105,7 @@ export const CredentialsInput: FC<{
onSelectCredentials: (newValue?: CredentialsMetaInput) => void;
siblingInputs?: Record<string, any>;
hideIfSingleCredentialAvailable?: boolean;
onLoaded?: (loaded: boolean) => void;
}> = ({
schema,
className,
@@ -112,6 +113,7 @@ export const CredentialsInput: FC<{
onSelectCredentials,
siblingInputs,
hideIfSingleCredentialAvailable = true,
onLoaded,
}) => {
const [isAPICredentialsModalOpen, setAPICredentialsModalOpen] =
useState(false);
@@ -129,6 +131,13 @@ export const CredentialsInput: FC<{
const api = useBackendAPI();
const credentials = useCredentials(schema, siblingInputs);
// Report loaded state to parent
useEffect(() => {
if (onLoaded) {
onLoaded(Boolean(credentials && credentials.isLoading === false));
}
}, [credentials, onLoaded]);
// Deselect credentials if they do not exist (e.g. provider was changed)
useEffect(() => {
if (!credentials || !("savedCredentials" in credentials)) return;

View File

@@ -0,0 +1,48 @@
import { getV2ListLibraryAgentsResponse } from "@/app/api/__generated__/endpoints/library/library";
import type { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
import { LibraryAgentResponse } from "@/app/api/__generated__/models/libraryAgentResponse";
export function filterAgents(agents: LibraryAgent[], term?: string | null) {
const t = term?.trim().toLowerCase();
if (!t) return agents;
return agents.filter(
(a) =>
a.name.toLowerCase().includes(t) ||
a.description.toLowerCase().includes(t),
);
}
export function getInitialData(
cachedAgents: LibraryAgent[],
searchTerm: string | null,
pageSize: number,
) {
const filtered = filterAgents(
cachedAgents as unknown as LibraryAgent[],
searchTerm,
);
if (!filtered.length) {
return undefined;
}
const firstPageAgents: LibraryAgent[] = filtered.slice(0, pageSize);
const totalItems = filtered.length;
const totalPages = Math.max(1, Math.ceil(totalItems / pageSize));
const firstPage: getV2ListLibraryAgentsResponse = {
status: 200,
data: {
agents: firstPageAgents,
pagination: {
total_items: totalItems,
total_pages: totalPages,
current_page: 1,
page_size: pageSize,
},
} satisfies LibraryAgentResponse,
headers: new Headers(),
};
return { pageParams: [1], pages: [firstPage] };
}

View File

@@ -3,9 +3,13 @@
import { useGetV2ListLibraryAgentsInfinite } from "@/app/api/__generated__/endpoints/library/library";
import { LibraryAgentResponse } from "@/app/api/__generated__/models/libraryAgentResponse";
import { useLibraryPageContext } from "../state-provider";
import { useLibraryAgentsStore } from "@/hooks/useLibraryAgents/store";
import { getInitialData } from "./helpers";
export const useLibraryAgentList = () => {
const { searchTerm, librarySort } = useLibraryPageContext();
const { agents: cachedAgents } = useLibraryAgentsStore();
const {
data: agents,
fetchNextPage,
@@ -21,6 +25,7 @@ export const useLibraryAgentList = () => {
},
{
query: {
initialData: getInitialData(cachedAgents, searchTerm, 8),
getNextPageParam: (lastPage) => {
const pagination = (lastPage.data as LibraryAgentResponse).pagination;
const isMore =

View File

@@ -14,12 +14,12 @@ import { environment } from "@/services/environment";
export default function LoginPage() {
const {
user,
form,
feedback,
turnstile,
captchaKey,
isLoading,
isLoggedIn,
isCloudEnv,
isUserLoading,
isGoogleLoading,
@@ -30,7 +30,7 @@ export default function LoginPage() {
handleCloseNotAllowedModal,
} = useLoginPage();
if (isUserLoading || isLoggedIn) {
if (isUserLoading || user) {
return <LoadingLogin />;
}

View File

@@ -162,7 +162,7 @@ export function useLoginPage() {
feedback,
turnstile,
captchaKey,
isLoggedIn: !!user,
user,
isLoading,
isCloudEnv,
isUserLoading,

View File

@@ -1,4 +1,7 @@
import { usePostV2AddMarketplaceAgent } from "@/app/api/__generated__/endpoints/library/library";
import {
getGetV2ListLibraryAgentsQueryKey,
usePostV2AddMarketplaceAgent,
} from "@/app/api/__generated__/endpoints/library/library";
import { useToast } from "@/components/molecules/Toast/use-toast";
import { useRouter } from "next/navigation";
import * as Sentry from "@sentry/nextjs";
@@ -6,6 +9,7 @@ import { useGetV2DownloadAgentFile } from "@/app/api/__generated__/endpoints/sto
import { useOnboarding } from "@/providers/onboarding/onboarding-provider";
import { analytics } from "@/services/analytics";
import { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
import { useQueryClient } from "@tanstack/react-query";
interface UseAgentInfoProps {
storeListingVersionId: string;
@@ -15,6 +19,7 @@ export const useAgentInfo = ({ storeListingVersionId }: UseAgentInfoProps) => {
const { toast } = useToast();
const router = useRouter();
const { completeStep } = useOnboarding();
const queryClient = useQueryClient();
const {
mutateAsync: addMarketplaceAgentToLibrary,
@@ -46,6 +51,10 @@ export const useAgentInfo = ({ storeListingVersionId }: UseAgentInfoProps) => {
if (isAddingAgentFirstTime) {
completeStep("MARKETPLACE_ADD_AGENT");
await queryClient.invalidateQueries({
queryKey: getGetV2ListLibraryAgentsQueryKey(),
});
analytics.sendDatafastEvent("add_to_library", {
name: data.name,
id: data.id,

View File

@@ -5,7 +5,7 @@ import {
} from "@/app/api/__generated__/endpoints/auth/auth";
import { SettingsForm } from "@/app/(platform)/profile/(user)/settings/components/SettingsForm/SettingsForm";
import { useSupabase } from "@/lib/supabase/hooks/useSupabase";
import { useTimezoneDetection } from "@/hooks/useTimezoneDetection";
import { useTimezoneDetection } from "@/app/(platform)/profile/(user)/settings/useTimezoneDetection";
import * as React from "react";
import SettingsLoading from "./loading";
import { redirect } from "next/navigation";
@@ -28,6 +28,7 @@ export default function SettingsPage() {
},
},
});
useTimezoneDetection(timezone);
const { user, isUserLoading } = useSupabase();

View File

@@ -70,7 +70,7 @@
@layer components {
.agpt-border-input {
@apply m-0.5 border border-input focus:border-gray-400 focus:outline-none focus:ring-1 focus:ring-gray-400 data-[state=open]:border-gray-400 data-[state=open]:ring-1 data-[state=open]:ring-gray-400;
@apply m-0.5 border border-input data-[state=open]:border-gray-400 data-[state=open]:ring-1 data-[state=open]:ring-gray-400 focus:border-gray-400 focus:outline-none focus:ring-1 focus:ring-gray-400;
}
.agpt-shadow-input {

View File

@@ -13,7 +13,7 @@ const Checkbox = React.forwardRef<
<CheckboxPrimitive.Root
ref={ref}
className={cn(
"peer h-4 w-4 shrink-0 rounded-sm border border-neutral-200 border-neutral-900 shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-neutral-950 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-neutral-900 data-[state=checked]:text-neutral-50 dark:border-neutral-50 dark:border-neutral-800 dark:focus-visible:ring-neutral-300 dark:data-[state=checked]:bg-neutral-50 dark:data-[state=checked]:text-neutral-900",
"peer h-4 w-4 shrink-0 rounded-sm border border-neutral-200 border-neutral-900 shadow data-[state=checked]:bg-neutral-900 data-[state=checked]:text-neutral-50 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-neutral-950 disabled:cursor-not-allowed disabled:opacity-50 dark:border-neutral-50 dark:border-neutral-800 dark:data-[state=checked]:bg-neutral-50 dark:data-[state=checked]:text-neutral-900 dark:focus-visible:ring-neutral-300",
className,
)}
{...props}

View File

@@ -44,7 +44,7 @@ const DialogContent = React.forwardRef<
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-white transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-neutral-950 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-neutral-100 data-[state=open]:text-neutral-500 dark:ring-offset-neutral-950 dark:focus:ring-neutral-300 dark:data-[state=open]:bg-neutral-800 dark:data-[state=open]:text-neutral-400">
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-white transition-opacity data-[state=open]:bg-neutral-100 data-[state=open]:text-neutral-500 hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-neutral-950 focus:ring-offset-2 disabled:pointer-events-none dark:ring-offset-neutral-950 dark:data-[state=open]:bg-neutral-800 dark:data-[state=open]:text-neutral-400 dark:focus:ring-neutral-300">
<Cross2Icon className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>

View File

@@ -31,7 +31,7 @@ const DropdownMenuSubTrigger = React.forwardRef<
<DropdownMenuPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-neutral-100 data-[state=open]:bg-neutral-100 dark:focus:bg-neutral-800 dark:data-[state=open]:bg-neutral-800",
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[state=open]:bg-neutral-100 focus:bg-neutral-100 dark:data-[state=open]:bg-neutral-800 dark:focus:bg-neutral-800",
inset && "pl-8",
className,
)}
@@ -88,7 +88,7 @@ const DropdownMenuItem = React.forwardRef<
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-neutral-100 focus:text-neutral-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-neutral-800 dark:focus:text-neutral-50",
"relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50 focus:bg-neutral-100 focus:text-neutral-900 dark:focus:bg-neutral-800 dark:focus:text-neutral-50",
inset && "pl-8",
className,
)}
@@ -104,7 +104,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-neutral-100 focus:text-neutral-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-neutral-800 dark:focus:text-neutral-50",
"relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50 focus:bg-neutral-100 focus:text-neutral-900 dark:focus:bg-neutral-800 dark:focus:text-neutral-50",
className,
)}
checked={checked}
@@ -128,7 +128,7 @@ const DropdownMenuRadioItem = React.forwardRef<
<DropdownMenuPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-neutral-100 focus:text-neutral-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-neutral-800 dark:focus:text-neutral-50",
"relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50 focus:bg-neutral-100 focus:text-neutral-900 dark:focus:bg-neutral-800 dark:focus:text-neutral-50",
className,
)}
{...props}

View File

@@ -24,7 +24,7 @@ const SelectTrigger = React.forwardRef<
<SelectPrimitive.Trigger
ref={ref}
className={cn(
"agpt-border-input agpt-shadow-input flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border bg-transparent px-3 py-2 text-sm ring-offset-white placeholder:text-neutral-500 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 data-[state=open]:border-gray-400 dark:placeholder:text-neutral-400 [&>span]:line-clamp-1",
"agpt-border-input agpt-shadow-input flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border bg-transparent px-3 py-2 text-sm ring-offset-white placeholder:text-neutral-500 data-[state=open]:border-gray-400 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 dark:placeholder:text-neutral-400 [&>span]:line-clamp-1",
className,
)}
{...props}
@@ -123,7 +123,7 @@ const SelectItem = React.forwardRef<
<SelectPrimitive.Item
ref={ref}
className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-neutral-100 focus:text-neutral-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-neutral-800 dark:focus:text-neutral-50",
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 focus:bg-neutral-100 focus:text-neutral-900 dark:focus:bg-neutral-800 dark:focus:text-neutral-50",
className,
)}
{...props}

View File

@@ -64,7 +64,7 @@ const SheetContent = React.forwardRef<
className={cn(sheetVariants({ side }), className)}
{...props}
>
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-white transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-neutral-950 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-neutral-100 dark:ring-offset-neutral-950 dark:focus:ring-neutral-300 dark:data-[state=open]:bg-neutral-800">
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-white transition-opacity data-[state=open]:bg-neutral-100 hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-neutral-950 focus:ring-offset-2 disabled:pointer-events-none dark:ring-offset-neutral-950 dark:data-[state=open]:bg-neutral-800 dark:focus:ring-neutral-300">
<Cross2Icon className="h-4 w-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>

View File

@@ -58,7 +58,7 @@ const TableRow = React.forwardRef<
<tr
ref={ref}
className={cn(
"border-b transition-colors hover:bg-neutral-100/50 data-[state=selected]:bg-neutral-100 dark:hover:bg-neutral-800/50 dark:data-[state=selected]:bg-neutral-800",
"border-b transition-colors data-[state=selected]:bg-neutral-100 hover:bg-neutral-100/50 dark:data-[state=selected]:bg-neutral-800 dark:hover:bg-neutral-800/50",
className,
)}
{...props}

View File

@@ -11,7 +11,7 @@ const Switch = React.forwardRef<
>(({ className, ...props }, ref) => (
<SwitchPrimitives.Root
className={cn(
"peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-950 focus-visible:ring-offset-2 focus-visible:ring-offset-white disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-neutral-900 data-[state=unchecked]:bg-neutral-200 dark:focus-visible:ring-neutral-300 dark:focus-visible:ring-offset-neutral-950 dark:data-[state=checked]:bg-neutral-50 dark:data-[state=unchecked]:bg-neutral-800",
"peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors data-[state=checked]:bg-neutral-900 data-[state=unchecked]:bg-neutral-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-950 focus-visible:ring-offset-2 focus-visible:ring-offset-white disabled:cursor-not-allowed disabled:opacity-50 dark:data-[state=checked]:bg-neutral-50 dark:data-[state=unchecked]:bg-neutral-800 dark:focus-visible:ring-neutral-300 dark:focus-visible:ring-offset-neutral-950",
className,
)}
{...props}

View File

@@ -14,7 +14,7 @@ export function EmailNotAllowedModal({ isOpen, onClose }: Props) {
>
<Dialog.Content>
<div className="py-4">
<WaitlistErrorContent onClose={onClose} />
<WaitlistErrorContent />
</div>
</Dialog.Content>
</Dialog>

View File

@@ -1,50 +1,39 @@
import { Button } from "../atoms/Button/Button";
import { Text } from "../atoms/Text/Text";
interface WaitlistErrorContentProps {
onClose: () => void;
closeButtonText?: string;
closeButtonVariant?: "primary" | "secondary";
interface Props {
onBackToLogin?: () => void;
}
export function WaitlistErrorContent({
onClose,
closeButtonText = "Close",
closeButtonVariant = "primary",
}: WaitlistErrorContentProps) {
export function WaitlistErrorContent(props: Props) {
return (
<div className="flex flex-col items-center gap-6">
<Text variant="h3">Join the Waitlist</Text>
<Text variant="h3">We&apos;re in closed beta</Text>
<div className="flex flex-col gap-4 text-center">
<Text variant="large-medium" className="text-center">
The AutoGPT Platform is currently in closed beta. Your email address
isn&apos;t on our current allowlist for early access.
</Text>
<Text variant="body" className="text-center">
Join our waitlist to get notified when we open up access!
<Text variant="large" className="text-center">
Looks like your email isn&apos;t in our early access list just yet.
Join the waitlist and we will let you know the moment we open up
access!
</Text>
</div>
<div className="flex gap-3">
<div className="flex gap-2">
<Button
variant="secondary"
onClick={() => {
window.open("https://agpt.co/waitlist", "_blank");
}}
>
Join Waitlist
</Button>
<Button variant={closeButtonVariant} onClick={onClose}>
{closeButtonText}
</Button>
{props.onBackToLogin ? (
<Button variant="secondary" onClick={props.onBackToLogin}>
Back to Login
</Button>
) : null}
</div>
<div className="flex flex-col gap-2">
<Text variant="small" className="text-center text-muted-foreground">
Already signed up for the waitlist? Make sure you&apos;re using the
exact same email address you used when signing up.
</Text>
<Text variant="small" className="text-center text-muted-foreground">
If you&apos;re not sure which email you used or need help, contact us
at{" "}
Already joined? Double-check you are using the same email you signed
up with. Need a hand? Emails us at{" "}
<a
href="mailto:contact@agpt.co"
className="underline hover:text-foreground"
@@ -58,7 +47,7 @@ export function WaitlistErrorContent({
rel="noopener noreferrer"
className="underline hover:text-foreground"
>
reach out on Discord
message us on Discord
</a>
</Text>
</div>

View File

@@ -1,26 +1,20 @@
import { useGetV1ListAllExecutions } from "@/app/api/__generated__/endpoints/graphs/graphs";
import { useGetV2ListLibraryAgents } from "@/app/api/__generated__/endpoints/library/library";
import BackendAPI from "@/lib/autogpt-server-api/client";
import type { GraphExecution, GraphID } from "@/lib/autogpt-server-api/types";
import { useCallback, useEffect, useState } from "react";
import * as Sentry from "@sentry/nextjs";
import { toast } from "sonner";
import {
NotificationState,
categorizeExecutions,
handleExecutionUpdate,
} from "./helpers";
type AgentInfoMap = Map<
string,
{ name: string; description: string; library_agent_id?: string }
>;
import { useLibraryAgents } from "@/hooks/useLibraryAgents/useLibraryAgents";
export function useAgentActivityDropdown() {
const [isOpen, setIsOpen] = useState(false);
const [api] = useState(() => new BackendAPI());
const { agentInfoMap } = useLibraryAgents();
const [notifications, setNotifications] = useState<NotificationState>({
activeExecutions: [],
@@ -30,13 +24,6 @@ export function useAgentActivityDropdown() {
});
const [isConnected, setIsConnected] = useState(false);
const [agentInfoMap, setAgentInfoMap] = useState<AgentInfoMap>(new Map());
const {
data: agents,
isSuccess: agentsSuccess,
error: agentsError,
} = useGetV2ListLibraryAgents();
const {
data: executions,
@@ -46,59 +33,6 @@ export function useAgentActivityDropdown() {
query: { select: (res) => (res.status === 200 ? res.data : null) },
});
// Create a map of library agents
useEffect(() => {
if (agentsError) {
Sentry.captureException(agentsError, {
tags: {
context: "library_agents_fetch",
},
});
toast.error("Failed to load agent information", {
description:
"There was a problem connecting to our servers. Agent activity may be limited.",
});
return;
}
if (agents && agentsSuccess) {
if (agents.status !== 200) {
Sentry.captureException(new Error("Failed to load library agents"), {
extra: {
status: agents.status,
error: agents.data,
},
});
toast.error("Invalid agent data received", {
description:
"The server returned invalid data. Agent activity may be limited.",
});
return;
}
const libraryAgents = agents.data;
if (!libraryAgents.agents || !libraryAgents.agents.length) return;
const agentMap = new Map<
string,
{ name: string; description: string; library_agent_id?: string }
>();
libraryAgents.agents.forEach((agent) => {
if (agent.graph_id && agent.id) {
agentMap.set(agent.graph_id, {
name: agent.name || `Agent ${agent.graph_id.slice(0, 8)}`,
description: agent.description || "",
library_agent_id: agent.id,
});
}
});
setAgentInfoMap(agentMap);
}
}, [agents, agentsSuccess, agentsError]);
// Handle real-time execution updates
const handleExecutionEvent = useCallback(
(execution: GraphExecution) => {
@@ -156,8 +90,8 @@ export function useAgentActivityDropdown() {
return {
...notifications,
isConnected,
isReady: executionsSuccess && agentsSuccess,
error: executionsError || agentsError,
isReady: executionsSuccess,
error: executionsError,
isOpen,
setIsOpen,
};

View File

@@ -45,8 +45,8 @@ export const NavbarView = ({ isLoggedIn }: NavbarViewProps) => {
</div>
{/* Right section */}
<div className="hidden flex-1 items-center justify-end gap-4 md:flex">
{isLoggedIn ? (
{isLoggedIn ? (
<div className="hidden flex-1 items-center justify-end gap-4 md:flex">
<div className="flex items-center gap-4">
<AgentActivityDropdown />
{profile && <Wallet />}
@@ -57,11 +57,13 @@ export const NavbarView = ({ isLoggedIn }: NavbarViewProps) => {
menuItemGroups={dynamicMenuItems}
/>
</div>
) : (
</div>
) : (
<div className="flex w-full items-center justify-end">
<LoginButton />
)}
{/* <ThemeToggle /> */}
</div>
</div>
)}
{/* <ThemeToggle /> */}
</nav>
{/* Mobile Navbar - Adjust positioning */}
<>

View File

@@ -25,7 +25,7 @@ const DropdownMenuSubTrigger = React.forwardRef<
<DropdownMenuPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-neutral-100 data-[state=open]:bg-neutral-100 dark:focus:bg-neutral-800 dark:data-[state=open]:bg-neutral-800",
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[state=open]:bg-neutral-100 focus:bg-neutral-100 dark:data-[state=open]:bg-neutral-800 dark:focus:bg-neutral-800",
inset && "pl-8",
className,
)}
@@ -82,7 +82,7 @@ const DropdownMenuItem = React.forwardRef<
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-neutral-100 focus:text-neutral-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-neutral-800 dark:focus:text-neutral-50",
"relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50 focus:bg-neutral-100 focus:text-neutral-900 dark:focus:bg-neutral-800 dark:focus:text-neutral-50",
inset && "pl-8",
className,
)}
@@ -98,7 +98,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-neutral-100 focus:text-neutral-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-neutral-800 dark:focus:text-neutral-50",
"relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50 focus:bg-neutral-100 focus:text-neutral-900 dark:focus:bg-neutral-800 dark:focus:text-neutral-50",
className,
)}
checked={checked}
@@ -122,7 +122,7 @@ const DropdownMenuRadioItem = React.forwardRef<
<DropdownMenuPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-neutral-100 focus:text-neutral-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-neutral-800 dark:focus:text-neutral-50",
"relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50 focus:bg-neutral-100 focus:text-neutral-900 dark:focus:bg-neutral-800 dark:focus:text-neutral-50",
className,
)}
{...props}

View File

@@ -109,7 +109,7 @@ const TabsLineTrigger = React.forwardRef<
elementRef.current = node;
}}
className={cn(
"relative inline-flex items-center justify-center whitespace-nowrap px-3 py-3 font-sans text-[1rem] font-medium leading-[1.5rem] text-zinc-700 transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-400 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:text-purple-600",
"relative inline-flex items-center justify-center whitespace-nowrap px-3 py-3 font-sans text-[1rem] font-medium leading-[1.5rem] text-zinc-700 transition-all data-[state=active]:text-purple-600 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-400 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
className,
)}
{...props}

View File

@@ -1,155 +1,49 @@
"use client";
import React, { useEffect, useState } from "react";
import { Button } from "../../__legacy__/ui/button";
import React from "react";
import { QuestionMarkCircledIcon } from "@radix-ui/react-icons";
import { useRouter, usePathname } from "next/navigation";
import * as Sentry from "@sentry/nextjs";
import { getCurrentUser } from "@/lib/supabase/actions";
import { useTallyPopup } from "./useTallyPopup";
import { Button } from "@/components/atoms/Button/Button";
const TallyPopupSimple = () => {
const [isFormVisible, setIsFormVisible] = useState(false);
const [sentryReplayId, setSentryReplayId] = useState("");
const [replayUrl, setReplayUrl] = useState("");
const [pageUrl, setPageUrl] = useState("");
const [userAgent, setUserAgent] = useState("");
const [isAuthenticated, setIsAuthenticated] = useState<boolean | null>(null);
// const [userId, setUserId] = useState<string>("");
const [userEmail, setUserEmail] = useState<string>("");
const router = useRouter();
const pathname = usePathname();
export function TallyPopupSimple() {
const { state, handlers } = useTallyPopup();
const [show_tutorial, setShowTutorial] = useState(false);
useEffect(() => {
setShowTutorial(pathname.includes("build"));
}, [pathname]);
useEffect(() => {
// Set client-side values
if (typeof window !== "undefined") {
setPageUrl(window.location.href);
setUserAgent(window.navigator.userAgent);
const replay = Sentry.getReplay();
if (replay) {
const replayId = replay.getReplayId();
if (replayId) {
setSentryReplayId(replayId);
const orgSlug = "significant-gravitas";
setReplayUrl(`https://${orgSlug}.sentry.io/replays/${replayId}/`);
}
}
}
}, [pathname]);
useEffect(() => {
// Check authentication status using server action (works with httpOnly cookies)
getCurrentUser().then(({ user }) => {
setIsAuthenticated(user != null);
// setUserId(user?.id || "");
setUserEmail(user?.email || "");
});
}, [pathname]);
useEffect(() => {
// Load Tally script
const script = document.createElement("script");
script.src = "https://tally.so/widgets/embed.js";
script.async = true;
document.head.appendChild(script);
// Setup event listeners for Tally events
const handleTallyMessage = (event: MessageEvent) => {
if (typeof event.data === "string") {
// Ignore iframe-resizer messages
if (
event.data.startsWith("[iFrameSize") ||
event.data.startsWith("[iFrameResizer")
) {
return;
}
try {
const data = JSON.parse(event.data);
// Only process Tally events
if (!data.event?.startsWith("Tally.")) {
return;
}
if (data.event === "Tally.FormLoaded") {
setIsFormVisible(true);
// Flush Sentry replay when form opens
if (typeof window !== "undefined") {
const replay = Sentry.getReplay();
if (replay) {
replay.flush();
}
}
} else if (data.event === "Tally.PopupClosed") {
setIsFormVisible(false);
}
} catch (error) {
// Only log errors for messages we care about
if (event.data.includes("Tally")) {
console.error("Error parsing Tally message:", error);
}
}
}
};
window.addEventListener("message", handleTallyMessage);
return () => {
document.head.removeChild(script);
window.removeEventListener("message", handleTallyMessage);
};
}, []);
if (isFormVisible) {
if (state.isFormVisible) {
return null;
}
const resetTutorial = () => {
router.push("/build?resetTutorial=true");
};
return (
<div className="fixed bottom-1 right-24 z-20 hidden select-none items-center gap-4 p-3 transition-all duration-300 ease-in-out md:flex">
{show_tutorial && (
{state.showTutorial && (
<Button
variant="default"
onClick={resetTutorial}
variant="primary"
onClick={handlers.handleResetTutorial}
className="mb-0 h-14 w-28 rounded-2xl bg-[rgba(65,65,64,1)] text-left font-sans text-lg font-medium leading-6"
>
Tutorial
</Button>
)}
<Button
className="h-14 w-14 rounded-full bg-[rgba(65,65,64,1)]"
variant="default"
variant="primary"
data-tally-open="3yx2L0"
data-tally-emoji-text="👋"
data-tally-emoji-animation="wave"
data-sentry-replay-id={sentryReplayId || "not-initialized"}
data-sentry-replay-url={replayUrl || "not-initialized"}
data-user-agent={userAgent}
data-page-url={pageUrl}
data-sentry-replay-id={state.sentryReplayId || "not-initialized"}
data-sentry-replay-url={state.replayUrl || "not-initialized"}
data-user-agent={state.userAgent}
data-page-url={state.pageUrl}
data-is-authenticated={
isAuthenticated === null ? "unknown" : String(isAuthenticated)
state.isAuthenticated === null
? "unknown"
: String(state.isAuthenticated)
}
data-email={userEmail || "not-authenticated"}
// data-user-id={userId || "not-authenticated"}
data-email={state.userEmail || "not-authenticated"}
>
<QuestionMarkCircledIcon className="h-14 w-14" />
<span className="sr-only">Reach Out</span>
<QuestionMarkCircledIcon className="h-6 w-6" />
<span className="">Give Feedback</span>
</Button>
</div>
);
};
}
export default TallyPopupSimple;

View File

@@ -0,0 +1,126 @@
import { usePathname, useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import * as Sentry from "@sentry/nextjs";
import { getCurrentUser } from "@/lib/supabase/actions";
export function useTallyPopup() {
const [isFormVisible, setIsFormVisible] = useState(false);
const [sentryReplayId, setSentryReplayId] = useState("");
const [replayUrl, setReplayUrl] = useState("");
const [pageUrl, setPageUrl] = useState("");
const [userAgent, setUserAgent] = useState("");
const [isAuthenticated, setIsAuthenticated] = useState<boolean | null>(null);
const [userEmail, setUserEmail] = useState<string>("");
const router = useRouter();
const pathname = usePathname();
const [showTutorial, setShowTutorial] = useState(false);
useEffect(() => {
setShowTutorial(pathname.includes("build"));
}, [pathname]);
useEffect(() => {
// Set client-side values
if (typeof window !== "undefined") {
setPageUrl(window.location.href);
setUserAgent(window.navigator.userAgent);
const replay = Sentry.getReplay();
if (replay) {
const replayId = replay.getReplayId();
if (replayId) {
setSentryReplayId(replayId);
const orgSlug = "significant-gravitas";
setReplayUrl(`https://${orgSlug}.sentry.io/replays/${replayId}/`);
}
}
}
}, [pathname]);
useEffect(() => {
// Check authentication status using server action (works with httpOnly cookies)
getCurrentUser().then(({ user }) => {
setIsAuthenticated(user != null);
setUserEmail(user?.email || "");
});
}, [pathname]);
useEffect(() => {
// Load Tally script
const script = document.createElement("script");
script.src = "https://tally.so/widgets/embed.js";
script.async = true;
document.head.appendChild(script);
// Setup event listeners for Tally events
const handleTallyMessage = (event: MessageEvent) => {
if (typeof event.data === "string") {
// Ignore iframe-resizer messages
if (
event.data.startsWith("[iFrameSize") ||
event.data.startsWith("[iFrameResizer")
) {
return;
}
try {
const data = JSON.parse(event.data);
// Only process Tally events
if (!data.event?.startsWith("Tally.")) {
return;
}
if (data.event === "Tally.FormLoaded") {
setIsFormVisible(true);
// Flush Sentry replay when form opens
if (typeof window !== "undefined") {
const replay = Sentry.getReplay();
if (replay) {
replay.flush();
}
}
} else if (data.event === "Tally.PopupClosed") {
setIsFormVisible(false);
}
} catch (error) {
// Only log errors for messages we care about
if (event.data.includes("Tally")) {
console.error("Error parsing Tally message:", error);
}
}
}
};
window.addEventListener("message", handleTallyMessage);
return () => {
document.head.removeChild(script);
window.removeEventListener("message", handleTallyMessage);
};
}, []);
function handleResetTutorial() {
router.push("/build?resetTutorial=true");
}
return {
state: {
showTutorial,
sentryReplayId,
replayUrl,
pageUrl,
userAgent,
isAuthenticated,
isFormVisible,
userEmail,
},
handlers: {
handleResetTutorial,
},
};
}

View File

@@ -21,17 +21,15 @@ import {
Node,
} from "@/lib/autogpt-server-api";
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
import {
deepEquals,
getTypeColor,
removeEmptyStringsAndNulls,
} from "@/lib/utils";
import { deepEquals, getTypeColor, pruneEmptyValues } from "@/lib/utils";
import { MarkerType } from "@xyflow/react";
import { default as NextLink } from "next/link";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Flag, useGetFlag } from "@/services/feature-flags/use-get-flag";
import { useOnboarding } from "@/providers/onboarding/onboarding-provider";
import { useQueryClient } from "@tanstack/react-query";
import { getGetV2ListLibraryAgentsQueryKey } from "@/app/api/__generated__/endpoints/library/library";
export default function useAgentGraph(
flowID?: GraphID,
@@ -44,6 +42,7 @@ export default function useAgentGraph(
const pathname = usePathname();
const searchParams = useSearchParams();
const api = useBackendAPI();
const queryClient = useQueryClient();
const [isScheduling, setIsScheduling] = useState(false);
const [savedAgent, setSavedAgent] = useState<Graph | null>(null);
@@ -598,7 +597,10 @@ export default function useAgentGraph(
return {};
}
return rebuildObjectUsingSchema(blockSchema, node.data.hardcodedValues);
return rebuildObjectUsingSchema(
blockSchema,
pruneEmptyValues(node.data.hardcodedValues),
);
},
[availableBlocks],
);
@@ -607,6 +609,7 @@ export default function useAgentGraph(
const links = xyEdges.map((edge): LinkCreatable => {
let sourceName = edge.sourceHandle || "";
const sourceNode = xyNodes.find((node) => node.id === edge.source);
const sinkNode = xyNodes.find((node) => node.id === edge.target);
// Special case for SmartDecisionMakerBlock
if (
@@ -616,8 +619,8 @@ export default function useAgentGraph(
sourceName = `tools_^_${normalizeToolName(getToolFuncName(edge.target))}_~_${normalizeToolName(edge.targetHandle || "")}`;
}
return {
source_id: edge.source,
sink_id: edge.target,
source_id: sourceNode?.data.backend_id ?? edge.source,
sink_id: sinkNode?.data.backend_id ?? edge.target,
source_name: sourceName,
sink_name: edge.targetHandle || "",
};
@@ -629,7 +632,7 @@ export default function useAgentGraph(
recommended_schedule_cron: agentRecommendedScheduleCron || null,
nodes: xyNodes.map(
(node): NodeCreatable => ({
id: node.id,
id: node.data.backend_id ?? node.id,
block_id: node.data.block_id,
input_default: prepareNodeInputData(node),
metadata: {
@@ -695,8 +698,9 @@ export default function useAgentGraph(
// Update the node IDs on the frontend
setSavedAgent(newSavedAgent);
setXYNodes((prev) => {
return newSavedAgent.nodes
setXYNodes((prev) =>
newSavedAgent.nodes
.map((backendNode) => {
const key = `${backendNode.block_id}_${backendNode.metadata.position.x}_${backendNode.metadata.position.y}`;
const frontendNodeID = blockIDToNodeIDMap[key];
@@ -709,22 +713,26 @@ export default function useAgentGraph(
position,
data: {
...frontendNode.data,
hardcodedValues: removeEmptyStringsAndNulls(
frontendNode.data.hardcodedValues,
),
status: undefined,
// NOTE: we don't update `node.id` because it would also require
// updating many references in other places. Instead, we keep the
// backend node ID in `node.data.backend_id`.
backend_id: backendNode.id,
executionResults: [],
metadata,
// Reset & close node output
isOutputOpen: false,
status: undefined,
executionResults: undefined,
},
} satisfies CustomNode)
: _backendNodeToXYNode(backendNode, newSavedAgent); // fallback
})
.filter((node) => node !== null);
});
.filter((node) => node !== null),
);
// Reset bead count
setXYEdges((edges) => {
return edges.map(
setXYEdges((edges) =>
edges.map(
(edge): CustomEdge => ({
...edge,
data: {
@@ -735,8 +743,8 @@ export default function useAgentGraph(
beadData: new Map(),
},
}),
);
});
),
);
return newSavedAgent;
}, [
api,
@@ -755,6 +763,11 @@ export default function useAgentGraph(
setIsSaving(true);
try {
await _saveAgent();
await queryClient.invalidateQueries({
queryKey: getGetV2ListLibraryAgentsQueryKey(),
});
completeStep("BUILDER_SAVE_AGENT");
} catch (error) {
const errorMessage =

View File

@@ -0,0 +1,128 @@
import { create } from "zustand";
import * as Sentry from "@sentry/nextjs";
import { storage, Key } from "@/services/storage/local-storage";
import {
getV2ListLibraryAgents,
type getV2ListLibraryAgentsResponse,
} from "@/app/api/__generated__/endpoints/library/library";
import type { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
export type AgentInfo = LibraryAgent;
type AgentStore = {
agents: AgentInfo[];
lastUpdatedAt?: number;
isRefreshing: boolean;
error?: unknown;
loadFromCache: () => void;
refreshAll: () => Promise<void>;
};
type CachedAgents = {
agents: LibraryAgent[];
lastUpdatedAt: number;
};
async function fetchAllLibraryAgents() {
const pageSize = 100;
let page = 1;
const all: LibraryAgent[] = [];
let res: getV2ListLibraryAgentsResponse | undefined;
try {
res = await getV2ListLibraryAgents({ page, page_size: pageSize });
} catch (err) {
Sentry.captureException(err, { tags: { context: "library_agents_fetch" } });
throw err;
}
if (!res || res.status !== 200) return all;
const { agents, pagination } = res.data;
all.push(...agents);
const totalPages = pagination?.total_pages ?? 1;
for (page = 2; page <= totalPages; page += 1) {
try {
const next = await getV2ListLibraryAgents({ page, page_size: pageSize });
if (next.status === 200) {
all.push(...next.data.agents);
}
} catch (err) {
Sentry.captureException(err, {
tags: { context: "library_agents_fetch" },
});
}
}
return all;
}
function persistCache(cached: CachedAgents) {
try {
storage.set(Key.LIBRARY_AGENTS_CACHE, JSON.stringify(cached));
} catch (error) {
// Ignore cache failures
// eslint-disable-next-line no-console
console.error("Failed to persist library agents cache", error);
Sentry.captureException(error, {
tags: { context: "library_agents_cache_persist" },
});
}
}
function readCache(): CachedAgents | undefined {
try {
const raw = storage.get(Key.LIBRARY_AGENTS_CACHE);
if (!raw) return;
return JSON.parse(raw) as CachedAgents;
} catch {
return;
}
}
export const useLibraryAgentsStore = create<AgentStore>((set, get) => ({
agents: [],
lastUpdatedAt: undefined,
isRefreshing: false,
error: undefined,
loadFromCache: () => {
const cached = readCache();
if (cached?.agents?.length) {
set({ agents: cached.agents, lastUpdatedAt: cached.lastUpdatedAt });
}
},
refreshAll: async () => {
if (get().isRefreshing) return;
set({ isRefreshing: true, error: undefined });
try {
const agents = await fetchAllLibraryAgents();
const snapshot: CachedAgents = { agents, lastUpdatedAt: Date.now() };
persistCache(snapshot);
set({ agents, lastUpdatedAt: snapshot.lastUpdatedAt });
} catch (error) {
set({ error });
} finally {
set({ isRefreshing: false });
}
},
}));
export function buildAgentInfoMap(agents: AgentInfo[]) {
const map = new Map<
string,
{ name: string; description: string; library_agent_id?: string }
>();
agents.forEach((a) => {
if (a.graph_id && a.id) {
map.set(a.graph_id, {
name:
a.name || (a.graph_id ? `Agent ${a.graph_id.slice(0, 8)}` : "Agent"),
description: a.description || "",
library_agent_id: a.id,
});
}
});
return map;
}

View File

@@ -0,0 +1,21 @@
import { useEffect, useMemo } from "react";
import { buildAgentInfoMap, useLibraryAgentsStore } from "./store";
let initialized = false;
export function useLibraryAgents() {
const { agents, isRefreshing, lastUpdatedAt, loadFromCache, refreshAll } =
useLibraryAgentsStore();
useEffect(() => {
if (!initialized) {
loadFromCache();
void refreshAll();
initialized = true;
}
}, [loadFromCache, refreshAll]);
const agentInfoMap = useMemo(() => buildAgentInfoMap(agents), [agents]);
return { agents, agentInfoMap, isRefreshing, lastUpdatedAt };
}

View File

@@ -2,7 +2,11 @@ import { type ClassValue, clsx } from "clsx";
import { isEmpty as _isEmpty } from "lodash";
import { twMerge } from "tailwind-merge";
import { Category } from "@/lib/autogpt-server-api/types";
import {
BlockIOObjectSubSchema,
BlockIORootSchema,
Category,
} from "@/lib/autogpt-server-api/types";
import { NodeDimension } from "@/app/(platform)/build/components/legacy-builder/Flow/Flow";
export function cn(...inputs: ClassValue[]) {
@@ -177,32 +181,73 @@ export function setNestedProperty(obj: any, path: string, value: any) {
current[keys[keys.length - 1]] = value;
}
export function removeEmptyStringsAndNulls(obj: any): any {
export function pruneEmptyValues(
obj: any,
removeEmptyStrings: boolean = true,
): any {
if (Array.isArray(obj)) {
// If obj is an array, recursively check each element,
// but element removal is avoided to prevent index changes.
return obj.map((item) =>
item === undefined || item === null
? ""
: removeEmptyStringsAndNulls(item),
: pruneEmptyValues(item, removeEmptyStrings),
);
} else if (typeof obj === "object" && obj !== null) {
// If obj is an object, recursively remove empty strings and nulls from its properties
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
const value = obj[key];
if (
value === null ||
value === undefined ||
(typeof value === "string" && value === "")
) {
delete obj[key];
} else {
obj[key] = removeEmptyStringsAndNulls(value);
if (!obj.hasOwnProperty(key)) continue;
const value = obj[key];
if (
value === null ||
value === undefined ||
(typeof value === "string" && value === "" && removeEmptyStrings)
) {
delete obj[key];
} else if (typeof value === "object") {
obj[key] = pruneEmptyValues(value, removeEmptyStrings);
}
}
}
return obj;
}
export function fillObjectDefaultsFromSchema(
obj: Record<any, any>,
schema: BlockIORootSchema | BlockIOObjectSubSchema,
) {
for (const key in schema.properties) {
if (!schema.properties.hasOwnProperty(key)) continue;
const propertySchema = schema.properties[key];
if ("default" in propertySchema && propertySchema.default !== undefined) {
// Apply simple default values
obj[key] ??= propertySchema.default;
} else if (
propertySchema.type === "object" &&
"properties" in propertySchema
) {
// Recursively fill defaults for nested objects
obj[key] = fillObjectDefaultsFromSchema(obj[key] ?? {}, propertySchema);
} else if (propertySchema.type === "array") {
obj[key] ??= [];
// If the array items are objects, fill their defaults as well
if (
Array.isArray(obj[key]) &&
propertySchema.items?.type === "object" &&
"properties" in propertySchema.items
) {
for (const item of obj[key]) {
if (typeof item === "object" && item !== null) {
fillObjectDefaultsFromSchema(item, propertySchema.items);
}
}
}
}
}
return obj;
}

View File

@@ -7,6 +7,7 @@ export enum Key {
COPIED_FLOW_DATA = "copied-flow-data",
SHEPHERD_TOUR = "shepherd-tour",
WALLET_LAST_SEEN_CREDITS = "wallet-last-seen-credits",
LIBRARY_AGENTS_CACHE = "library-agents-cache",
}
function get(key: Key) {

View File

@@ -74,46 +74,6 @@ test.describe("Library", () => {
test.expect(allAgents.length).toEqual(displayedCount);
});
test("sorting works correctly", async ({ page }) => {
await page.goto("/library");
const initialAgents = await libraryPage.getAgents();
expect(initialAgents.length).toBeGreaterThan(0);
await libraryPage.selectSortOption(page, "Creation Date");
await libraryPage.waitForAgentsToLoad();
const creationDateSortOption = await libraryPage.getCurrentSortOption();
expect(creationDateSortOption).toContain("Creation Date");
const creationDateAgents = await libraryPage.getAgents();
expect(creationDateAgents.length).toBeGreaterThan(0);
await libraryPage.selectSortOption(page, "Last Modified");
await libraryPage.waitForAgentsToLoad();
const lastModifiedSortOption = await libraryPage.getCurrentSortOption();
expect(lastModifiedSortOption).toContain("Last Modified");
const lastModifiedAgents = await libraryPage.getAgents();
expect(lastModifiedAgents.length).toBeGreaterThan(0);
if (initialAgents.length > 1) {
const initialFirstAgentId = initialAgents[0].id;
const creationDateFirstAgentId = creationDateAgents[0].id;
const lastModifiedFirstAgentId = lastModifiedAgents[0].id;
expect(
creationDateFirstAgentId !== initialFirstAgentId ||
lastModifiedFirstAgentId !== initialFirstAgentId ||
creationDateFirstAgentId !== lastModifiedFirstAgentId,
).toBeTruthy();
}
expect(creationDateAgents.length).toEqual(initialAgents.length);
expect(lastModifiedAgents.length).toEqual(initialAgents.length);
});
test("searching works correctly", async ({ page }) => {
await page.goto("/library");