mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-02-09 14:25:25 -05:00
Compare commits
32 Commits
fix/execut
...
claude/fix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a2435067c | ||
|
|
519d4bd67e | ||
|
|
4350ad95d7 | ||
|
|
8769104242 | ||
|
|
22c486583a | ||
|
|
87d4bcd025 | ||
|
|
c2c7cc94cf | ||
|
|
e6eef0379e | ||
|
|
cc2c183a5f | ||
|
|
73fcadf0e8 | ||
|
|
44d17885ed | ||
|
|
21652a5422 | ||
|
|
eed07b173a | ||
|
|
c5e8b0b08f | ||
|
|
cd3e35df9e | ||
|
|
4a7bc006a8 | ||
|
|
4c474417bc | ||
|
|
99e2261254 | ||
|
|
cab498fa8c | ||
|
|
22078671df | ||
|
|
0082a72657 | ||
|
|
9a1d940677 | ||
|
|
1a8ed4c291 | ||
|
|
c5acb0d4cc | ||
|
|
fc0d0903f2 | ||
|
|
e640d36265 | ||
|
|
ca53b752d2 | ||
|
|
1d3d60f7ef | ||
|
|
cc9179178f | ||
|
|
e8d37ab116 | ||
|
|
7f7ef6a271 | ||
|
|
aefac541d9 |
@@ -11,7 +11,7 @@ jobs:
|
|||||||
stale:
|
stale:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v9
|
- uses: actions/stale@v10
|
||||||
with:
|
with:
|
||||||
# operations-per-run: 5000
|
# operations-per-run: 5000
|
||||||
stale-issue-message: >
|
stale-issue-message: >
|
||||||
|
|||||||
2
.github/workflows/repo-pr-label.yml
vendored
2
.github/workflows/repo-pr-label.yml
vendored
@@ -61,6 +61,6 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/labeler@v5
|
- uses: actions/labeler@v6
|
||||||
with:
|
with:
|
||||||
sync-labels: true
|
sync-labels: true
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import logging
|
import logging
|
||||||
from typing import Any, Literal
|
from typing import Any
|
||||||
|
|
||||||
from prisma.enums import ReviewStatus
|
from prisma.enums import ReviewStatus
|
||||||
|
|
||||||
@@ -45,11 +45,11 @@ class HumanInTheLoopBlock(Block):
|
|||||||
)
|
)
|
||||||
|
|
||||||
class Output(BlockSchemaOutput):
|
class Output(BlockSchemaOutput):
|
||||||
reviewed_data: Any = SchemaField(
|
approved_data: Any = SchemaField(
|
||||||
description="The data after human review (may be modified)"
|
description="The data when approved (may be modified by reviewer)"
|
||||||
)
|
)
|
||||||
status: Literal["approved", "rejected"] = SchemaField(
|
rejected_data: Any = SchemaField(
|
||||||
description="Status of the review: 'approved' or 'rejected'"
|
description="The data when rejected (may be modified by reviewer)"
|
||||||
)
|
)
|
||||||
review_message: str = SchemaField(
|
review_message: str = SchemaField(
|
||||||
description="Any message provided by the reviewer", default=""
|
description="Any message provided by the reviewer", default=""
|
||||||
@@ -69,8 +69,7 @@ class HumanInTheLoopBlock(Block):
|
|||||||
"editable": True,
|
"editable": True,
|
||||||
},
|
},
|
||||||
test_output=[
|
test_output=[
|
||||||
("status", "approved"),
|
("approved_data", {"name": "John Doe", "age": 30}),
|
||||||
("reviewed_data", {"name": "John Doe", "age": 30}),
|
|
||||||
],
|
],
|
||||||
test_mock={
|
test_mock={
|
||||||
"get_or_create_human_review": lambda *_args, **_kwargs: ReviewResult(
|
"get_or_create_human_review": lambda *_args, **_kwargs: ReviewResult(
|
||||||
@@ -116,8 +115,7 @@ class HumanInTheLoopBlock(Block):
|
|||||||
logger.info(
|
logger.info(
|
||||||
f"HITL block skipping review for node {node_exec_id} - safe mode disabled"
|
f"HITL block skipping review for node {node_exec_id} - safe mode disabled"
|
||||||
)
|
)
|
||||||
yield "status", "approved"
|
yield "approved_data", input_data.data
|
||||||
yield "reviewed_data", input_data.data
|
|
||||||
yield "review_message", "Auto-approved (safe mode disabled)"
|
yield "review_message", "Auto-approved (safe mode disabled)"
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -158,12 +156,11 @@ class HumanInTheLoopBlock(Block):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if result.status == ReviewStatus.APPROVED:
|
if result.status == ReviewStatus.APPROVED:
|
||||||
yield "status", "approved"
|
yield "approved_data", result.data
|
||||||
yield "reviewed_data", result.data
|
|
||||||
if result.message:
|
if result.message:
|
||||||
yield "review_message", result.message
|
yield "review_message", result.message
|
||||||
|
|
||||||
elif result.status == ReviewStatus.REJECTED:
|
elif result.status == ReviewStatus.REJECTED:
|
||||||
yield "status", "rejected"
|
yield "rejected_data", result.data
|
||||||
if result.message:
|
if result.message:
|
||||||
yield "review_message", result.message
|
yield "review_message", result.message
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ async def get_or_create_human_review(
|
|||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
return ReviewResult(
|
return ReviewResult(
|
||||||
data=review.payload if review.status == ReviewStatus.APPROVED else None,
|
data=review.payload,
|
||||||
status=review.status,
|
status=review.status,
|
||||||
message=review.reviewMessage or "",
|
message=review.reviewMessage or "",
|
||||||
processed=review.processed,
|
processed=review.processed,
|
||||||
|
|||||||
@@ -442,6 +442,8 @@ async def get_recommended_agents(user_id: str) -> list[StoreAgentDetails]:
|
|||||||
runs=agent.runs,
|
runs=agent.runs,
|
||||||
rating=agent.rating,
|
rating=agent.rating,
|
||||||
versions=agent.versions,
|
versions=agent.versions,
|
||||||
|
agentGraphVersions=agent.agentGraphVersions,
|
||||||
|
agentGraphId=agent.agentGraphId,
|
||||||
last_updated=agent.updated_at,
|
last_updated=agent.updated_at,
|
||||||
)
|
)
|
||||||
for agent in recommended_agents
|
for agent in recommended_agents
|
||||||
|
|||||||
@@ -134,18 +134,14 @@ async def process_review_action(
|
|||||||
# Build review decisions map
|
# Build review decisions map
|
||||||
review_decisions = {}
|
review_decisions = {}
|
||||||
for review in request.reviews:
|
for review in request.reviews:
|
||||||
if review.approved:
|
review_status = (
|
||||||
review_decisions[review.node_exec_id] = (
|
ReviewStatus.APPROVED if review.approved else ReviewStatus.REJECTED
|
||||||
ReviewStatus.APPROVED,
|
)
|
||||||
review.reviewed_data,
|
review_decisions[review.node_exec_id] = (
|
||||||
review.message,
|
review_status,
|
||||||
)
|
review.reviewed_data,
|
||||||
else:
|
review.message,
|
||||||
review_decisions[review.node_exec_id] = (
|
)
|
||||||
ReviewStatus.REJECTED,
|
|
||||||
None,
|
|
||||||
review.message,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Process all reviews
|
# Process all reviews
|
||||||
updated_reviews = await process_all_reviews_for_execution(
|
updated_reviews = await process_all_reviews_for_execution(
|
||||||
|
|||||||
@@ -538,6 +538,7 @@ async def update_library_agent(
|
|||||||
library_agent_id: str,
|
library_agent_id: str,
|
||||||
user_id: str,
|
user_id: str,
|
||||||
auto_update_version: Optional[bool] = None,
|
auto_update_version: Optional[bool] = None,
|
||||||
|
graph_version: Optional[int] = None,
|
||||||
is_favorite: Optional[bool] = None,
|
is_favorite: Optional[bool] = None,
|
||||||
is_archived: Optional[bool] = None,
|
is_archived: Optional[bool] = None,
|
||||||
is_deleted: Optional[Literal[False]] = None,
|
is_deleted: Optional[Literal[False]] = None,
|
||||||
@@ -550,6 +551,7 @@ async def update_library_agent(
|
|||||||
library_agent_id: The ID of the LibraryAgent to update.
|
library_agent_id: The ID of the LibraryAgent to update.
|
||||||
user_id: The owner of this LibraryAgent.
|
user_id: The owner of this LibraryAgent.
|
||||||
auto_update_version: Whether the agent should auto-update to active version.
|
auto_update_version: Whether the agent should auto-update to active version.
|
||||||
|
graph_version: Specific graph version to update to.
|
||||||
is_favorite: Whether this agent is marked as a favorite.
|
is_favorite: Whether this agent is marked as a favorite.
|
||||||
is_archived: Whether this agent is archived.
|
is_archived: Whether this agent is archived.
|
||||||
settings: User-specific settings for this library agent.
|
settings: User-specific settings for this library agent.
|
||||||
@@ -563,8 +565,8 @@ async def update_library_agent(
|
|||||||
"""
|
"""
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"Updating library agent {library_agent_id} for user {user_id} with "
|
f"Updating library agent {library_agent_id} for user {user_id} with "
|
||||||
f"auto_update_version={auto_update_version}, is_favorite={is_favorite}, "
|
f"auto_update_version={auto_update_version}, graph_version={graph_version}, "
|
||||||
f"is_archived={is_archived}, settings={settings}"
|
f"is_favorite={is_favorite}, is_archived={is_archived}, settings={settings}"
|
||||||
)
|
)
|
||||||
update_fields: prisma.types.LibraryAgentUpdateManyMutationInput = {}
|
update_fields: prisma.types.LibraryAgentUpdateManyMutationInput = {}
|
||||||
if auto_update_version is not None:
|
if auto_update_version is not None:
|
||||||
@@ -581,10 +583,23 @@ async def update_library_agent(
|
|||||||
update_fields["isDeleted"] = is_deleted
|
update_fields["isDeleted"] = is_deleted
|
||||||
if settings is not None:
|
if settings is not None:
|
||||||
update_fields["settings"] = SafeJson(settings.model_dump())
|
update_fields["settings"] = SafeJson(settings.model_dump())
|
||||||
if not update_fields:
|
|
||||||
raise ValueError("No values were passed to update")
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# If graph_version is provided, update to that specific version
|
||||||
|
if graph_version is not None:
|
||||||
|
# Get the current agent to find its graph_id
|
||||||
|
agent = await get_library_agent(id=library_agent_id, user_id=user_id)
|
||||||
|
# Update to the specified version using existing function
|
||||||
|
return await update_agent_version_in_library(
|
||||||
|
user_id=user_id,
|
||||||
|
agent_graph_id=agent.graph_id,
|
||||||
|
agent_graph_version=graph_version,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Otherwise, just update the simple fields
|
||||||
|
if not update_fields:
|
||||||
|
raise ValueError("No values were passed to update")
|
||||||
|
|
||||||
n_updated = await prisma.models.LibraryAgent.prisma().update_many(
|
n_updated = await prisma.models.LibraryAgent.prisma().update_many(
|
||||||
where={"id": library_agent_id, "userId": user_id},
|
where={"id": library_agent_id, "userId": user_id},
|
||||||
data=update_fields,
|
data=update_fields,
|
||||||
|
|||||||
@@ -385,6 +385,9 @@ class LibraryAgentUpdateRequest(pydantic.BaseModel):
|
|||||||
auto_update_version: Optional[bool] = pydantic.Field(
|
auto_update_version: Optional[bool] = pydantic.Field(
|
||||||
default=None, description="Auto-update the agent version"
|
default=None, description="Auto-update the agent version"
|
||||||
)
|
)
|
||||||
|
graph_version: Optional[int] = pydantic.Field(
|
||||||
|
default=None, description="Specific graph version to update to"
|
||||||
|
)
|
||||||
is_favorite: Optional[bool] = pydantic.Field(
|
is_favorite: Optional[bool] = pydantic.Field(
|
||||||
default=None, description="Mark the agent as a favorite"
|
default=None, description="Mark the agent as a favorite"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -284,6 +284,7 @@ async def update_library_agent(
|
|||||||
library_agent_id=library_agent_id,
|
library_agent_id=library_agent_id,
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
auto_update_version=payload.auto_update_version,
|
auto_update_version=payload.auto_update_version,
|
||||||
|
graph_version=payload.graph_version,
|
||||||
is_favorite=payload.is_favorite,
|
is_favorite=payload.is_favorite,
|
||||||
is_archived=payload.is_archived,
|
is_archived=payload.is_archived,
|
||||||
settings=payload.settings,
|
settings=payload.settings,
|
||||||
|
|||||||
@@ -42,10 +42,12 @@ async def _get_cached_store_agents(
|
|||||||
|
|
||||||
# Cache individual agent details for 15 minutes
|
# Cache individual agent details for 15 minutes
|
||||||
@cached(maxsize=200, ttl_seconds=300, shared_cache=True)
|
@cached(maxsize=200, ttl_seconds=300, shared_cache=True)
|
||||||
async def _get_cached_agent_details(username: str, agent_name: str):
|
async def _get_cached_agent_details(
|
||||||
|
username: str, agent_name: str, include_changelog: bool = False
|
||||||
|
):
|
||||||
"""Cached helper to get agent details."""
|
"""Cached helper to get agent details."""
|
||||||
return await backend.server.v2.store.db.get_store_agent_details(
|
return await backend.server.v2.store.db.get_store_agent_details(
|
||||||
username=username, agent_name=agent_name
|
username=username, agent_name=agent_name, include_changelog=include_changelog
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -256,7 +256,7 @@ async def log_search_term(search_query: str):
|
|||||||
|
|
||||||
|
|
||||||
async def get_store_agent_details(
|
async def get_store_agent_details(
|
||||||
username: str, agent_name: str
|
username: str, agent_name: str, include_changelog: bool = False
|
||||||
) -> backend.server.v2.store.model.StoreAgentDetails:
|
) -> backend.server.v2.store.model.StoreAgentDetails:
|
||||||
"""Get PUBLIC store agent details from the StoreAgent view"""
|
"""Get PUBLIC store agent details from the StoreAgent view"""
|
||||||
logger.debug(f"Getting store agent details for {username}/{agent_name}")
|
logger.debug(f"Getting store agent details for {username}/{agent_name}")
|
||||||
@@ -321,6 +321,27 @@ async def get_store_agent_details(
|
|||||||
else:
|
else:
|
||||||
recommended_schedule_cron = None
|
recommended_schedule_cron = None
|
||||||
|
|
||||||
|
# Fetch changelog data if requested
|
||||||
|
changelog_data = None
|
||||||
|
if include_changelog and store_listing:
|
||||||
|
changelog_versions = (
|
||||||
|
await prisma.models.StoreListingVersion.prisma().find_many(
|
||||||
|
where={
|
||||||
|
"storeListingId": store_listing.id,
|
||||||
|
"submissionStatus": prisma.enums.SubmissionStatus.APPROVED,
|
||||||
|
},
|
||||||
|
order=[{"version": "desc"}],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
changelog_data = [
|
||||||
|
backend.server.v2.store.model.ChangelogEntry(
|
||||||
|
version=str(version.version),
|
||||||
|
changes_summary=version.changesSummary or "No changes recorded",
|
||||||
|
date=version.createdAt,
|
||||||
|
)
|
||||||
|
for version in changelog_versions
|
||||||
|
]
|
||||||
|
|
||||||
logger.debug(f"Found agent details for {username}/{agent_name}")
|
logger.debug(f"Found agent details for {username}/{agent_name}")
|
||||||
return backend.server.v2.store.model.StoreAgentDetails(
|
return backend.server.v2.store.model.StoreAgentDetails(
|
||||||
store_listing_version_id=agent.storeListingVersionId,
|
store_listing_version_id=agent.storeListingVersionId,
|
||||||
@@ -337,10 +358,13 @@ async def get_store_agent_details(
|
|||||||
runs=agent.runs,
|
runs=agent.runs,
|
||||||
rating=agent.rating,
|
rating=agent.rating,
|
||||||
versions=agent.versions,
|
versions=agent.versions,
|
||||||
|
agentGraphVersions=agent.agentGraphVersions,
|
||||||
|
agentGraphId=agent.agentGraphId,
|
||||||
last_updated=agent.updated_at,
|
last_updated=agent.updated_at,
|
||||||
active_version_id=active_version_id,
|
active_version_id=active_version_id,
|
||||||
has_approved_version=has_approved_version,
|
has_approved_version=has_approved_version,
|
||||||
recommended_schedule_cron=recommended_schedule_cron,
|
recommended_schedule_cron=recommended_schedule_cron,
|
||||||
|
changelog=changelog_data,
|
||||||
)
|
)
|
||||||
except backend.server.v2.store.exceptions.AgentNotFoundError:
|
except backend.server.v2.store.exceptions.AgentNotFoundError:
|
||||||
raise
|
raise
|
||||||
@@ -408,6 +432,8 @@ async def get_store_agent_by_version_id(
|
|||||||
runs=agent.runs,
|
runs=agent.runs,
|
||||||
rating=agent.rating,
|
rating=agent.rating,
|
||||||
versions=agent.versions,
|
versions=agent.versions,
|
||||||
|
agentGraphVersions=agent.agentGraphVersions,
|
||||||
|
agentGraphId=agent.agentGraphId,
|
||||||
last_updated=agent.updated_at,
|
last_updated=agent.updated_at,
|
||||||
)
|
)
|
||||||
except backend.server.v2.store.exceptions.AgentNotFoundError:
|
except backend.server.v2.store.exceptions.AgentNotFoundError:
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ async def test_get_store_agents(mocker):
|
|||||||
runs=10,
|
runs=10,
|
||||||
rating=4.5,
|
rating=4.5,
|
||||||
versions=["1.0"],
|
versions=["1.0"],
|
||||||
|
agentGraphVersions=["1"],
|
||||||
|
agentGraphId="test-graph-id",
|
||||||
updated_at=datetime.now(),
|
updated_at=datetime.now(),
|
||||||
is_available=False,
|
is_available=False,
|
||||||
useForOnboarding=False,
|
useForOnboarding=False,
|
||||||
@@ -83,6 +85,8 @@ async def test_get_store_agent_details(mocker):
|
|||||||
runs=10,
|
runs=10,
|
||||||
rating=4.5,
|
rating=4.5,
|
||||||
versions=["1.0"],
|
versions=["1.0"],
|
||||||
|
agentGraphVersions=["1"],
|
||||||
|
agentGraphId="test-graph-id",
|
||||||
updated_at=datetime.now(),
|
updated_at=datetime.now(),
|
||||||
is_available=False,
|
is_available=False,
|
||||||
useForOnboarding=False,
|
useForOnboarding=False,
|
||||||
@@ -105,6 +109,8 @@ async def test_get_store_agent_details(mocker):
|
|||||||
runs=15,
|
runs=15,
|
||||||
rating=4.8,
|
rating=4.8,
|
||||||
versions=["1.0", "2.0"],
|
versions=["1.0", "2.0"],
|
||||||
|
agentGraphVersions=["1", "2"],
|
||||||
|
agentGraphId="test-graph-id-active",
|
||||||
updated_at=datetime.now(),
|
updated_at=datetime.now(),
|
||||||
is_available=True,
|
is_available=True,
|
||||||
useForOnboarding=False,
|
useForOnboarding=False,
|
||||||
|
|||||||
@@ -7,6 +7,12 @@ import pydantic
|
|||||||
from backend.util.models import Pagination
|
from backend.util.models import Pagination
|
||||||
|
|
||||||
|
|
||||||
|
class ChangelogEntry(pydantic.BaseModel):
|
||||||
|
version: str
|
||||||
|
changes_summary: str
|
||||||
|
date: datetime.datetime
|
||||||
|
|
||||||
|
|
||||||
class MyAgent(pydantic.BaseModel):
|
class MyAgent(pydantic.BaseModel):
|
||||||
agent_id: str
|
agent_id: str
|
||||||
agent_version: int
|
agent_version: int
|
||||||
@@ -55,12 +61,17 @@ class StoreAgentDetails(pydantic.BaseModel):
|
|||||||
runs: int
|
runs: int
|
||||||
rating: float
|
rating: float
|
||||||
versions: list[str]
|
versions: list[str]
|
||||||
|
agentGraphVersions: list[str]
|
||||||
|
agentGraphId: str
|
||||||
last_updated: datetime.datetime
|
last_updated: datetime.datetime
|
||||||
recommended_schedule_cron: str | None = None
|
recommended_schedule_cron: str | None = None
|
||||||
|
|
||||||
active_version_id: str | None = None
|
active_version_id: str | None = None
|
||||||
has_approved_version: bool = False
|
has_approved_version: bool = False
|
||||||
|
|
||||||
|
# Optional changelog data when include_changelog=True
|
||||||
|
changelog: list[ChangelogEntry] | None = None
|
||||||
|
|
||||||
|
|
||||||
class Creator(pydantic.BaseModel):
|
class Creator(pydantic.BaseModel):
|
||||||
name: str
|
name: str
|
||||||
|
|||||||
@@ -72,6 +72,8 @@ def test_store_agent_details():
|
|||||||
runs=50,
|
runs=50,
|
||||||
rating=4.5,
|
rating=4.5,
|
||||||
versions=["1.0", "2.0"],
|
versions=["1.0", "2.0"],
|
||||||
|
agentGraphVersions=["1", "2"],
|
||||||
|
agentGraphId="test-graph-id",
|
||||||
last_updated=datetime.datetime.now(),
|
last_updated=datetime.datetime.now(),
|
||||||
)
|
)
|
||||||
assert details.slug == "test-agent"
|
assert details.slug == "test-agent"
|
||||||
|
|||||||
@@ -154,7 +154,11 @@ async def get_agents(
|
|||||||
tags=["store", "public"],
|
tags=["store", "public"],
|
||||||
response_model=backend.server.v2.store.model.StoreAgentDetails,
|
response_model=backend.server.v2.store.model.StoreAgentDetails,
|
||||||
)
|
)
|
||||||
async def get_agent(username: str, agent_name: str):
|
async def get_agent(
|
||||||
|
username: str,
|
||||||
|
agent_name: str,
|
||||||
|
include_changelog: bool = fastapi.Query(default=False),
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
This is only used on the AgentDetails Page.
|
This is only used on the AgentDetails Page.
|
||||||
|
|
||||||
@@ -164,7 +168,7 @@ async def get_agent(username: str, agent_name: str):
|
|||||||
# URL decode the agent name since it comes from the URL path
|
# URL decode the agent name since it comes from the URL path
|
||||||
agent_name = urllib.parse.unquote(agent_name).lower()
|
agent_name = urllib.parse.unquote(agent_name).lower()
|
||||||
agent = await store_cache._get_cached_agent_details(
|
agent = await store_cache._get_cached_agent_details(
|
||||||
username=username, agent_name=agent_name
|
username=username, agent_name=agent_name, include_changelog=include_changelog
|
||||||
)
|
)
|
||||||
return agent
|
return agent
|
||||||
|
|
||||||
|
|||||||
@@ -388,6 +388,8 @@ def test_get_agent_details(
|
|||||||
runs=100,
|
runs=100,
|
||||||
rating=4.5,
|
rating=4.5,
|
||||||
versions=["1.0.0", "1.1.0"],
|
versions=["1.0.0", "1.1.0"],
|
||||||
|
agentGraphVersions=["1", "2"],
|
||||||
|
agentGraphId="test-graph-id",
|
||||||
last_updated=FIXED_NOW,
|
last_updated=FIXED_NOW,
|
||||||
)
|
)
|
||||||
mock_db_call = mocker.patch("backend.server.v2.store.db.get_store_agent_details")
|
mock_db_call = mocker.patch("backend.server.v2.store.db.get_store_agent_details")
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
-- Fix StoreSubmission view to use agentGraphVersion instead of version for agent_version field
|
||||||
|
-- This ensures that submission.agent_version returns the actual agent graph version, not the store listing version number
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
|
||||||
|
-- Recreate the view with the corrected agent_version field (using agentGraphVersion instead of version)
|
||||||
|
CREATE OR REPLACE VIEW "StoreSubmission" AS
|
||||||
|
SELECT
|
||||||
|
sl.id AS listing_id,
|
||||||
|
sl."owningUserId" AS user_id,
|
||||||
|
slv."agentGraphId" AS agent_id,
|
||||||
|
slv."agentGraphVersion" AS agent_version,
|
||||||
|
sl.slug,
|
||||||
|
COALESCE(slv.name, '') AS name,
|
||||||
|
slv."subHeading" AS sub_heading,
|
||||||
|
slv.description,
|
||||||
|
slv.instructions,
|
||||||
|
slv."imageUrls" AS image_urls,
|
||||||
|
slv."submittedAt" AS date_submitted,
|
||||||
|
slv."submissionStatus" AS status,
|
||||||
|
COALESCE(ar.run_count, 0::bigint) AS runs,
|
||||||
|
COALESCE(avg(sr.score::numeric), 0.0)::double precision AS rating,
|
||||||
|
slv.id AS store_listing_version_id,
|
||||||
|
slv."reviewerId" AS reviewer_id,
|
||||||
|
slv."reviewComments" AS review_comments,
|
||||||
|
slv."internalComments" AS internal_comments,
|
||||||
|
slv."reviewedAt" AS reviewed_at,
|
||||||
|
slv."changesSummary" AS changes_summary,
|
||||||
|
slv."videoUrl" AS video_url,
|
||||||
|
slv.categories
|
||||||
|
FROM "StoreListing" sl
|
||||||
|
JOIN "StoreListingVersion" slv ON slv."storeListingId" = sl.id
|
||||||
|
LEFT JOIN "StoreListingReview" sr ON sr."storeListingVersionId" = slv.id
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT "AgentGraphExecution"."agentGraphId", count(*) AS run_count
|
||||||
|
FROM "AgentGraphExecution"
|
||||||
|
GROUP BY "AgentGraphExecution"."agentGraphId"
|
||||||
|
) ar ON ar."agentGraphId" = slv."agentGraphId"
|
||||||
|
WHERE sl."isDeleted" = false
|
||||||
|
GROUP BY sl.id, sl."owningUserId", slv.id, slv."agentGraphId", slv."agentGraphVersion", sl.slug, slv.name,
|
||||||
|
slv."subHeading", slv.description, slv.instructions, slv."imageUrls", slv."submittedAt",
|
||||||
|
slv."submissionStatus", slv."reviewerId", slv."reviewComments", slv."internalComments",
|
||||||
|
slv."reviewedAt", slv."changesSummary", slv."videoUrl", slv.categories, ar.run_count;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
-- Add agentGraphVersions field to StoreAgent view for consistent version comparison
|
||||||
|
-- This keeps the existing versions field unchanged and adds a new field with graph versions
|
||||||
|
-- This makes it safe for version comparison with LibraryAgent.graph_version
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
|
||||||
|
-- Drop and recreate the StoreAgent view with new agentGraphVersions field
|
||||||
|
DROP VIEW IF EXISTS "StoreAgent";
|
||||||
|
|
||||||
|
CREATE OR REPLACE VIEW "StoreAgent" AS
|
||||||
|
WITH latest_versions AS (
|
||||||
|
SELECT
|
||||||
|
"storeListingId",
|
||||||
|
MAX(version) AS max_version
|
||||||
|
FROM "StoreListingVersion"
|
||||||
|
WHERE "submissionStatus" = 'APPROVED'
|
||||||
|
GROUP BY "storeListingId"
|
||||||
|
),
|
||||||
|
agent_versions AS (
|
||||||
|
SELECT
|
||||||
|
"storeListingId",
|
||||||
|
array_agg(DISTINCT version::text ORDER BY version::text) AS versions
|
||||||
|
FROM "StoreListingVersion"
|
||||||
|
WHERE "submissionStatus" = 'APPROVED'
|
||||||
|
GROUP BY "storeListingId"
|
||||||
|
),
|
||||||
|
agent_graph_versions AS (
|
||||||
|
SELECT
|
||||||
|
"storeListingId",
|
||||||
|
array_agg(DISTINCT "agentGraphVersion"::text ORDER BY "agentGraphVersion"::text) AS graph_versions
|
||||||
|
FROM "StoreListingVersion"
|
||||||
|
WHERE "submissionStatus" = 'APPROVED'
|
||||||
|
GROUP BY "storeListingId"
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
sl.id AS listing_id,
|
||||||
|
slv.id AS "storeListingVersionId",
|
||||||
|
slv."createdAt" AS updated_at,
|
||||||
|
sl.slug,
|
||||||
|
COALESCE(slv.name, '') AS agent_name,
|
||||||
|
slv."videoUrl" AS agent_video,
|
||||||
|
slv."agentOutputDemoUrl" AS agent_output_demo,
|
||||||
|
COALESCE(slv."imageUrls", ARRAY[]::text[]) AS agent_image,
|
||||||
|
slv."isFeatured" AS featured,
|
||||||
|
p.username AS creator_username, -- Allow NULL for malformed sub-agents
|
||||||
|
p."avatarUrl" AS creator_avatar, -- Allow NULL for malformed sub-agents
|
||||||
|
slv."subHeading" AS sub_heading,
|
||||||
|
slv.description,
|
||||||
|
slv.categories,
|
||||||
|
slv.search,
|
||||||
|
COALESCE(ar.run_count, 0::bigint) AS runs,
|
||||||
|
COALESCE(rs.avg_rating, 0.0)::double precision AS rating,
|
||||||
|
COALESCE(av.versions, ARRAY[slv.version::text]) AS versions,
|
||||||
|
COALESCE(agv.graph_versions, ARRAY[slv."agentGraphVersion"::text]) AS "agentGraphVersions",
|
||||||
|
slv."agentGraphId",
|
||||||
|
slv."isAvailable" AS is_available,
|
||||||
|
COALESCE(sl."useForOnboarding", false) AS "useForOnboarding"
|
||||||
|
FROM "StoreListing" sl
|
||||||
|
JOIN latest_versions lv
|
||||||
|
ON sl.id = lv."storeListingId"
|
||||||
|
JOIN "StoreListingVersion" slv
|
||||||
|
ON slv."storeListingId" = lv."storeListingId"
|
||||||
|
AND slv.version = lv.max_version
|
||||||
|
AND slv."submissionStatus" = 'APPROVED'
|
||||||
|
JOIN "AgentGraph" a
|
||||||
|
ON slv."agentGraphId" = a.id
|
||||||
|
AND slv."agentGraphVersion" = a.version
|
||||||
|
LEFT JOIN "Profile" p
|
||||||
|
ON sl."owningUserId" = p."userId"
|
||||||
|
LEFT JOIN "mv_review_stats" rs
|
||||||
|
ON sl.id = rs."storeListingId"
|
||||||
|
LEFT JOIN "mv_agent_run_counts" ar
|
||||||
|
ON a.id = ar."agentGraphId"
|
||||||
|
LEFT JOIN agent_versions av
|
||||||
|
ON sl.id = av."storeListingId"
|
||||||
|
LEFT JOIN agent_graph_versions agv
|
||||||
|
ON sl.id = agv."storeListingId"
|
||||||
|
WHERE sl."isDeleted" = false
|
||||||
|
AND sl."hasApprovedVersion" = true;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
@@ -728,11 +728,13 @@ view StoreAgent {
|
|||||||
description String
|
description String
|
||||||
categories String[]
|
categories String[]
|
||||||
search Unsupported("tsvector")? @default(dbgenerated("''::tsvector"))
|
search Unsupported("tsvector")? @default(dbgenerated("''::tsvector"))
|
||||||
runs Int
|
runs Int
|
||||||
rating Float
|
rating Float
|
||||||
versions String[]
|
versions String[]
|
||||||
is_available Boolean @default(true)
|
agentGraphVersions String[]
|
||||||
useForOnboarding Boolean @default(false)
|
agentGraphId String
|
||||||
|
is_available Boolean @default(true)
|
||||||
|
useForOnboarding Boolean @default(false)
|
||||||
|
|
||||||
// Materialized views used (refreshed every 15 minutes via pg_cron):
|
// Materialized views used (refreshed every 15 minutes via pg_cron):
|
||||||
// - mv_agent_run_counts - Pre-aggregated agent execution counts by agentGraphId
|
// - mv_agent_run_counts - Pre-aggregated agent execution counts by agentGraphId
|
||||||
|
|||||||
@@ -3,6 +3,14 @@ import { withSentryConfig } from "@sentry/nextjs";
|
|||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
productionBrowserSourceMaps: true,
|
productionBrowserSourceMaps: true,
|
||||||
|
experimental: {
|
||||||
|
serverActions: {
|
||||||
|
bodySizeLimit: "256mb",
|
||||||
|
},
|
||||||
|
// Increase body size limit for API routes (file uploads) - 256MB to match backend limit
|
||||||
|
proxyClientMaxBodySize: "256mb",
|
||||||
|
middlewareClientMaxBodySize: "256mb",
|
||||||
|
},
|
||||||
images: {
|
images: {
|
||||||
domains: [
|
domains: [
|
||||||
// We dont need to maintain alphabetical order here
|
// We dont need to maintain alphabetical order here
|
||||||
|
|||||||
@@ -137,9 +137,8 @@
|
|||||||
"concurrently": "9.2.1",
|
"concurrently": "9.2.1",
|
||||||
"cross-env": "10.1.0",
|
"cross-env": "10.1.0",
|
||||||
"eslint": "8.57.1",
|
"eslint": "8.57.1",
|
||||||
"eslint-config-next": "15.5.2",
|
"eslint-config-next": "15.5.7",
|
||||||
"eslint-plugin-storybook": "9.1.5",
|
"eslint-plugin-storybook": "9.1.5",
|
||||||
"import-in-the-middle": "1.14.2",
|
|
||||||
"msw": "2.11.6",
|
"msw": "2.11.6",
|
||||||
"msw-storybook-addon": "2.0.6",
|
"msw-storybook-addon": "2.0.6",
|
||||||
"orval": "7.13.0",
|
"orval": "7.13.0",
|
||||||
|
|||||||
282
autogpt_platform/frontend/pnpm-lock.yaml
generated
282
autogpt_platform/frontend/pnpm-lock.yaml
generated
@@ -331,14 +331,11 @@ importers:
|
|||||||
specifier: 8.57.1
|
specifier: 8.57.1
|
||||||
version: 8.57.1
|
version: 8.57.1
|
||||||
eslint-config-next:
|
eslint-config-next:
|
||||||
specifier: 15.5.2
|
specifier: 15.5.7
|
||||||
version: 15.5.2(eslint@8.57.1)(typescript@5.9.3)
|
version: 15.5.7(eslint@8.57.1)(typescript@5.9.3)
|
||||||
eslint-plugin-storybook:
|
eslint-plugin-storybook:
|
||||||
specifier: 9.1.5
|
specifier: 9.1.5
|
||||||
version: 9.1.5(eslint@8.57.1)(storybook@9.1.5(@testing-library/dom@10.4.1)(msw@2.11.6(@types/node@24.10.0)(typescript@5.9.3))(prettier@3.6.2))(typescript@5.9.3)
|
version: 9.1.5(eslint@8.57.1)(storybook@9.1.5(@testing-library/dom@10.4.1)(msw@2.11.6(@types/node@24.10.0)(typescript@5.9.3))(prettier@3.6.2))(typescript@5.9.3)
|
||||||
import-in-the-middle:
|
|
||||||
specifier: 1.14.2
|
|
||||||
version: 1.14.2
|
|
||||||
msw:
|
msw:
|
||||||
specifier: 2.11.6
|
specifier: 2.11.6
|
||||||
version: 2.11.6(@types/node@24.10.0)(typescript@5.9.3)
|
version: 2.11.6(@types/node@24.10.0)(typescript@5.9.3)
|
||||||
@@ -986,12 +983,15 @@ packages:
|
|||||||
'@date-fns/tz@1.4.1':
|
'@date-fns/tz@1.4.1':
|
||||||
resolution: {integrity: sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==}
|
resolution: {integrity: sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==}
|
||||||
|
|
||||||
'@emnapi/core@1.5.0':
|
'@emnapi/core@1.7.1':
|
||||||
resolution: {integrity: sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==}
|
resolution: {integrity: sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==}
|
||||||
|
|
||||||
'@emnapi/runtime@1.5.0':
|
'@emnapi/runtime@1.5.0':
|
||||||
resolution: {integrity: sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==}
|
resolution: {integrity: sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==}
|
||||||
|
|
||||||
|
'@emnapi/runtime@1.7.1':
|
||||||
|
resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==}
|
||||||
|
|
||||||
'@emnapi/wasi-threads@1.1.0':
|
'@emnapi/wasi-threads@1.1.0':
|
||||||
resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==}
|
resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==}
|
||||||
|
|
||||||
@@ -1329,6 +1329,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==}
|
resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==}
|
||||||
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
|
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
|
||||||
|
|
||||||
|
'@eslint-community/regexpp@4.12.2':
|
||||||
|
resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==}
|
||||||
|
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
|
||||||
|
|
||||||
'@eslint/eslintrc@2.1.4':
|
'@eslint/eslintrc@2.1.4':
|
||||||
resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==}
|
resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
@@ -1605,8 +1609,8 @@ packages:
|
|||||||
'@next/env@15.4.10':
|
'@next/env@15.4.10':
|
||||||
resolution: {integrity: sha512-knhmoJ0Vv7VRf6pZEPSnciUG1S4bIhWx+qTYBW/AjxEtlzsiNORPk8sFDCEvqLfmKuey56UB9FL1UdHEV3uBrg==}
|
resolution: {integrity: sha512-knhmoJ0Vv7VRf6pZEPSnciUG1S4bIhWx+qTYBW/AjxEtlzsiNORPk8sFDCEvqLfmKuey56UB9FL1UdHEV3uBrg==}
|
||||||
|
|
||||||
'@next/eslint-plugin-next@15.5.2':
|
'@next/eslint-plugin-next@15.5.7':
|
||||||
resolution: {integrity: sha512-lkLrRVxcftuOsJNhWatf1P2hNVfh98k/omQHrCEPPriUypR6RcS13IvLdIrEvkm9AH2Nu2YpR5vLqBuy6twH3Q==}
|
resolution: {integrity: sha512-DtRU2N7BkGr8r+pExfuWHwMEPX5SD57FeA6pxdgCHODo+b/UgIgjE+rgWKtJAbEbGhVZ2jtHn4g3wNhWFoNBQQ==}
|
||||||
|
|
||||||
'@next/swc-darwin-arm64@15.4.8':
|
'@next/swc-darwin-arm64@15.4.8':
|
||||||
resolution: {integrity: sha512-Pf6zXp7yyQEn7sqMxur6+kYcywx5up1J849psyET7/8pG2gQTVMjU3NzgIt8SeEP5to3If/SaWmaA6H6ysBr1A==}
|
resolution: {integrity: sha512-Pf6zXp7yyQEn7sqMxur6+kYcywx5up1J849psyET7/8pG2gQTVMjU3NzgIt8SeEP5to3If/SaWmaA6H6ysBr1A==}
|
||||||
@@ -2622,8 +2626,8 @@ packages:
|
|||||||
'@rtsao/scc@1.1.0':
|
'@rtsao/scc@1.1.0':
|
||||||
resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
|
resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
|
||||||
|
|
||||||
'@rushstack/eslint-patch@1.12.0':
|
'@rushstack/eslint-patch@1.15.0':
|
||||||
resolution: {integrity: sha512-5EwMtOqvJMMa3HbmxLlF74e+3/HhwBTMcvt3nqVJgGCozO6hzIPOBlwm8mGVNR9SN2IJpxSnlxczyDjcn7qIyw==}
|
resolution: {integrity: sha512-ojSshQPKwVvSMR8yT2L/QtUkV5SXi/IfDiJ4/8d6UbTPjiHVmxZzUAzGD8Tzks1b9+qQkZa0isUOvYObedITaw==}
|
||||||
|
|
||||||
'@scarf/scarf@1.4.0':
|
'@scarf/scarf@1.4.0':
|
||||||
resolution: {integrity: sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==}
|
resolution: {integrity: sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==}
|
||||||
@@ -3097,8 +3101,8 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@testing-library/dom': '>=7.21.4'
|
'@testing-library/dom': '>=7.21.4'
|
||||||
|
|
||||||
'@tybys/wasm-util@0.10.0':
|
'@tybys/wasm-util@0.10.1':
|
||||||
resolution: {integrity: sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==}
|
resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
|
||||||
|
|
||||||
'@types/aria-query@5.0.4':
|
'@types/aria-query@5.0.4':
|
||||||
resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==}
|
resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==}
|
||||||
@@ -3288,16 +3292,16 @@ packages:
|
|||||||
'@types/ws@8.18.1':
|
'@types/ws@8.18.1':
|
||||||
resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
|
resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
|
||||||
|
|
||||||
'@typescript-eslint/eslint-plugin@8.43.0':
|
'@typescript-eslint/eslint-plugin@8.48.1':
|
||||||
resolution: {integrity: sha512-8tg+gt7ENL7KewsKMKDHXR1vm8tt9eMxjJBYINf6swonlWgkYn5NwyIgXpbbDxTNU5DgpDFfj95prcTq2clIQQ==}
|
resolution: {integrity: sha512-X63hI1bxl5ohelzr0LY5coufyl0LJNthld+abwxpCoo6Gq+hSqhKwci7MUWkXo67mzgUK6YFByhmaHmUcuBJmA==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@typescript-eslint/parser': ^8.43.0
|
'@typescript-eslint/parser': ^8.48.1
|
||||||
eslint: ^8.57.0 || ^9.0.0
|
eslint: ^8.57.0 || ^9.0.0
|
||||||
typescript: '>=4.8.4 <6.0.0'
|
typescript: '>=4.8.4 <6.0.0'
|
||||||
|
|
||||||
'@typescript-eslint/parser@8.43.0':
|
'@typescript-eslint/parser@8.48.1':
|
||||||
resolution: {integrity: sha512-B7RIQiTsCBBmY+yW4+ILd6mF5h1FUwJsVvpqkrgpszYifetQ2Ke+Z4u6aZh0CblkUGIdR59iYVyXqqZGkZ3aBw==}
|
resolution: {integrity: sha512-PC0PDZfJg8sP7cmKe6L3QIL8GZwU5aRvUFedqSIpw3B+QjRSUZeeITC2M5XKeMXEzL6wccN196iy3JLwKNvDVA==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^8.57.0 || ^9.0.0
|
eslint: ^8.57.0 || ^9.0.0
|
||||||
@@ -3315,6 +3319,12 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: '>=4.8.4 <6.0.0'
|
typescript: '>=4.8.4 <6.0.0'
|
||||||
|
|
||||||
|
'@typescript-eslint/project-service@8.48.1':
|
||||||
|
resolution: {integrity: sha512-HQWSicah4s9z2/HifRPQ6b6R7G+SBx64JlFQpgSSHWPKdvCZX57XCbszg/bapbRsOEv42q5tayTYcEFpACcX1w==}
|
||||||
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
peerDependencies:
|
||||||
|
typescript: '>=4.8.4 <6.0.0'
|
||||||
|
|
||||||
'@typescript-eslint/scope-manager@8.43.0':
|
'@typescript-eslint/scope-manager@8.43.0':
|
||||||
resolution: {integrity: sha512-daSWlQ87ZhsjrbMLvpuuMAt3y4ba57AuvadcR7f3nl8eS3BjRc8L9VLxFLk92RL5xdXOg6IQ+qKjjqNEimGuAg==}
|
resolution: {integrity: sha512-daSWlQ87ZhsjrbMLvpuuMAt3y4ba57AuvadcR7f3nl8eS3BjRc8L9VLxFLk92RL5xdXOg6IQ+qKjjqNEimGuAg==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
@@ -3323,6 +3333,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-LF4b/NmGvdWEHD2H4MsHD8ny6JpiVNDzrSZr3CsckEgCbAGZbYM4Cqxvi9L+WqDMT+51Ozy7lt2M+d0JLEuBqA==}
|
resolution: {integrity: sha512-LF4b/NmGvdWEHD2H4MsHD8ny6JpiVNDzrSZr3CsckEgCbAGZbYM4Cqxvi9L+WqDMT+51Ozy7lt2M+d0JLEuBqA==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
|
'@typescript-eslint/scope-manager@8.48.1':
|
||||||
|
resolution: {integrity: sha512-rj4vWQsytQbLxC5Bf4XwZ0/CKd362DkWMUkviT7DCS057SK64D5lH74sSGzhI6PDD2HCEq02xAP9cX68dYyg1w==}
|
||||||
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
'@typescript-eslint/tsconfig-utils@8.43.0':
|
'@typescript-eslint/tsconfig-utils@8.43.0':
|
||||||
resolution: {integrity: sha512-ALC2prjZcj2YqqL5X/bwWQmHA2em6/94GcbB/KKu5SX3EBDOsqztmmX1kMkvAJHzxk7TazKzJfFiEIagNV3qEA==}
|
resolution: {integrity: sha512-ALC2prjZcj2YqqL5X/bwWQmHA2em6/94GcbB/KKu5SX3EBDOsqztmmX1kMkvAJHzxk7TazKzJfFiEIagNV3qEA==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
@@ -3335,8 +3349,14 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: '>=4.8.4 <6.0.0'
|
typescript: '>=4.8.4 <6.0.0'
|
||||||
|
|
||||||
'@typescript-eslint/type-utils@8.43.0':
|
'@typescript-eslint/tsconfig-utils@8.48.1':
|
||||||
resolution: {integrity: sha512-qaH1uLBpBuBBuRf8c1mLJ6swOfzCXryhKND04Igr4pckzSEW9JX5Aw9AgW00kwfjWJF0kk0ps9ExKTfvXfw4Qg==}
|
resolution: {integrity: sha512-k0Jhs4CpEffIBm6wPaCXBAD7jxBtrHjrSgtfCjUvPp9AZ78lXKdTR8fxyZO5y4vWNlOvYXRtngSZNSn+H53Jkw==}
|
||||||
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
peerDependencies:
|
||||||
|
typescript: '>=4.8.4 <6.0.0'
|
||||||
|
|
||||||
|
'@typescript-eslint/type-utils@8.48.1':
|
||||||
|
resolution: {integrity: sha512-1jEop81a3LrJQLTf/1VfPQdhIY4PlGDBc/i67EVWObrtvcziysbLN3oReexHOM6N3jyXgCrkBsZpqwH0hiDOQg==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^8.57.0 || ^9.0.0
|
eslint: ^8.57.0 || ^9.0.0
|
||||||
@@ -3350,6 +3370,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-lNCWCbq7rpg7qDsQrd3D6NyWYu+gkTENkG5IKYhUIcxSb59SQC/hEQ+MrG4sTgBVghTonNWq42bA/d4yYumldQ==}
|
resolution: {integrity: sha512-lNCWCbq7rpg7qDsQrd3D6NyWYu+gkTENkG5IKYhUIcxSb59SQC/hEQ+MrG4sTgBVghTonNWq42bA/d4yYumldQ==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
|
'@typescript-eslint/types@8.48.1':
|
||||||
|
resolution: {integrity: sha512-+fZ3LZNeiELGmimrujsDCT4CRIbq5oXdHe7chLiW8qzqyPMnn1puNstCrMNVAqwcl2FdIxkuJ4tOs/RFDBVc/Q==}
|
||||||
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
'@typescript-eslint/typescript-estree@8.43.0':
|
'@typescript-eslint/typescript-estree@8.43.0':
|
||||||
resolution: {integrity: sha512-7Vv6zlAhPb+cvEpP06WXXy/ZByph9iL6BQRBDj4kmBsW98AqEeQHlj/13X+sZOrKSo9/rNKH4Ul4f6EICREFdw==}
|
resolution: {integrity: sha512-7Vv6zlAhPb+cvEpP06WXXy/ZByph9iL6BQRBDj4kmBsW98AqEeQHlj/13X+sZOrKSo9/rNKH4Ul4f6EICREFdw==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
@@ -3362,6 +3386,12 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: '>=4.8.4 <6.0.0'
|
typescript: '>=4.8.4 <6.0.0'
|
||||||
|
|
||||||
|
'@typescript-eslint/typescript-estree@8.48.1':
|
||||||
|
resolution: {integrity: sha512-/9wQ4PqaefTK6POVTjJaYS0bynCgzh6ClJHGSBj06XEHjkfylzB+A3qvyaXnErEZSaxhIo4YdyBgq6j4RysxDg==}
|
||||||
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
peerDependencies:
|
||||||
|
typescript: '>=4.8.4 <6.0.0'
|
||||||
|
|
||||||
'@typescript-eslint/utils@8.43.0':
|
'@typescript-eslint/utils@8.43.0':
|
||||||
resolution: {integrity: sha512-S1/tEmkUeeswxd0GGcnwuVQPFWo8NzZTOMxCvw8BX7OMxnNae+i8Tm7REQen/SwUIPoPqfKn7EaZ+YLpiB3k9g==}
|
resolution: {integrity: sha512-S1/tEmkUeeswxd0GGcnwuVQPFWo8NzZTOMxCvw8BX7OMxnNae+i8Tm7REQen/SwUIPoPqfKn7EaZ+YLpiB3k9g==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
@@ -3376,6 +3406,13 @@ packages:
|
|||||||
eslint: ^8.57.0 || ^9.0.0
|
eslint: ^8.57.0 || ^9.0.0
|
||||||
typescript: '>=4.8.4 <6.0.0'
|
typescript: '>=4.8.4 <6.0.0'
|
||||||
|
|
||||||
|
'@typescript-eslint/utils@8.48.1':
|
||||||
|
resolution: {integrity: sha512-fAnhLrDjiVfey5wwFRwrweyRlCmdz5ZxXz2G/4cLn0YDLjTapmN4gcCsTBR1N2rWnZSDeWpYtgLDsJt+FpmcwA==}
|
||||||
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
peerDependencies:
|
||||||
|
eslint: ^8.57.0 || ^9.0.0
|
||||||
|
typescript: '>=4.8.4 <6.0.0'
|
||||||
|
|
||||||
'@typescript-eslint/visitor-keys@8.43.0':
|
'@typescript-eslint/visitor-keys@8.43.0':
|
||||||
resolution: {integrity: sha512-T+S1KqRD4sg/bHfLwrpF/K3gQLBM1n7Rp7OjjikjTEssI2YJzQpi5WXoynOaQ93ERIuq3O8RBTOUYDKszUCEHw==}
|
resolution: {integrity: sha512-T+S1KqRD4sg/bHfLwrpF/K3gQLBM1n7Rp7OjjikjTEssI2YJzQpi5WXoynOaQ93ERIuq3O8RBTOUYDKszUCEHw==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
@@ -3384,6 +3421,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-tUFMXI4gxzzMXt4xpGJEsBsTox0XbNQ1y94EwlD/CuZwFcQP79xfQqMhau9HsRc/J0cAPA/HZt1dZPtGn9V/7w==}
|
resolution: {integrity: sha512-tUFMXI4gxzzMXt4xpGJEsBsTox0XbNQ1y94EwlD/CuZwFcQP79xfQqMhau9HsRc/J0cAPA/HZt1dZPtGn9V/7w==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
|
'@typescript-eslint/visitor-keys@8.48.1':
|
||||||
|
resolution: {integrity: sha512-BmxxndzEWhE4TIEEMBs8lP3MBWN3jFPs/p6gPm/wkv02o41hI6cq9AuSmGAaTTHPtA1FTi2jBre4A9rm5ZmX+Q==}
|
||||||
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
'@ungap/structured-clone@1.3.0':
|
'@ungap/structured-clone@1.3.0':
|
||||||
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
|
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
|
||||||
|
|
||||||
@@ -4585,8 +4626,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
|
resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
eslint-config-next@15.5.2:
|
eslint-config-next@15.5.7:
|
||||||
resolution: {integrity: sha512-3hPZghsLupMxxZ2ggjIIrat/bPniM2yRpsVPVM40rp8ZMzKWOJp2CGWn7+EzoV2ddkUr5fxNfHpF+wU1hGt/3g==}
|
resolution: {integrity: sha512-nU/TRGHHeG81NeLW5DeQT5t6BDUqbpsNQTvef1ld/tqHT+/zTx60/TIhKnmPISTTe++DVo+DLxDmk4rnwHaZVw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^7.23.0 || ^8.0.0 || ^9.0.0
|
eslint: ^7.23.0 || ^8.0.0 || ^9.0.0
|
||||||
typescript: '>=3.3.1'
|
typescript: '>=3.3.1'
|
||||||
@@ -4918,6 +4959,10 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
next: '>=13.2.0'
|
next: '>=13.2.0'
|
||||||
|
|
||||||
|
generator-function@2.0.1:
|
||||||
|
resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
gensync@1.0.0-beta.2:
|
gensync@1.0.0-beta.2:
|
||||||
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
|
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
@@ -4946,8 +4991,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==}
|
resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
get-tsconfig@4.10.1:
|
get-tsconfig@4.13.0:
|
||||||
resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==}
|
resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==}
|
||||||
|
|
||||||
github-slugger@2.0.0:
|
github-slugger@2.0.0:
|
||||||
resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==}
|
resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==}
|
||||||
@@ -5168,9 +5213,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
|
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
import-in-the-middle@1.14.2:
|
|
||||||
resolution: {integrity: sha512-5tCuY9BV8ujfOpwtAGgsTx9CGUapcFMEEyByLv1B+v2+6DhAcw+Zr0nhQT7uwaZ7DiourxFEscghOR8e1aPLQw==}
|
|
||||||
|
|
||||||
import-in-the-middle@2.0.0:
|
import-in-the-middle@2.0.0:
|
||||||
resolution: {integrity: sha512-yNZhyQYqXpkT0AKq3F3KLasUSK4fHvebNH5hOsKQw2dhGSALvQ4U0BqUc5suziKvydO5u5hgN2hy1RJaho8U5A==}
|
resolution: {integrity: sha512-yNZhyQYqXpkT0AKq3F3KLasUSK4fHvebNH5hOsKQw2dhGSALvQ4U0BqUc5suziKvydO5u5hgN2hy1RJaho8U5A==}
|
||||||
|
|
||||||
@@ -5282,6 +5324,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==}
|
resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
is-generator-function@1.1.2:
|
||||||
|
resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
is-glob@4.0.3:
|
is-glob@4.0.3:
|
||||||
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
|
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -5903,8 +5949,8 @@ packages:
|
|||||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
napi-postinstall@0.3.3:
|
napi-postinstall@0.3.4:
|
||||||
resolution: {integrity: sha512-uTp172LLXSxuSYHv/kou+f6KW3SMppU9ivthaVTXian9sOt3XM/zHYHpRZiLgQoxeWfYUnslNWQHF1+G71xcow==}
|
resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==}
|
||||||
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
|
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
@@ -6769,6 +6815,11 @@ packages:
|
|||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
resolve@1.22.11:
|
||||||
|
resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
resolve@1.22.8:
|
resolve@1.22.8:
|
||||||
resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
|
resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -7858,7 +7909,7 @@ snapshots:
|
|||||||
'@babel/helper-plugin-utils': 7.27.1
|
'@babel/helper-plugin-utils': 7.27.1
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
lodash.debounce: 4.0.8
|
lodash.debounce: 4.0.8
|
||||||
resolve: 1.22.10
|
resolve: 1.22.11
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -8550,7 +8601,7 @@ snapshots:
|
|||||||
|
|
||||||
'@date-fns/tz@1.4.1': {}
|
'@date-fns/tz@1.4.1': {}
|
||||||
|
|
||||||
'@emnapi/core@1.5.0':
|
'@emnapi/core@1.7.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@emnapi/wasi-threads': 1.1.0
|
'@emnapi/wasi-threads': 1.1.0
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
@@ -8561,6 +8612,11 @@ snapshots:
|
|||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@emnapi/runtime@1.7.1':
|
||||||
|
dependencies:
|
||||||
|
tslib: 2.8.1
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@emnapi/wasi-threads@1.1.0':
|
'@emnapi/wasi-threads@1.1.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
@@ -8739,6 +8795,8 @@ snapshots:
|
|||||||
|
|
||||||
'@eslint-community/regexpp@4.12.1': {}
|
'@eslint-community/regexpp@4.12.1': {}
|
||||||
|
|
||||||
|
'@eslint-community/regexpp@4.12.2': {}
|
||||||
|
|
||||||
'@eslint/eslintrc@2.1.4':
|
'@eslint/eslintrc@2.1.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
ajv: 6.12.6
|
ajv: 6.12.6
|
||||||
@@ -8996,16 +9054,16 @@ snapshots:
|
|||||||
|
|
||||||
'@napi-rs/wasm-runtime@0.2.12':
|
'@napi-rs/wasm-runtime@0.2.12':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@emnapi/core': 1.5.0
|
'@emnapi/core': 1.7.1
|
||||||
'@emnapi/runtime': 1.5.0
|
'@emnapi/runtime': 1.7.1
|
||||||
'@tybys/wasm-util': 0.10.0
|
'@tybys/wasm-util': 0.10.1
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@neoconfetti/react@1.0.0': {}
|
'@neoconfetti/react@1.0.0': {}
|
||||||
|
|
||||||
'@next/env@15.4.10': {}
|
'@next/env@15.4.10': {}
|
||||||
|
|
||||||
'@next/eslint-plugin-next@15.5.2':
|
'@next/eslint-plugin-next@15.5.7':
|
||||||
dependencies:
|
dependencies:
|
||||||
fast-glob: 3.3.1
|
fast-glob: 3.3.1
|
||||||
|
|
||||||
@@ -10115,7 +10173,7 @@ snapshots:
|
|||||||
|
|
||||||
'@rtsao/scc@1.1.0': {}
|
'@rtsao/scc@1.1.0': {}
|
||||||
|
|
||||||
'@rushstack/eslint-patch@1.12.0': {}
|
'@rushstack/eslint-patch@1.15.0': {}
|
||||||
|
|
||||||
'@scarf/scarf@1.4.0': {}
|
'@scarf/scarf@1.4.0': {}
|
||||||
|
|
||||||
@@ -10867,7 +10925,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@testing-library/dom': 10.4.1
|
'@testing-library/dom': 10.4.1
|
||||||
|
|
||||||
'@tybys/wasm-util@0.10.0':
|
'@tybys/wasm-util@0.10.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
optional: true
|
optional: true
|
||||||
@@ -11065,14 +11123,14 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 24.10.0
|
'@types/node': 24.10.0
|
||||||
|
|
||||||
'@typescript-eslint/eslint-plugin@8.43.0(@typescript-eslint/parser@8.43.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)':
|
'@typescript-eslint/eslint-plugin@8.48.1(@typescript-eslint/parser@8.48.1(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint-community/regexpp': 4.12.1
|
'@eslint-community/regexpp': 4.12.2
|
||||||
'@typescript-eslint/parser': 8.43.0(eslint@8.57.1)(typescript@5.9.3)
|
'@typescript-eslint/parser': 8.48.1(eslint@8.57.1)(typescript@5.9.3)
|
||||||
'@typescript-eslint/scope-manager': 8.43.0
|
'@typescript-eslint/scope-manager': 8.48.1
|
||||||
'@typescript-eslint/type-utils': 8.43.0(eslint@8.57.1)(typescript@5.9.3)
|
'@typescript-eslint/type-utils': 8.48.1(eslint@8.57.1)(typescript@5.9.3)
|
||||||
'@typescript-eslint/utils': 8.43.0(eslint@8.57.1)(typescript@5.9.3)
|
'@typescript-eslint/utils': 8.48.1(eslint@8.57.1)(typescript@5.9.3)
|
||||||
'@typescript-eslint/visitor-keys': 8.43.0
|
'@typescript-eslint/visitor-keys': 8.48.1
|
||||||
eslint: 8.57.1
|
eslint: 8.57.1
|
||||||
graphemer: 1.4.0
|
graphemer: 1.4.0
|
||||||
ignore: 7.0.5
|
ignore: 7.0.5
|
||||||
@@ -11082,12 +11140,12 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@typescript-eslint/parser@8.43.0(eslint@8.57.1)(typescript@5.9.3)':
|
'@typescript-eslint/parser@8.48.1(eslint@8.57.1)(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/scope-manager': 8.43.0
|
'@typescript-eslint/scope-manager': 8.48.1
|
||||||
'@typescript-eslint/types': 8.43.0
|
'@typescript-eslint/types': 8.48.1
|
||||||
'@typescript-eslint/typescript-estree': 8.43.0(typescript@5.9.3)
|
'@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3)
|
||||||
'@typescript-eslint/visitor-keys': 8.43.0
|
'@typescript-eslint/visitor-keys': 8.48.1
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
eslint: 8.57.1
|
eslint: 8.57.1
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
@@ -11097,7 +11155,7 @@ snapshots:
|
|||||||
'@typescript-eslint/project-service@8.43.0(typescript@5.9.3)':
|
'@typescript-eslint/project-service@8.43.0(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/tsconfig-utils': 8.43.0(typescript@5.9.3)
|
'@typescript-eslint/tsconfig-utils': 8.43.0(typescript@5.9.3)
|
||||||
'@typescript-eslint/types': 8.43.0
|
'@typescript-eslint/types': 8.48.1
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -11106,7 +11164,16 @@ snapshots:
|
|||||||
'@typescript-eslint/project-service@8.46.2(typescript@5.9.3)':
|
'@typescript-eslint/project-service@8.46.2(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/tsconfig-utils': 8.46.2(typescript@5.9.3)
|
'@typescript-eslint/tsconfig-utils': 8.46.2(typescript@5.9.3)
|
||||||
'@typescript-eslint/types': 8.46.2
|
'@typescript-eslint/types': 8.48.1
|
||||||
|
debug: 4.4.3
|
||||||
|
typescript: 5.9.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
'@typescript-eslint/project-service@8.48.1(typescript@5.9.3)':
|
||||||
|
dependencies:
|
||||||
|
'@typescript-eslint/tsconfig-utils': 8.48.1(typescript@5.9.3)
|
||||||
|
'@typescript-eslint/types': 8.48.1
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -11122,6 +11189,11 @@ snapshots:
|
|||||||
'@typescript-eslint/types': 8.46.2
|
'@typescript-eslint/types': 8.46.2
|
||||||
'@typescript-eslint/visitor-keys': 8.46.2
|
'@typescript-eslint/visitor-keys': 8.46.2
|
||||||
|
|
||||||
|
'@typescript-eslint/scope-manager@8.48.1':
|
||||||
|
dependencies:
|
||||||
|
'@typescript-eslint/types': 8.48.1
|
||||||
|
'@typescript-eslint/visitor-keys': 8.48.1
|
||||||
|
|
||||||
'@typescript-eslint/tsconfig-utils@8.43.0(typescript@5.9.3)':
|
'@typescript-eslint/tsconfig-utils@8.43.0(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
@@ -11130,11 +11202,15 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
|
|
||||||
'@typescript-eslint/type-utils@8.43.0(eslint@8.57.1)(typescript@5.9.3)':
|
'@typescript-eslint/tsconfig-utils@8.48.1(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/types': 8.43.0
|
typescript: 5.9.3
|
||||||
'@typescript-eslint/typescript-estree': 8.43.0(typescript@5.9.3)
|
|
||||||
'@typescript-eslint/utils': 8.43.0(eslint@8.57.1)(typescript@5.9.3)
|
'@typescript-eslint/type-utils@8.48.1(eslint@8.57.1)(typescript@5.9.3)':
|
||||||
|
dependencies:
|
||||||
|
'@typescript-eslint/types': 8.48.1
|
||||||
|
'@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3)
|
||||||
|
'@typescript-eslint/utils': 8.48.1(eslint@8.57.1)(typescript@5.9.3)
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
eslint: 8.57.1
|
eslint: 8.57.1
|
||||||
ts-api-utils: 2.1.0(typescript@5.9.3)
|
ts-api-utils: 2.1.0(typescript@5.9.3)
|
||||||
@@ -11146,6 +11222,8 @@ snapshots:
|
|||||||
|
|
||||||
'@typescript-eslint/types@8.46.2': {}
|
'@typescript-eslint/types@8.46.2': {}
|
||||||
|
|
||||||
|
'@typescript-eslint/types@8.48.1': {}
|
||||||
|
|
||||||
'@typescript-eslint/typescript-estree@8.43.0(typescript@5.9.3)':
|
'@typescript-eslint/typescript-estree@8.43.0(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/project-service': 8.43.0(typescript@5.9.3)
|
'@typescript-eslint/project-service': 8.43.0(typescript@5.9.3)
|
||||||
@@ -11156,7 +11234,7 @@ snapshots:
|
|||||||
fast-glob: 3.3.3
|
fast-glob: 3.3.3
|
||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
minimatch: 9.0.5
|
minimatch: 9.0.5
|
||||||
semver: 7.7.2
|
semver: 7.7.3
|
||||||
ts-api-utils: 2.1.0(typescript@5.9.3)
|
ts-api-utils: 2.1.0(typescript@5.9.3)
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -11178,6 +11256,21 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
'@typescript-eslint/typescript-estree@8.48.1(typescript@5.9.3)':
|
||||||
|
dependencies:
|
||||||
|
'@typescript-eslint/project-service': 8.48.1(typescript@5.9.3)
|
||||||
|
'@typescript-eslint/tsconfig-utils': 8.48.1(typescript@5.9.3)
|
||||||
|
'@typescript-eslint/types': 8.48.1
|
||||||
|
'@typescript-eslint/visitor-keys': 8.48.1
|
||||||
|
debug: 4.4.3
|
||||||
|
minimatch: 9.0.5
|
||||||
|
semver: 7.7.3
|
||||||
|
tinyglobby: 0.2.15
|
||||||
|
ts-api-utils: 2.1.0(typescript@5.9.3)
|
||||||
|
typescript: 5.9.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
'@typescript-eslint/utils@8.43.0(eslint@8.57.1)(typescript@5.9.3)':
|
'@typescript-eslint/utils@8.43.0(eslint@8.57.1)(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint-community/eslint-utils': 4.9.0(eslint@8.57.1)
|
'@eslint-community/eslint-utils': 4.9.0(eslint@8.57.1)
|
||||||
@@ -11200,6 +11293,17 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
'@typescript-eslint/utils@8.48.1(eslint@8.57.1)(typescript@5.9.3)':
|
||||||
|
dependencies:
|
||||||
|
'@eslint-community/eslint-utils': 4.9.0(eslint@8.57.1)
|
||||||
|
'@typescript-eslint/scope-manager': 8.48.1
|
||||||
|
'@typescript-eslint/types': 8.48.1
|
||||||
|
'@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3)
|
||||||
|
eslint: 8.57.1
|
||||||
|
typescript: 5.9.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
'@typescript-eslint/visitor-keys@8.43.0':
|
'@typescript-eslint/visitor-keys@8.43.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/types': 8.43.0
|
'@typescript-eslint/types': 8.43.0
|
||||||
@@ -11210,6 +11314,11 @@ snapshots:
|
|||||||
'@typescript-eslint/types': 8.46.2
|
'@typescript-eslint/types': 8.46.2
|
||||||
eslint-visitor-keys: 4.2.1
|
eslint-visitor-keys: 4.2.1
|
||||||
|
|
||||||
|
'@typescript-eslint/visitor-keys@8.48.1':
|
||||||
|
dependencies:
|
||||||
|
'@typescript-eslint/types': 8.48.1
|
||||||
|
eslint-visitor-keys: 4.2.1
|
||||||
|
|
||||||
'@ungap/structured-clone@1.3.0': {}
|
'@ungap/structured-clone@1.3.0': {}
|
||||||
|
|
||||||
'@unrs/resolver-binding-android-arm-eabi@1.11.1':
|
'@unrs/resolver-binding-android-arm-eabi@1.11.1':
|
||||||
@@ -12532,16 +12641,16 @@ snapshots:
|
|||||||
|
|
||||||
escape-string-regexp@5.0.0: {}
|
escape-string-regexp@5.0.0: {}
|
||||||
|
|
||||||
eslint-config-next@15.5.2(eslint@8.57.1)(typescript@5.9.3):
|
eslint-config-next@15.5.7(eslint@8.57.1)(typescript@5.9.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@next/eslint-plugin-next': 15.5.2
|
'@next/eslint-plugin-next': 15.5.7
|
||||||
'@rushstack/eslint-patch': 1.12.0
|
'@rushstack/eslint-patch': 1.15.0
|
||||||
'@typescript-eslint/eslint-plugin': 8.43.0(@typescript-eslint/parser@8.43.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)
|
'@typescript-eslint/eslint-plugin': 8.48.1(@typescript-eslint/parser@8.48.1(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)
|
||||||
'@typescript-eslint/parser': 8.43.0(eslint@8.57.1)(typescript@5.9.3)
|
'@typescript-eslint/parser': 8.48.1(eslint@8.57.1)(typescript@5.9.3)
|
||||||
eslint: 8.57.1
|
eslint: 8.57.1
|
||||||
eslint-import-resolver-node: 0.3.9
|
eslint-import-resolver-node: 0.3.9
|
||||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1)
|
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1)
|
||||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.43.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1)
|
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.48.1(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1)
|
||||||
eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1)
|
eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1)
|
||||||
eslint-plugin-react: 7.37.5(eslint@8.57.1)
|
eslint-plugin-react: 7.37.5(eslint@8.57.1)
|
||||||
eslint-plugin-react-hooks: 5.2.0(eslint@8.57.1)
|
eslint-plugin-react-hooks: 5.2.0(eslint@8.57.1)
|
||||||
@@ -12556,7 +12665,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
debug: 3.2.7
|
debug: 3.2.7
|
||||||
is-core-module: 2.16.1
|
is-core-module: 2.16.1
|
||||||
resolve: 1.22.10
|
resolve: 1.22.11
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -12565,28 +12674,28 @@ snapshots:
|
|||||||
'@nolyfill/is-core-module': 1.0.39
|
'@nolyfill/is-core-module': 1.0.39
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
eslint: 8.57.1
|
eslint: 8.57.1
|
||||||
get-tsconfig: 4.10.1
|
get-tsconfig: 4.13.0
|
||||||
is-bun-module: 2.0.0
|
is-bun-module: 2.0.0
|
||||||
stable-hash: 0.0.5
|
stable-hash: 0.0.5
|
||||||
tinyglobby: 0.2.15
|
tinyglobby: 0.2.15
|
||||||
unrs-resolver: 1.11.1
|
unrs-resolver: 1.11.1
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.43.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1)
|
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.48.1(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
eslint-module-utils@2.12.1(@typescript-eslint/parser@8.43.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1):
|
eslint-module-utils@2.12.1(@typescript-eslint/parser@8.48.1(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 3.2.7
|
debug: 3.2.7
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@typescript-eslint/parser': 8.43.0(eslint@8.57.1)(typescript@5.9.3)
|
'@typescript-eslint/parser': 8.48.1(eslint@8.57.1)(typescript@5.9.3)
|
||||||
eslint: 8.57.1
|
eslint: 8.57.1
|
||||||
eslint-import-resolver-node: 0.3.9
|
eslint-import-resolver-node: 0.3.9
|
||||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1)
|
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1):
|
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@rtsao/scc': 1.1.0
|
'@rtsao/scc': 1.1.0
|
||||||
array-includes: 3.1.9
|
array-includes: 3.1.9
|
||||||
@@ -12597,7 +12706,7 @@ snapshots:
|
|||||||
doctrine: 2.1.0
|
doctrine: 2.1.0
|
||||||
eslint: 8.57.1
|
eslint: 8.57.1
|
||||||
eslint-import-resolver-node: 0.3.9
|
eslint-import-resolver-node: 0.3.9
|
||||||
eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.43.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1)
|
eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.48.1(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1)
|
||||||
hasown: 2.0.2
|
hasown: 2.0.2
|
||||||
is-core-module: 2.16.1
|
is-core-module: 2.16.1
|
||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
@@ -12609,7 +12718,7 @@ snapshots:
|
|||||||
string.prototype.trimend: 1.0.9
|
string.prototype.trimend: 1.0.9
|
||||||
tsconfig-paths: 3.15.0
|
tsconfig-paths: 3.15.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@typescript-eslint/parser': 8.43.0(eslint@8.57.1)(typescript@5.9.3)
|
'@typescript-eslint/parser': 8.48.1(eslint@8.57.1)(typescript@5.9.3)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- eslint-import-resolver-typescript
|
- eslint-import-resolver-typescript
|
||||||
- eslint-import-resolver-webpack
|
- eslint-import-resolver-webpack
|
||||||
@@ -12958,6 +13067,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
next: 15.4.10(@babel/core@7.28.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
next: 15.4.10(@babel/core@7.28.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
|
||||||
|
generator-function@2.0.1: {}
|
||||||
|
|
||||||
gensync@1.0.0-beta.2: {}
|
gensync@1.0.0-beta.2: {}
|
||||||
|
|
||||||
get-caller-file@2.0.5: {}
|
get-caller-file@2.0.5: {}
|
||||||
@@ -12990,7 +13101,7 @@ snapshots:
|
|||||||
es-errors: 1.3.0
|
es-errors: 1.3.0
|
||||||
get-intrinsic: 1.3.0
|
get-intrinsic: 1.3.0
|
||||||
|
|
||||||
get-tsconfig@4.10.1:
|
get-tsconfig@4.13.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
resolve-pkg-maps: 1.0.0
|
resolve-pkg-maps: 1.0.0
|
||||||
|
|
||||||
@@ -13274,13 +13385,6 @@ snapshots:
|
|||||||
parent-module: 1.0.1
|
parent-module: 1.0.1
|
||||||
resolve-from: 4.0.0
|
resolve-from: 4.0.0
|
||||||
|
|
||||||
import-in-the-middle@1.14.2:
|
|
||||||
dependencies:
|
|
||||||
acorn: 8.15.0
|
|
||||||
acorn-import-attributes: 1.9.5(acorn@8.15.0)
|
|
||||||
cjs-module-lexer: 1.4.3
|
|
||||||
module-details-from-path: 1.0.4
|
|
||||||
|
|
||||||
import-in-the-middle@2.0.0:
|
import-in-the-middle@2.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
acorn: 8.15.0
|
acorn: 8.15.0
|
||||||
@@ -13357,7 +13461,7 @@ snapshots:
|
|||||||
|
|
||||||
is-bun-module@2.0.0:
|
is-bun-module@2.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
semver: 7.7.2
|
semver: 7.7.3
|
||||||
|
|
||||||
is-callable@1.2.7: {}
|
is-callable@1.2.7: {}
|
||||||
|
|
||||||
@@ -13395,6 +13499,14 @@ snapshots:
|
|||||||
has-tostringtag: 1.0.2
|
has-tostringtag: 1.0.2
|
||||||
safe-regex-test: 1.1.0
|
safe-regex-test: 1.1.0
|
||||||
|
|
||||||
|
is-generator-function@1.1.2:
|
||||||
|
dependencies:
|
||||||
|
call-bound: 1.0.4
|
||||||
|
generator-function: 2.0.1
|
||||||
|
get-proto: 1.0.1
|
||||||
|
has-tostringtag: 1.0.2
|
||||||
|
safe-regex-test: 1.1.0
|
||||||
|
|
||||||
is-glob@4.0.3:
|
is-glob@4.0.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-extglob: 2.1.1
|
is-extglob: 2.1.1
|
||||||
@@ -14215,7 +14327,7 @@ snapshots:
|
|||||||
|
|
||||||
nanoid@3.3.11: {}
|
nanoid@3.3.11: {}
|
||||||
|
|
||||||
napi-postinstall@0.3.3: {}
|
napi-postinstall@0.3.4: {}
|
||||||
|
|
||||||
natural-compare@1.4.0: {}
|
natural-compare@1.4.0: {}
|
||||||
|
|
||||||
@@ -15185,6 +15297,12 @@ snapshots:
|
|||||||
path-parse: 1.0.7
|
path-parse: 1.0.7
|
||||||
supports-preserve-symlinks-flag: 1.0.0
|
supports-preserve-symlinks-flag: 1.0.0
|
||||||
|
|
||||||
|
resolve@1.22.11:
|
||||||
|
dependencies:
|
||||||
|
is-core-module: 2.16.1
|
||||||
|
path-parse: 1.0.7
|
||||||
|
supports-preserve-symlinks-flag: 1.0.0
|
||||||
|
|
||||||
resolve@1.22.8:
|
resolve@1.22.8:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-core-module: 2.16.1
|
is-core-module: 2.16.1
|
||||||
@@ -15996,7 +16114,7 @@ snapshots:
|
|||||||
|
|
||||||
unrs-resolver@1.11.1:
|
unrs-resolver@1.11.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
napi-postinstall: 0.3.3
|
napi-postinstall: 0.3.4
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@unrs/resolver-binding-android-arm-eabi': 1.11.1
|
'@unrs/resolver-binding-android-arm-eabi': 1.11.1
|
||||||
'@unrs/resolver-binding-android-arm64': 1.11.1
|
'@unrs/resolver-binding-android-arm64': 1.11.1
|
||||||
@@ -16224,7 +16342,7 @@ snapshots:
|
|||||||
is-async-function: 2.1.1
|
is-async-function: 2.1.1
|
||||||
is-date-object: 1.1.0
|
is-date-object: 1.1.0
|
||||||
is-finalizationregistry: 1.1.1
|
is-finalizationregistry: 1.1.1
|
||||||
is-generator-function: 1.1.0
|
is-generator-function: 1.1.2
|
||||||
is-regex: 1.2.1
|
is-regex: 1.2.1
|
||||||
is-weakref: 1.1.1
|
is-weakref: 1.1.1
|
||||||
isarray: 2.0.5
|
isarray: 2.0.5
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import {
|
|||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/__legacy__/ui/card";
|
} from "@/components/__legacy__/ui/card";
|
||||||
import { ErrorCard } from "@/components/molecules/ErrorCard/ErrorCard";
|
import { ErrorCard } from "@/components/molecules/ErrorCard/ErrorCard";
|
||||||
import { InformationTooltip } from "@/components/molecules/InformationTooltip/InformationTooltip";
|
|
||||||
import { CircleNotchIcon } from "@phosphor-icons/react/dist/ssr";
|
import { CircleNotchIcon } from "@phosphor-icons/react/dist/ssr";
|
||||||
import { Play } from "lucide-react";
|
import { Play } from "lucide-react";
|
||||||
import OnboardingButton from "../components/OnboardingButton";
|
import OnboardingButton from "../components/OnboardingButton";
|
||||||
@@ -79,20 +78,13 @@ export default function Page() {
|
|||||||
<CardContent className="flex flex-col gap-4">
|
<CardContent className="flex flex-col gap-4">
|
||||||
{Object.entries(agent?.input_schema.properties || {}).map(
|
{Object.entries(agent?.input_schema.properties || {}).map(
|
||||||
([key, inputSubSchema]) => (
|
([key, inputSubSchema]) => (
|
||||||
<div key={key} className="flex flex-col space-y-2">
|
<RunAgentInputs
|
||||||
<label className="flex items-center gap-1 text-sm font-medium">
|
key={key}
|
||||||
{inputSubSchema.title || key}
|
schema={inputSubSchema}
|
||||||
<InformationTooltip
|
value={onboarding.state?.agentInput?.[key]}
|
||||||
description={inputSubSchema.description}
|
placeholder={inputSubSchema.description}
|
||||||
/>
|
onChange={(value) => handleSetAgentInput(key, value)}
|
||||||
</label>
|
/>
|
||||||
<RunAgentInputs
|
|
||||||
schema={inputSubSchema}
|
|
||||||
value={onboarding.state?.agentInput?.[key]}
|
|
||||||
placeholder={inputSubSchema.description}
|
|
||||||
onChange={(value) => handleSetAgentInput(key, value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
),
|
),
|
||||||
)}
|
)}
|
||||||
<AgentOnboardingCredentials
|
<AgentOnboardingCredentials
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { parseAsString, useQueryStates } from "nuqs";
|
|||||||
import { AgentOutputs } from "./components/AgentOutputs/AgentOutputs";
|
import { AgentOutputs } from "./components/AgentOutputs/AgentOutputs";
|
||||||
import { RunGraph } from "./components/RunGraph/RunGraph";
|
import { RunGraph } from "./components/RunGraph/RunGraph";
|
||||||
import { ScheduleGraph } from "./components/ScheduleGraph/ScheduleGraph";
|
import { ScheduleGraph } from "./components/ScheduleGraph/ScheduleGraph";
|
||||||
|
import { PublishToMarketplace } from "./components/PublishToMarketplace/PublishToMarketplace";
|
||||||
import { memo } from "react";
|
import { memo } from "react";
|
||||||
|
|
||||||
export const BuilderActions = memo(() => {
|
export const BuilderActions = memo(() => {
|
||||||
@@ -13,6 +14,7 @@ export const BuilderActions = memo(() => {
|
|||||||
<AgentOutputs flowID={flowID} />
|
<AgentOutputs flowID={flowID} />
|
||||||
<RunGraph flowID={flowID} />
|
<RunGraph flowID={flowID} />
|
||||||
<ScheduleGraph flowID={flowID} />
|
<ScheduleGraph flowID={flowID} />
|
||||||
|
<PublishToMarketplace flowID={flowID} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { ShareIcon } from "@phosphor-icons/react";
|
||||||
|
import { BuilderActionButton } from "../BuilderActionButton";
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from "@/components/atoms/Tooltip/BaseTooltip";
|
||||||
|
import { usePublishToMarketplace } from "./usePublishToMarketplace";
|
||||||
|
import { PublishAgentModal } from "@/components/contextual/PublishAgentModal/PublishAgentModal";
|
||||||
|
|
||||||
|
export const PublishToMarketplace = ({ flowID }: { flowID: string | null }) => {
|
||||||
|
const { handlePublishToMarketplace, publishState, handleStateChange } =
|
||||||
|
usePublishToMarketplace({ flowID });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<BuilderActionButton
|
||||||
|
onClick={handlePublishToMarketplace}
|
||||||
|
disabled={!flowID}
|
||||||
|
>
|
||||||
|
<ShareIcon className="size-6 drop-shadow-sm" />
|
||||||
|
</BuilderActionButton>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>Publish to Marketplace</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<PublishAgentModal
|
||||||
|
targetState={publishState}
|
||||||
|
onStateChange={handleStateChange}
|
||||||
|
preSelectedAgentId={flowID || undefined}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { useState, useCallback } from "react";
|
||||||
|
import type { StoreSubmission } from "@/app/api/__generated__/models/storeSubmission";
|
||||||
|
|
||||||
|
export type PublishStep = "select" | "info" | "review";
|
||||||
|
|
||||||
|
export type PublishState = {
|
||||||
|
isOpen: boolean;
|
||||||
|
step: PublishStep;
|
||||||
|
submissionData: StoreSubmission | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultPublishState: PublishState = {
|
||||||
|
isOpen: false,
|
||||||
|
step: "select",
|
||||||
|
submissionData: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
interface UsePublishToMarketplaceProps {
|
||||||
|
flowID: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function usePublishToMarketplace({
|
||||||
|
flowID,
|
||||||
|
}: UsePublishToMarketplaceProps) {
|
||||||
|
const [publishState, setPublishState] =
|
||||||
|
useState<PublishState>(defaultPublishState);
|
||||||
|
|
||||||
|
const handlePublishToMarketplace = () => {
|
||||||
|
if (!flowID) return;
|
||||||
|
|
||||||
|
// Open the publish modal starting with the select step
|
||||||
|
setPublishState({
|
||||||
|
isOpen: true,
|
||||||
|
step: "select",
|
||||||
|
submissionData: null,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleStateChange = useCallback((newState: PublishState) => {
|
||||||
|
setPublishState(newState);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
handlePublishToMarketplace,
|
||||||
|
publishState,
|
||||||
|
handleStateChange,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ import { AgentExecutionStatus } from "@/app/api/__generated__/models/agentExecut
|
|||||||
|
|
||||||
export const useFlow = () => {
|
export const useFlow = () => {
|
||||||
const [isLocked, setIsLocked] = useState(false);
|
const [isLocked, setIsLocked] = useState(false);
|
||||||
|
const [hasAutoFramed, setHasAutoFramed] = useState(false);
|
||||||
const addNodes = useNodeStore(useShallow((state) => state.addNodes));
|
const addNodes = useNodeStore(useShallow((state) => state.addNodes));
|
||||||
const addLinks = useEdgeStore(useShallow((state) => state.addLinks));
|
const addLinks = useEdgeStore(useShallow((state) => state.addLinks));
|
||||||
const updateNodeStatus = useNodeStore(
|
const updateNodeStatus = useNodeStore(
|
||||||
@@ -187,9 +188,36 @@ export const useFlow = () => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const linkCount = graph?.links?.length ?? 0;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fitView({ padding: 0.2, duration: 800, maxZoom: 2 });
|
if (isGraphLoading || isBlocksLoading) {
|
||||||
}, [fitView]);
|
setHasAutoFramed(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasAutoFramed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rafId = requestAnimationFrame(() => {
|
||||||
|
fitView({ padding: 0.2, duration: 800, maxZoom: 1 });
|
||||||
|
setHasAutoFramed(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => cancelAnimationFrame(rafId);
|
||||||
|
}, [
|
||||||
|
fitView,
|
||||||
|
hasAutoFramed,
|
||||||
|
customNodes.length,
|
||||||
|
isBlocksLoading,
|
||||||
|
isGraphLoading,
|
||||||
|
linkCount,
|
||||||
|
]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setHasAutoFramed(false);
|
||||||
|
}, [flowID, flowVersion]);
|
||||||
|
|
||||||
// Drag and drop block from block menu
|
// Drag and drop block from block menu
|
||||||
const onDragOver = useCallback((event: React.DragEvent) => {
|
const onDragOver = useCallback((event: React.DragEvent) => {
|
||||||
|
|||||||
@@ -106,7 +106,11 @@ export const CustomNode: React.FC<NodeProps<CustomNode>> = React.memo(
|
|||||||
/>
|
/>
|
||||||
<NodeAdvancedToggle nodeId={nodeId} />
|
<NodeAdvancedToggle nodeId={nodeId} />
|
||||||
{data.uiType != BlockUIType.OUTPUT && (
|
{data.uiType != BlockUIType.OUTPUT && (
|
||||||
<OutputHandler outputSchema={outputSchema} nodeId={nodeId} />
|
<OutputHandler
|
||||||
|
uiType={data.uiType}
|
||||||
|
outputSchema={outputSchema}
|
||||||
|
nodeId={nodeId}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
<NodeDataRenderer nodeId={nodeId} />
|
<NodeDataRenderer nodeId={nodeId} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,17 +20,32 @@ export const FormCreator = React.memo(
|
|||||||
className?: string;
|
className?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const updateNodeData = useNodeStore((state) => state.updateNodeData);
|
const updateNodeData = useNodeStore((state) => state.updateNodeData);
|
||||||
|
|
||||||
const getHardCodedValues = useNodeStore(
|
const getHardCodedValues = useNodeStore(
|
||||||
(state) => state.getHardCodedValues,
|
(state) => state.getHardCodedValues,
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleChange = ({ formData }: any) => {
|
const handleChange = ({ formData }: any) => {
|
||||||
if ("credentials" in formData && !formData.credentials?.id) {
|
if ("credentials" in formData && !formData.credentials?.id) {
|
||||||
delete formData.credentials;
|
delete formData.credentials;
|
||||||
}
|
}
|
||||||
updateNodeData(nodeId, { hardcodedValues: formData });
|
|
||||||
|
const updatedValues =
|
||||||
|
uiType === BlockUIType.AGENT
|
||||||
|
? {
|
||||||
|
...getHardCodedValues(nodeId),
|
||||||
|
inputs: formData,
|
||||||
|
}
|
||||||
|
: formData;
|
||||||
|
|
||||||
|
updateNodeData(nodeId, { hardcodedValues: updatedValues });
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialValues = getHardCodedValues(nodeId);
|
const hardcodedValues = getHardCodedValues(nodeId);
|
||||||
|
const initialValues =
|
||||||
|
uiType === BlockUIType.AGENT
|
||||||
|
? (hardcodedValues.inputs ?? {})
|
||||||
|
: hardcodedValues;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
|
|||||||
@@ -14,13 +14,16 @@ import {
|
|||||||
import { useEdgeStore } from "@/app/(platform)/build/stores/edgeStore";
|
import { useEdgeStore } from "@/app/(platform)/build/stores/edgeStore";
|
||||||
import { getTypeDisplayInfo } from "./helpers";
|
import { getTypeDisplayInfo } from "./helpers";
|
||||||
import { generateHandleId } from "../handlers/helpers";
|
import { generateHandleId } from "../handlers/helpers";
|
||||||
|
import { BlockUIType } from "../../types";
|
||||||
|
|
||||||
export const OutputHandler = ({
|
export const OutputHandler = ({
|
||||||
outputSchema,
|
outputSchema,
|
||||||
nodeId,
|
nodeId,
|
||||||
|
uiType,
|
||||||
}: {
|
}: {
|
||||||
outputSchema: RJSFSchema;
|
outputSchema: RJSFSchema;
|
||||||
nodeId: string;
|
nodeId: string;
|
||||||
|
uiType: BlockUIType;
|
||||||
}) => {
|
}) => {
|
||||||
const { isOutputConnected } = useEdgeStore();
|
const { isOutputConnected } = useEdgeStore();
|
||||||
const properties = outputSchema?.properties || {};
|
const properties = outputSchema?.properties || {};
|
||||||
@@ -79,7 +82,9 @@ export const OutputHandler = ({
|
|||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<NodeHandle
|
<NodeHandle
|
||||||
handleId={generateHandleId(key)}
|
handleId={
|
||||||
|
uiType === BlockUIType.AGENT ? key : generateHandleId(key)
|
||||||
|
}
|
||||||
isConnected={isConnected}
|
isConnected={isConnected}
|
||||||
side="right"
|
side="right"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
|
|||||||
import { getV2GetSpecificAgent } from "@/app/api/__generated__/endpoints/store/store";
|
import { getV2GetSpecificAgent } from "@/app/api/__generated__/endpoints/store/store";
|
||||||
import {
|
import {
|
||||||
getGetV2ListLibraryAgentsQueryKey,
|
getGetV2ListLibraryAgentsQueryKey,
|
||||||
|
getV2GetLibraryAgent,
|
||||||
usePostV2AddMarketplaceAgent,
|
usePostV2AddMarketplaceAgent,
|
||||||
} from "@/app/api/__generated__/endpoints/library/library";
|
} from "@/app/api/__generated__/endpoints/library/library";
|
||||||
import {
|
import {
|
||||||
@@ -151,7 +152,12 @@ export const useBlockMenuSearch = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const libraryAgent = response.data as LibraryAgent;
|
const libraryAgent = response.data as LibraryAgent;
|
||||||
addAgentToBuilder(libraryAgent);
|
|
||||||
|
const { data: libraryAgentDetails } = await getV2GetLibraryAgent(
|
||||||
|
libraryAgent.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
addAgentToBuilder(libraryAgentDetails as LibraryAgent);
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: "Agent Added",
|
title: "Agent Added",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { getGetV2GetBuilderItemCountsQueryKey } from "@/app/api/__generated__/endpoints/default/default";
|
import { getGetV2GetBuilderItemCountsQueryKey } from "@/app/api/__generated__/endpoints/default/default";
|
||||||
import {
|
import {
|
||||||
getGetV2ListLibraryAgentsQueryKey,
|
getGetV2ListLibraryAgentsQueryKey,
|
||||||
|
getV2GetLibraryAgent,
|
||||||
usePostV2AddMarketplaceAgent,
|
usePostV2AddMarketplaceAgent,
|
||||||
} from "@/app/api/__generated__/endpoints/library/library";
|
} from "@/app/api/__generated__/endpoints/library/library";
|
||||||
import {
|
import {
|
||||||
@@ -105,8 +106,16 @@ export const useMarketplaceAgentsContent = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Here, libraryAgent has empty input and output schemas.
|
||||||
|
// Not updating the endpoint because this endpoint is used elsewhere.
|
||||||
|
// TODO: Create a new endpoint for builder specific to marketplace agents.
|
||||||
const libraryAgent = response.data as LibraryAgent;
|
const libraryAgent = response.data as LibraryAgent;
|
||||||
addAgentToBuilder(libraryAgent);
|
|
||||||
|
const { data: libraryAgentDetails } = await getV2GetLibraryAgent(
|
||||||
|
libraryAgent.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
addAgentToBuilder(libraryAgentDetails as LibraryAgent);
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: "Agent Added",
|
title: "Agent Added",
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ const FlowEditor: React.FC<{
|
|||||||
updateNode,
|
updateNode,
|
||||||
getViewport,
|
getViewport,
|
||||||
setViewport,
|
setViewport,
|
||||||
|
fitView,
|
||||||
screenToFlowPosition,
|
screenToFlowPosition,
|
||||||
} = useReactFlow<CustomNode, CustomEdge>();
|
} = useReactFlow<CustomNode, CustomEdge>();
|
||||||
const [nodeId, setNodeId] = useState<number>(1);
|
const [nodeId, setNodeId] = useState<number>(1);
|
||||||
@@ -115,6 +116,7 @@ const FlowEditor: React.FC<{
|
|||||||
const [pinBlocksPopover, setPinBlocksPopover] = useState(false);
|
const [pinBlocksPopover, setPinBlocksPopover] = useState(false);
|
||||||
// State to control if save popover should be pinned open
|
// State to control if save popover should be pinned open
|
||||||
const [pinSavePopover, setPinSavePopover] = useState(false);
|
const [pinSavePopover, setPinSavePopover] = useState(false);
|
||||||
|
const [hasAutoFramed, setHasAutoFramed] = useState(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
agentName,
|
agentName,
|
||||||
@@ -482,35 +484,26 @@ const FlowEditor: React.FC<{
|
|||||||
return uuidv4();
|
return uuidv4();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Set the initial view port to center the canvas.
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const { x, y } = getViewport();
|
if (nodes.length === 0) {
|
||||||
if (nodes.length <= 0 || x !== 0 || y !== 0) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const topLeft = { x: Infinity, y: Infinity };
|
if (hasAutoFramed) {
|
||||||
const bottomRight = { x: -Infinity, y: -Infinity };
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
nodes.forEach((node) => {
|
const rafId = requestAnimationFrame(() => {
|
||||||
const { x, y } = node.position;
|
fitView({ padding: 0.2, duration: 800, maxZoom: 1 });
|
||||||
topLeft.x = Math.min(topLeft.x, x);
|
setHasAutoFramed(true);
|
||||||
topLeft.y = Math.min(topLeft.y, y);
|
|
||||||
// Rough estimate of the width and height of the node: 500x400.
|
|
||||||
bottomRight.x = Math.max(bottomRight.x, x + 500);
|
|
||||||
bottomRight.y = Math.max(bottomRight.y, y + 400);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const centerX = (topLeft.x + bottomRight.x) / 2;
|
return () => cancelAnimationFrame(rafId);
|
||||||
const centerY = (topLeft.y + bottomRight.y) / 2;
|
}, [fitView, hasAutoFramed, nodes.length]);
|
||||||
const zoom = 0.8;
|
|
||||||
|
|
||||||
setViewport({
|
useEffect(() => {
|
||||||
x: window.innerWidth / 2 - centerX * zoom,
|
setHasAutoFramed(false);
|
||||||
y: window.innerHeight / 2 - centerY * zoom,
|
}, [flowID, flowVersion]);
|
||||||
zoom: zoom,
|
|
||||||
});
|
|
||||||
}, [nodes, getViewport, setViewport]);
|
|
||||||
|
|
||||||
const navigateToNode = useCallback(
|
const navigateToNode = useCallback(
|
||||||
(nodeId: string) => {
|
(nodeId: string) => {
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Button } from "@/components/atoms/Button/Button";
|
||||||
|
import { Text } from "@/components/atoms/Text/Text";
|
||||||
|
|
||||||
|
interface MarketplaceBannersProps {
|
||||||
|
hasUpdate?: boolean;
|
||||||
|
latestVersion?: number;
|
||||||
|
hasUnpublishedChanges?: boolean;
|
||||||
|
currentVersion?: number;
|
||||||
|
isUpdating?: boolean;
|
||||||
|
onUpdate?: () => void;
|
||||||
|
onPublish?: () => void;
|
||||||
|
onViewChanges?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MarketplaceBanners({
|
||||||
|
hasUpdate,
|
||||||
|
latestVersion,
|
||||||
|
hasUnpublishedChanges,
|
||||||
|
isUpdating,
|
||||||
|
onUpdate,
|
||||||
|
onPublish,
|
||||||
|
}: MarketplaceBannersProps) {
|
||||||
|
const renderUpdateBanner = () => {
|
||||||
|
if (hasUpdate && latestVersion) {
|
||||||
|
return (
|
||||||
|
<div className="mb-6 rounded-lg bg-gray-50 p-4 dark:bg-gray-900">
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<div>
|
||||||
|
<Text
|
||||||
|
variant="large-medium"
|
||||||
|
className="mb-2 text-neutral-900 dark:text-neutral-100"
|
||||||
|
>
|
||||||
|
Update available
|
||||||
|
</Text>
|
||||||
|
<Text variant="body" className="text-gray-700 dark:text-gray-300">
|
||||||
|
You should update your agent in order to get the latest / best
|
||||||
|
results
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
{onUpdate && (
|
||||||
|
<div className="flex justify-start">
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
onClick={onUpdate}
|
||||||
|
disabled={isUpdating}
|
||||||
|
className="bg-neutral-800 text-white hover:bg-neutral-900 dark:bg-neutral-700 dark:hover:bg-neutral-800"
|
||||||
|
>
|
||||||
|
{isUpdating ? "Updating..." : "Update agent"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderUnpublishedChangesBanner = () => {
|
||||||
|
if (hasUnpublishedChanges) {
|
||||||
|
return (
|
||||||
|
<div className="mb-6 rounded-lg bg-gray-50 p-4 dark:bg-gray-900">
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<div>
|
||||||
|
<Text
|
||||||
|
variant="large-medium"
|
||||||
|
className="mb-2 text-neutral-900 dark:text-neutral-100"
|
||||||
|
>
|
||||||
|
Unpublished changes
|
||||||
|
</Text>
|
||||||
|
<Text variant="body" className="text-gray-700 dark:text-gray-300">
|
||||||
|
You've made changes to this agent that aren't
|
||||||
|
published yet. Would you like to publish the latest version?
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
{onPublish && (
|
||||||
|
<div className="flex justify-start">
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
onClick={onPublish}
|
||||||
|
className="bg-neutral-800 text-white hover:bg-neutral-900 dark:bg-neutral-700 dark:hover:bg-neutral-800"
|
||||||
|
>
|
||||||
|
Publish changes
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{renderUpdateBanner()}
|
||||||
|
{renderUnpublishedChangesBanner()}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,14 +1,18 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Button } from "@/components/atoms/Button/Button";
|
import { Button } from "@/components/atoms/Button/Button";
|
||||||
import { Breadcrumbs } from "@/components/molecules/Breadcrumbs/Breadcrumbs";
|
|
||||||
import { ErrorCard } from "@/components/molecules/ErrorCard/ErrorCard";
|
import { ErrorCard } from "@/components/molecules/ErrorCard/ErrorCard";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { PlusIcon } from "@phosphor-icons/react";
|
import { PlusIcon } from "@phosphor-icons/react";
|
||||||
|
import * as React from "react";
|
||||||
import { RunAgentModal } from "./components/modals/RunAgentModal/RunAgentModal";
|
import { RunAgentModal } from "./components/modals/RunAgentModal/RunAgentModal";
|
||||||
|
import { useMarketplaceUpdate } from "./hooks/useMarketplaceUpdate";
|
||||||
|
import { AgentVersionChangelog } from "./components/AgentVersionChangelog";
|
||||||
|
import { MarketplaceBanners } from "../../../../../components/MarketplaceBanners/MarketplaceBanners";
|
||||||
import { AgentRunsLoading } from "./components/other/AgentRunsLoading";
|
import { AgentRunsLoading } from "./components/other/AgentRunsLoading";
|
||||||
import { EmptySchedules } from "./components/other/EmptySchedules";
|
import { EmptySchedules } from "./components/other/EmptySchedules";
|
||||||
import { EmptyTasks } from "./components/other/EmptyTasks";
|
import { EmptyTasks } from "./components/other/EmptyTasks";
|
||||||
|
import { PublishAgentModal } from "@/components/contextual/PublishAgentModal/PublishAgentModal";
|
||||||
import { EmptyTemplates } from "./components/other/EmptyTemplates";
|
import { EmptyTemplates } from "./components/other/EmptyTemplates";
|
||||||
import { EmptyTriggers } from "./components/other/EmptyTriggers";
|
import { EmptyTriggers } from "./components/other/EmptyTriggers";
|
||||||
import { SectionWrap } from "./components/other/SectionWrap";
|
import { SectionWrap } from "./components/other/SectionWrap";
|
||||||
@@ -24,7 +28,6 @@ import { useNewAgentLibraryView } from "./useNewAgentLibraryView";
|
|||||||
|
|
||||||
export function NewAgentLibraryView() {
|
export function NewAgentLibraryView() {
|
||||||
const {
|
const {
|
||||||
agentId,
|
|
||||||
agent,
|
agent,
|
||||||
ready,
|
ready,
|
||||||
activeTemplate,
|
activeTemplate,
|
||||||
@@ -43,6 +46,189 @@ export function NewAgentLibraryView() {
|
|||||||
onScheduleCreated,
|
onScheduleCreated,
|
||||||
} = useNewAgentLibraryView();
|
} = useNewAgentLibraryView();
|
||||||
|
|
||||||
|
const {
|
||||||
|
hasAgentMarketplaceUpdate,
|
||||||
|
hasMarketplaceUpdate,
|
||||||
|
latestMarketplaceVersion,
|
||||||
|
isUpdating,
|
||||||
|
modalOpen,
|
||||||
|
setModalOpen,
|
||||||
|
handlePublishUpdate,
|
||||||
|
handleUpdateToLatest,
|
||||||
|
} = useMarketplaceUpdate({ agent });
|
||||||
|
|
||||||
|
const [changelogOpen, setChangelogOpen] = React.useState(false);
|
||||||
|
|
||||||
|
function renderMarketplaceUpdateBanner() {
|
||||||
|
return (
|
||||||
|
<MarketplaceBanners
|
||||||
|
hasUpdate={!!hasMarketplaceUpdate}
|
||||||
|
latestVersion={latestMarketplaceVersion}
|
||||||
|
hasUnpublishedChanges={!!hasAgentMarketplaceUpdate}
|
||||||
|
currentVersion={agent?.graph_version}
|
||||||
|
isUpdating={isUpdating}
|
||||||
|
onUpdate={handleUpdateToLatest}
|
||||||
|
onPublish={handlePublishUpdate}
|
||||||
|
onViewChanges={() => setChangelogOpen(true)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderPublishAgentModal() {
|
||||||
|
if (!modalOpen || !agent) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PublishAgentModal
|
||||||
|
targetState={{
|
||||||
|
isOpen: true,
|
||||||
|
step: "info",
|
||||||
|
submissionData: { isMarketplaceUpdate: true } as any,
|
||||||
|
}}
|
||||||
|
preSelectedAgentId={agent.graph_id}
|
||||||
|
preSelectedAgentVersion={agent.graph_version}
|
||||||
|
onStateChange={(state) => {
|
||||||
|
if (!state.isOpen) {
|
||||||
|
setModalOpen(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderAgentLibraryView() {
|
||||||
|
if (!agent) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mx-4 grid h-full grid-cols-1 gap-0 pt-3 md:ml-4 md:mr-0 md:gap-4 lg:grid-cols-[25%_70%]">
|
||||||
|
<SectionWrap className="mb-3 block">
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"border-b border-zinc-100 pb-5",
|
||||||
|
AGENT_LIBRARY_SECTION_PADDING_X,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<RunAgentModal
|
||||||
|
triggerSlot={
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
size="large"
|
||||||
|
className="w-full"
|
||||||
|
disabled={isTemplateLoading && activeTab === "templates"}
|
||||||
|
>
|
||||||
|
<PlusIcon size={20} /> New task
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
agent={agent}
|
||||||
|
onRunCreated={onRunInitiated}
|
||||||
|
onScheduleCreated={onScheduleCreated}
|
||||||
|
onTriggerSetup={onTriggerSetup}
|
||||||
|
initialInputValues={activeTemplate?.inputs}
|
||||||
|
initialInputCredentials={activeTemplate?.credentials}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<SidebarRunsList
|
||||||
|
agent={agent}
|
||||||
|
selectedRunId={activeItem ?? undefined}
|
||||||
|
onSelectRun={handleSelectRun}
|
||||||
|
onClearSelectedRun={handleClearSelectedRun}
|
||||||
|
onTabChange={setActiveTab}
|
||||||
|
onCountsChange={handleCountsChange}
|
||||||
|
/>
|
||||||
|
</SectionWrap>
|
||||||
|
|
||||||
|
{activeItem ? (
|
||||||
|
activeTab === "scheduled" ? (
|
||||||
|
<SelectedScheduleView
|
||||||
|
agent={agent}
|
||||||
|
scheduleId={activeItem}
|
||||||
|
onClearSelectedRun={handleClearSelectedRun}
|
||||||
|
banner={renderMarketplaceUpdateBanner()}
|
||||||
|
/>
|
||||||
|
) : activeTab === "templates" ? (
|
||||||
|
<SelectedTemplateView
|
||||||
|
agent={agent}
|
||||||
|
templateId={activeItem}
|
||||||
|
onClearSelectedRun={handleClearSelectedRun}
|
||||||
|
onRunCreated={(execution) =>
|
||||||
|
handleSelectRun(execution.id, "runs")
|
||||||
|
}
|
||||||
|
onSwitchToRunsTab={() => setActiveTab("runs")}
|
||||||
|
banner={renderMarketplaceUpdateBanner()}
|
||||||
|
/>
|
||||||
|
) : activeTab === "triggers" ? (
|
||||||
|
<SelectedTriggerView
|
||||||
|
agent={agent}
|
||||||
|
triggerId={activeItem}
|
||||||
|
onClearSelectedRun={handleClearSelectedRun}
|
||||||
|
onSwitchToRunsTab={() => setActiveTab("runs")}
|
||||||
|
banner={renderMarketplaceUpdateBanner()}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<SelectedRunView
|
||||||
|
agent={agent}
|
||||||
|
runId={activeItem}
|
||||||
|
onSelectRun={handleSelectRun}
|
||||||
|
onClearSelectedRun={handleClearSelectedRun}
|
||||||
|
banner={renderMarketplaceUpdateBanner()}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
) : sidebarLoading ? (
|
||||||
|
<LoadingSelectedContent agentName={agent.name} agentId={agent.id} />
|
||||||
|
) : activeTab === "scheduled" ? (
|
||||||
|
<SelectedViewLayout
|
||||||
|
agentName={agent.name}
|
||||||
|
agentId={agent.id}
|
||||||
|
banner={renderMarketplaceUpdateBanner()}
|
||||||
|
>
|
||||||
|
<EmptySchedules />
|
||||||
|
</SelectedViewLayout>
|
||||||
|
) : activeTab === "templates" ? (
|
||||||
|
<SelectedViewLayout
|
||||||
|
agentName={agent.name}
|
||||||
|
agentId={agent.id}
|
||||||
|
banner={renderMarketplaceUpdateBanner()}
|
||||||
|
>
|
||||||
|
<EmptyTemplates />
|
||||||
|
</SelectedViewLayout>
|
||||||
|
) : activeTab === "triggers" ? (
|
||||||
|
<SelectedViewLayout
|
||||||
|
agentName={agent.name}
|
||||||
|
agentId={agent.id}
|
||||||
|
banner={renderMarketplaceUpdateBanner()}
|
||||||
|
>
|
||||||
|
<EmptyTriggers />
|
||||||
|
</SelectedViewLayout>
|
||||||
|
) : (
|
||||||
|
<SelectedViewLayout
|
||||||
|
agentName={agent.name}
|
||||||
|
agentId={agent.id}
|
||||||
|
banner={renderMarketplaceUpdateBanner()}
|
||||||
|
>
|
||||||
|
<EmptyTasks
|
||||||
|
agent={agent}
|
||||||
|
onRun={onRunInitiated}
|
||||||
|
onTriggerSetup={onTriggerSetup}
|
||||||
|
onScheduleCreated={onScheduleCreated}
|
||||||
|
/>
|
||||||
|
</SelectedViewLayout>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderVersionChangelog() {
|
||||||
|
if (!agent) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AgentVersionChangelog
|
||||||
|
agent={agent}
|
||||||
|
isOpen={changelogOpen}
|
||||||
|
onClose={() => setChangelogOpen(false)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<ErrorCard
|
<ErrorCard
|
||||||
@@ -60,120 +246,30 @@ export function NewAgentLibraryView() {
|
|||||||
|
|
||||||
if (!sidebarLoading && !hasAnyItems) {
|
if (!sidebarLoading && !hasAnyItems) {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full flex-col">
|
<>
|
||||||
<div className="mx-6 pt-4">
|
<SelectedViewLayout
|
||||||
<Breadcrumbs
|
agentName={agent.name}
|
||||||
items={[
|
agentId={agent.id}
|
||||||
{ name: "My Library", link: "/library" },
|
banner={renderMarketplaceUpdateBanner()}
|
||||||
{ name: agent.name, link: `/library/agents/${agentId}` },
|
>
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex min-h-0 flex-1">
|
|
||||||
<EmptyTasks
|
<EmptyTasks
|
||||||
agent={agent}
|
agent={agent}
|
||||||
onRun={onRunInitiated}
|
onRun={onRunInitiated}
|
||||||
onTriggerSetup={onTriggerSetup}
|
onTriggerSetup={onTriggerSetup}
|
||||||
onScheduleCreated={onScheduleCreated}
|
onScheduleCreated={onScheduleCreated}
|
||||||
/>
|
/>
|
||||||
</div>
|
</SelectedViewLayout>
|
||||||
</div>
|
{renderPublishAgentModal()}
|
||||||
|
{renderVersionChangelog()}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-4 grid h-full grid-cols-1 gap-0 pt-3 md:ml-4 md:mr-0 md:gap-4 lg:grid-cols-[25%_70%]">
|
<>
|
||||||
<SectionWrap className="mb-3 block">
|
{renderAgentLibraryView()}
|
||||||
<div
|
{renderPublishAgentModal()}
|
||||||
className={cn(
|
{renderVersionChangelog()}
|
||||||
"border-b border-zinc-100 pb-5",
|
</>
|
||||||
AGENT_LIBRARY_SECTION_PADDING_X,
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<RunAgentModal
|
|
||||||
triggerSlot={
|
|
||||||
<Button
|
|
||||||
variant="primary"
|
|
||||||
size="large"
|
|
||||||
className="w-full"
|
|
||||||
disabled={isTemplateLoading && activeTab === "templates"}
|
|
||||||
>
|
|
||||||
<PlusIcon size={20} /> New task
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
agent={agent}
|
|
||||||
onRunCreated={onRunInitiated}
|
|
||||||
onScheduleCreated={onScheduleCreated}
|
|
||||||
onTriggerSetup={onTriggerSetup}
|
|
||||||
initialInputValues={activeTemplate?.inputs}
|
|
||||||
initialInputCredentials={activeTemplate?.credentials}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<SidebarRunsList
|
|
||||||
agent={agent}
|
|
||||||
selectedRunId={activeItem ?? undefined}
|
|
||||||
onSelectRun={handleSelectRun}
|
|
||||||
onClearSelectedRun={handleClearSelectedRun}
|
|
||||||
onTabChange={setActiveTab}
|
|
||||||
onCountsChange={handleCountsChange}
|
|
||||||
/>
|
|
||||||
</SectionWrap>
|
|
||||||
|
|
||||||
{activeItem ? (
|
|
||||||
activeTab === "scheduled" ? (
|
|
||||||
<SelectedScheduleView
|
|
||||||
agent={agent}
|
|
||||||
scheduleId={activeItem}
|
|
||||||
onClearSelectedRun={handleClearSelectedRun}
|
|
||||||
/>
|
|
||||||
) : activeTab === "templates" ? (
|
|
||||||
<SelectedTemplateView
|
|
||||||
agent={agent}
|
|
||||||
templateId={activeItem}
|
|
||||||
onClearSelectedRun={handleClearSelectedRun}
|
|
||||||
onRunCreated={(execution) => handleSelectRun(execution.id, "runs")}
|
|
||||||
onSwitchToRunsTab={() => setActiveTab("runs")}
|
|
||||||
/>
|
|
||||||
) : activeTab === "triggers" ? (
|
|
||||||
<SelectedTriggerView
|
|
||||||
agent={agent}
|
|
||||||
triggerId={activeItem}
|
|
||||||
onClearSelectedRun={handleClearSelectedRun}
|
|
||||||
onSwitchToRunsTab={() => setActiveTab("runs")}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<SelectedRunView
|
|
||||||
agent={agent}
|
|
||||||
runId={activeItem}
|
|
||||||
onSelectRun={handleSelectRun}
|
|
||||||
onClearSelectedRun={handleClearSelectedRun}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
) : sidebarLoading ? (
|
|
||||||
<LoadingSelectedContent agentName={agent.name} agentId={agent.id} />
|
|
||||||
) : activeTab === "scheduled" ? (
|
|
||||||
<SelectedViewLayout agentName={agent.name} agentId={agent.id}>
|
|
||||||
<EmptySchedules />
|
|
||||||
</SelectedViewLayout>
|
|
||||||
) : activeTab === "templates" ? (
|
|
||||||
<SelectedViewLayout agentName={agent.name} agentId={agent.id}>
|
|
||||||
<EmptyTemplates />
|
|
||||||
</SelectedViewLayout>
|
|
||||||
) : activeTab === "triggers" ? (
|
|
||||||
<SelectedViewLayout agentName={agent.name} agentId={agent.id}>
|
|
||||||
<EmptyTriggers />
|
|
||||||
</SelectedViewLayout>
|
|
||||||
) : (
|
|
||||||
<SelectedViewLayout agentName={agent.name} agentId={agent.id}>
|
|
||||||
<EmptyTasks
|
|
||||||
agent={agent}
|
|
||||||
onRun={onRunInitiated}
|
|
||||||
onTriggerSetup={onTriggerSetup}
|
|
||||||
onScheduleCreated={onScheduleCreated}
|
|
||||||
/>
|
|
||||||
</SelectedViewLayout>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,137 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Text } from "@/components/atoms/Text/Text";
|
||||||
|
import { Dialog } from "@/components/molecules/Dialog/Dialog";
|
||||||
|
import { Skeleton } from "@/components/__legacy__/ui/skeleton";
|
||||||
|
import { useGetV2GetSpecificAgent } from "@/app/api/__generated__/endpoints/store/store";
|
||||||
|
import { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
|
||||||
|
import { okData } from "@/app/api/helpers";
|
||||||
|
import type { StoreAgentDetails } from "@/app/api/__generated__/models/storeAgentDetails";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface AgentVersionChangelogProps {
|
||||||
|
agent: LibraryAgent;
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface VersionInfo {
|
||||||
|
version: number;
|
||||||
|
isCurrentVersion: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AgentVersionChangelog({
|
||||||
|
agent,
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
}: AgentVersionChangelogProps) {
|
||||||
|
// Get marketplace data if agent has marketplace listing
|
||||||
|
const { data: storeAgentData, isLoading } = useGetV2GetSpecificAgent(
|
||||||
|
agent?.marketplace_listing?.creator.slug || "",
|
||||||
|
agent?.marketplace_listing?.slug || "",
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
query: {
|
||||||
|
enabled: !!(
|
||||||
|
agent?.marketplace_listing?.creator.slug &&
|
||||||
|
agent?.marketplace_listing?.slug
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create version info from available graph versions
|
||||||
|
const storeData = okData<StoreAgentDetails>(storeAgentData);
|
||||||
|
const agentVersions: VersionInfo[] = storeData?.agentGraphVersions
|
||||||
|
? storeData.agentGraphVersions
|
||||||
|
.map((versionStr: string) => parseInt(versionStr, 10))
|
||||||
|
.sort((a: number, b: number) => b - a) // Sort descending (newest first)
|
||||||
|
.map((version: number) => ({
|
||||||
|
version,
|
||||||
|
isCurrentVersion: version === agent.graph_version,
|
||||||
|
}))
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const renderVersionItem = (versionInfo: VersionInfo) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={versionInfo.version}
|
||||||
|
className={`rounded-lg border p-4 ${
|
||||||
|
versionInfo.isCurrentVersion
|
||||||
|
? "border-blue-200 bg-blue-50 dark:border-blue-800 dark:bg-blue-950"
|
||||||
|
: "border-neutral-200 bg-white dark:border-neutral-700 dark:bg-neutral-800"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Text variant="body" className="font-semibold">
|
||||||
|
v{versionInfo.version}
|
||||||
|
</Text>
|
||||||
|
{versionInfo.isCurrentVersion && (
|
||||||
|
<span className="rounded-full bg-blue-100 px-2 py-1 text-xs font-medium text-blue-800 dark:bg-blue-900 dark:text-blue-100">
|
||||||
|
Current
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Text
|
||||||
|
variant="small"
|
||||||
|
className="mt-1 text-neutral-600 dark:text-neutral-400"
|
||||||
|
>
|
||||||
|
Available marketplace version
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
title={`Version History - ${agent.name}`}
|
||||||
|
styling={{
|
||||||
|
maxWidth: "45rem",
|
||||||
|
}}
|
||||||
|
controlled={{
|
||||||
|
isOpen: isOpen,
|
||||||
|
set: (isOpen) => {
|
||||||
|
if (!isOpen) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Dialog.Content>
|
||||||
|
<div className="max-h-[70vh] overflow-y-auto">
|
||||||
|
{isLoading ? (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<Skeleton className="h-4 w-full" />
|
||||||
|
<Skeleton className="h-20 w-full" />
|
||||||
|
<Skeleton className="h-20 w-full" />
|
||||||
|
<Skeleton className="h-20 w-full" />
|
||||||
|
</div>
|
||||||
|
) : agentVersions.length > 0 ? (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<Text
|
||||||
|
variant="small"
|
||||||
|
className="text-neutral-600 dark:text-neutral-400"
|
||||||
|
>
|
||||||
|
View changes and updates across different versions of this
|
||||||
|
agent.
|
||||||
|
</Text>
|
||||||
|
{agentVersions.map(renderVersionItem)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="py-8 text-center">
|
||||||
|
<Text
|
||||||
|
variant="body"
|
||||||
|
className="text-neutral-600 dark:text-neutral-400"
|
||||||
|
>
|
||||||
|
No version history available for this agent.
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Dialog.Content>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,16 +1,11 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import type { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
|
import type { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
|
||||||
import type {
|
import { Text } from "@/components/atoms/Text/Text";
|
||||||
BlockIOSubSchema,
|
import type { CredentialsMetaInput } from "@/lib/autogpt-server-api/types";
|
||||||
CredentialsMetaInput,
|
|
||||||
} from "@/lib/autogpt-server-api/types";
|
|
||||||
import { CredentialsInput } from "../CredentialsInputs/CredentialsInputs";
|
import { CredentialsInput } from "../CredentialsInputs/CredentialsInputs";
|
||||||
import {
|
import { RunAgentInputs } from "../RunAgentInputs/RunAgentInputs";
|
||||||
getAgentCredentialsFields,
|
import { getAgentCredentialsFields, getAgentInputFields } from "./helpers";
|
||||||
getAgentInputFields,
|
|
||||||
renderValue,
|
|
||||||
} from "./helpers";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
agent: LibraryAgent;
|
agent: LibraryAgent;
|
||||||
@@ -28,19 +23,23 @@ export function AgentInputsReadOnly({
|
|||||||
getAgentCredentialsFields(agent),
|
getAgentCredentialsFields(agent),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Take actual input entries as leading; augment with schema from input fields.
|
|
||||||
// TODO: ensure consistent ordering.
|
|
||||||
const inputEntries =
|
const inputEntries =
|
||||||
inputs &&
|
inputs &&
|
||||||
Object.entries(inputs).map<[string, [BlockIOSubSchema | undefined, any]]>(
|
Object.entries(inputs).map(([key, value]) => ({
|
||||||
([k, v]) => [k, [inputFields[k], v]],
|
key,
|
||||||
);
|
schema: inputFields[key],
|
||||||
|
value,
|
||||||
|
}));
|
||||||
|
|
||||||
const hasInputs = inputEntries && inputEntries.length > 0;
|
const hasInputs = inputEntries && inputEntries.length > 0;
|
||||||
const hasCredentials = credentialInputs && credentialFieldEntries.length > 0;
|
const hasCredentials = credentialInputs && credentialFieldEntries.length > 0;
|
||||||
|
|
||||||
if (!hasInputs && !hasCredentials) {
|
if (!hasInputs && !hasCredentials) {
|
||||||
return <div className="text-neutral-600">No input for this run.</div>;
|
return (
|
||||||
|
<Text variant="body" className="text-zinc-700">
|
||||||
|
No input for this run.
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -48,16 +47,20 @@ export function AgentInputsReadOnly({
|
|||||||
{/* Regular inputs */}
|
{/* Regular inputs */}
|
||||||
{hasInputs && (
|
{hasInputs && (
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
{inputEntries.map(([key, [schema, value]]) => (
|
{inputEntries.map(({ key, schema, value }) => {
|
||||||
<div key={key} className="flex flex-col gap-1.5">
|
if (!schema) return null;
|
||||||
<label className="text-sm font-medium">
|
|
||||||
{schema?.title || key}
|
return (
|
||||||
</label>
|
<RunAgentInputs
|
||||||
<p className="whitespace-pre-wrap break-words text-sm text-neutral-700">
|
key={key}
|
||||||
{renderValue(value)}
|
schema={schema}
|
||||||
</p>
|
value={value}
|
||||||
</div>
|
placeholder={schema.description}
|
||||||
))}
|
onChange={() => {}}
|
||||||
|
readOnly={true}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -62,12 +62,15 @@ export function CredentialRow({
|
|||||||
</div>
|
</div>
|
||||||
<IconKey className="h-5 w-5 shrink-0 text-zinc-800" />
|
<IconKey className="h-5 w-5 shrink-0 text-zinc-800" />
|
||||||
<div className="flex min-w-0 flex-1 flex-nowrap items-center gap-4">
|
<div className="flex min-w-0 flex-1 flex-nowrap items-center gap-4">
|
||||||
<Text variant="body" className="tracking-tight">
|
<Text
|
||||||
|
variant="body"
|
||||||
|
className="line-clamp-1 flex-[0_0_50%] text-ellipsis tracking-tight"
|
||||||
|
>
|
||||||
{getCredentialDisplayName(credential, displayName)}
|
{getCredentialDisplayName(credential, displayName)}
|
||||||
</Text>
|
</Text>
|
||||||
<Text
|
<Text
|
||||||
variant="large"
|
variant="large"
|
||||||
className="relative top-1 font-mono tracking-tight"
|
className="relative top-1 hidden flex-[0_0_40%] overflow-hidden truncate font-mono tracking-tight md:block"
|
||||||
>
|
>
|
||||||
{"*".repeat(MASKED_KEY_LENGTH)}
|
{"*".repeat(MASKED_KEY_LENGTH)}
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { Button } from "@/components/atoms/Button/Button";
|
|||||||
import { FileInput } from "@/components/atoms/FileInput/FileInput";
|
import { FileInput } from "@/components/atoms/FileInput/FileInput";
|
||||||
import { Switch } from "@/components/atoms/Switch/Switch";
|
import { Switch } from "@/components/atoms/Switch/Switch";
|
||||||
import { GoogleDrivePickerInput } from "@/components/contextual/GoogleDrivePicker/GoogleDrivePickerInput";
|
import { GoogleDrivePickerInput } from "@/components/contextual/GoogleDrivePicker/GoogleDrivePickerInput";
|
||||||
|
import { InformationTooltip } from "@/components/molecules/InformationTooltip/InformationTooltip";
|
||||||
import { TimePicker } from "@/components/molecules/TimePicker/TimePicker";
|
import { TimePicker } from "@/components/molecules/TimePicker/TimePicker";
|
||||||
import {
|
import {
|
||||||
BlockIOObjectSubSchema,
|
BlockIOObjectSubSchema,
|
||||||
@@ -32,6 +33,7 @@ interface Props {
|
|||||||
value?: any;
|
value?: any;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
onChange: (value: any) => void;
|
onChange: (value: any) => void;
|
||||||
|
readOnly?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -44,6 +46,7 @@ export function RunAgentInputs({
|
|||||||
value,
|
value,
|
||||||
placeholder,
|
placeholder,
|
||||||
onChange,
|
onChange,
|
||||||
|
readOnly = false,
|
||||||
...props
|
...props
|
||||||
}: Props & React.HTMLAttributes<HTMLElement>) {
|
}: Props & React.HTMLAttributes<HTMLElement>) {
|
||||||
const { handleUploadFile, uploadProgress } = useRunAgentInputs();
|
const { handleUploadFile, uploadProgress } = useRunAgentInputs();
|
||||||
@@ -62,7 +65,6 @@ export function RunAgentInputs({
|
|||||||
id={`${baseId}-number`}
|
id={`${baseId}-number`}
|
||||||
label={schema.title ?? placeholder ?? "Number"}
|
label={schema.title ?? placeholder ?? "Number"}
|
||||||
hideLabel
|
hideLabel
|
||||||
size="small"
|
|
||||||
type="number"
|
type="number"
|
||||||
value={value ?? ""}
|
value={value ?? ""}
|
||||||
placeholder={placeholder || "Enter number"}
|
placeholder={placeholder || "Enter number"}
|
||||||
@@ -80,7 +82,6 @@ export function RunAgentInputs({
|
|||||||
id={`${baseId}-textarea`}
|
id={`${baseId}-textarea`}
|
||||||
label={schema.title ?? placeholder ?? "Text"}
|
label={schema.title ?? placeholder ?? "Text"}
|
||||||
hideLabel
|
hideLabel
|
||||||
size="small"
|
|
||||||
type="textarea"
|
type="textarea"
|
||||||
rows={3}
|
rows={3}
|
||||||
value={value ?? ""}
|
value={value ?? ""}
|
||||||
@@ -102,7 +103,7 @@ export function RunAgentInputs({
|
|||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
className="w-full"
|
className="w-full"
|
||||||
showRemoveButton={false}
|
showRemoveButton={!readOnly}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
@@ -130,7 +131,6 @@ export function RunAgentInputs({
|
|||||||
id={`${baseId}-date`}
|
id={`${baseId}-date`}
|
||||||
label={schema.title ?? placeholder ?? "Date"}
|
label={schema.title ?? placeholder ?? "Date"}
|
||||||
hideLabel
|
hideLabel
|
||||||
size="small"
|
|
||||||
type="date"
|
type="date"
|
||||||
value={value ? format(value as Date, "yyyy-MM-dd") : ""}
|
value={value ? format(value as Date, "yyyy-MM-dd") : ""}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
@@ -159,7 +159,6 @@ export function RunAgentInputs({
|
|||||||
id={`${baseId}-datetime`}
|
id={`${baseId}-datetime`}
|
||||||
label={schema.title ?? placeholder ?? "Date time"}
|
label={schema.title ?? placeholder ?? "Date time"}
|
||||||
hideLabel
|
hideLabel
|
||||||
size="small"
|
|
||||||
type="datetime-local"
|
type="datetime-local"
|
||||||
value={value ?? ""}
|
value={value ?? ""}
|
||||||
onChange={(e) => onChange((e.target as HTMLInputElement).value)}
|
onChange={(e) => onChange((e.target as HTMLInputElement).value)}
|
||||||
@@ -194,7 +193,6 @@ export function RunAgentInputs({
|
|||||||
label={schema.title ?? placeholder ?? "Select"}
|
label={schema.title ?? placeholder ?? "Select"}
|
||||||
hideLabel
|
hideLabel
|
||||||
value={value ?? ""}
|
value={value ?? ""}
|
||||||
size="small"
|
|
||||||
onValueChange={(val: string) => onChange(val)}
|
onValueChange={(val: string) => onChange(val)}
|
||||||
placeholder={placeholder || "Select an option"}
|
placeholder={placeholder || "Select an option"}
|
||||||
options={schema.enum
|
options={schema.enum
|
||||||
@@ -217,7 +215,6 @@ export function RunAgentInputs({
|
|||||||
items={allKeys.map((key) => ({
|
items={allKeys.map((key) => ({
|
||||||
value: key,
|
value: key,
|
||||||
label: _schema.properties[key]?.title ?? key,
|
label: _schema.properties[key]?.title ?? key,
|
||||||
size: "small",
|
|
||||||
}))}
|
}))}
|
||||||
selectedValues={selectedValues}
|
selectedValues={selectedValues}
|
||||||
onChange={(values: string[]) =>
|
onChange={(values: string[]) =>
|
||||||
@@ -336,7 +333,6 @@ export function RunAgentInputs({
|
|||||||
id={`${baseId}-text`}
|
id={`${baseId}-text`}
|
||||||
label={schema.title ?? placeholder ?? "Text"}
|
label={schema.title ?? placeholder ?? "Text"}
|
||||||
hideLabel
|
hideLabel
|
||||||
size="small"
|
|
||||||
type="text"
|
type="text"
|
||||||
value={value ?? ""}
|
value={value ?? ""}
|
||||||
onChange={(e) => onChange((e.target as HTMLInputElement).value)}
|
onChange={(e) => onChange((e.target as HTMLInputElement).value)}
|
||||||
@@ -347,6 +343,17 @@ export function RunAgentInputs({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="no-drag relative flex w-full">{innerInputElement}</div>
|
<div className="flex w-full flex-col gap-0 space-y-2">
|
||||||
|
<label className="large-medium flex items-center gap-1 font-medium">
|
||||||
|
{schema.title || placeholder}
|
||||||
|
<InformationTooltip description={schema.description} />
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
className="no-drag relative flex w-full"
|
||||||
|
style={readOnly ? { pointerEvents: "none", opacity: 0.7 } : undefined}
|
||||||
|
>
|
||||||
|
{innerInputElement}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,22 +73,15 @@ export function ModalRunSection() {
|
|||||||
title="Task Inputs"
|
title="Task Inputs"
|
||||||
subtitle="Enter the information you want to provide to the agent for this task"
|
subtitle="Enter the information you want to provide to the agent for this task"
|
||||||
>
|
>
|
||||||
{/* Regular inputs */}
|
|
||||||
{inputFields.map(([key, inputSubSchema]) => (
|
{inputFields.map(([key, inputSubSchema]) => (
|
||||||
<div key={key} className="flex w-full flex-col gap-0 space-y-2">
|
<RunAgentInputs
|
||||||
<label className="flex items-center gap-1 text-sm font-medium">
|
key={key}
|
||||||
{inputSubSchema.title || key}
|
schema={inputSubSchema}
|
||||||
<InformationTooltip description={inputSubSchema.description} />
|
value={inputValues[key] ?? inputSubSchema.default}
|
||||||
</label>
|
placeholder={inputSubSchema.description}
|
||||||
|
onChange={(value) => setInputValue(key, value)}
|
||||||
<RunAgentInputs
|
data-testid={`agent-input-${key}`}
|
||||||
schema={inputSubSchema}
|
/>
|
||||||
value={inputValues[key] ?? inputSubSchema.default}
|
|
||||||
placeholder={inputSubSchema.description}
|
|
||||||
onChange={(value) => setInputValue(key, value)}
|
|
||||||
data-testid={`agent-input-${key}`}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
</ModalSection>
|
</ModalSection>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -4,20 +4,19 @@ import { AgentExecutionStatus } from "@/app/api/__generated__/models/agentExecut
|
|||||||
import type { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
|
import type { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
|
||||||
import { LoadingSpinner } from "@/components/atoms/LoadingSpinner/LoadingSpinner";
|
import { LoadingSpinner } from "@/components/atoms/LoadingSpinner/LoadingSpinner";
|
||||||
import { Text } from "@/components/atoms/Text/Text";
|
import { Text } from "@/components/atoms/Text/Text";
|
||||||
import {
|
|
||||||
Tooltip,
|
|
||||||
TooltipContent,
|
|
||||||
TooltipProvider,
|
|
||||||
TooltipTrigger,
|
|
||||||
} from "@/components/atoms/Tooltip/BaseTooltip";
|
|
||||||
import { ErrorCard } from "@/components/molecules/ErrorCard/ErrorCard";
|
import { ErrorCard } from "@/components/molecules/ErrorCard/ErrorCard";
|
||||||
|
import { InformationTooltip } from "@/components/molecules/InformationTooltip/InformationTooltip";
|
||||||
|
import {
|
||||||
|
ScrollableTabs,
|
||||||
|
ScrollableTabsContent,
|
||||||
|
ScrollableTabsList,
|
||||||
|
ScrollableTabsTrigger,
|
||||||
|
} from "@/components/molecules/ScrollableTabs/ScrollableTabs";
|
||||||
import { PendingReviewsList } from "@/components/organisms/PendingReviewsList/PendingReviewsList";
|
import { PendingReviewsList } from "@/components/organisms/PendingReviewsList/PendingReviewsList";
|
||||||
import { usePendingReviewsForExecution } from "@/hooks/usePendingReviews";
|
import { usePendingReviewsForExecution } from "@/hooks/usePendingReviews";
|
||||||
import { isLargeScreen, useBreakpoint } from "@/lib/hooks/useBreakpoint";
|
import { isLargeScreen, useBreakpoint } from "@/lib/hooks/useBreakpoint";
|
||||||
import { InfoIcon } from "@phosphor-icons/react";
|
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { AgentInputsReadOnly } from "../../modals/AgentInputsReadOnly/AgentInputsReadOnly";
|
import { AgentInputsReadOnly } from "../../modals/AgentInputsReadOnly/AgentInputsReadOnly";
|
||||||
import { AnchorLinksWrap } from "../AnchorLinksWrap";
|
|
||||||
import { LoadingSelectedContent } from "../LoadingSelectedContent";
|
import { LoadingSelectedContent } from "../LoadingSelectedContent";
|
||||||
import { RunDetailCard } from "../RunDetailCard/RunDetailCard";
|
import { RunDetailCard } from "../RunDetailCard/RunDetailCard";
|
||||||
import { RunDetailHeader } from "../RunDetailHeader/RunDetailHeader";
|
import { RunDetailHeader } from "../RunDetailHeader/RunDetailHeader";
|
||||||
@@ -28,14 +27,12 @@ import { SelectedRunActions } from "./components/SelectedRunActions/SelectedRunA
|
|||||||
import { WebhookTriggerSection } from "./components/WebhookTriggerSection";
|
import { WebhookTriggerSection } from "./components/WebhookTriggerSection";
|
||||||
import { useSelectedRunView } from "./useSelectedRunView";
|
import { useSelectedRunView } from "./useSelectedRunView";
|
||||||
|
|
||||||
const anchorStyles =
|
|
||||||
"border-b-2 border-transparent pb-1 text-sm font-medium text-slate-600 transition-colors hover:text-slate-900 hover:border-slate-900";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
agent: LibraryAgent;
|
agent: LibraryAgent;
|
||||||
runId: string;
|
runId: string;
|
||||||
onSelectRun?: (id: string) => void;
|
onSelectRun?: (id: string) => void;
|
||||||
onClearSelectedRun?: () => void;
|
onClearSelectedRun?: () => void;
|
||||||
|
banner?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SelectedRunView({
|
export function SelectedRunView({
|
||||||
@@ -43,6 +40,7 @@ export function SelectedRunView({
|
|||||||
runId,
|
runId,
|
||||||
onSelectRun,
|
onSelectRun,
|
||||||
onClearSelectedRun,
|
onClearSelectedRun,
|
||||||
|
banner,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const { run, preset, isLoading, responseError, httpError } =
|
const { run, preset, isLoading, responseError, httpError } =
|
||||||
useSelectedRunView(agent.graph_id, runId);
|
useSelectedRunView(agent.graph_id, runId);
|
||||||
@@ -65,13 +63,6 @@ export function SelectedRunView({
|
|||||||
const withSummary = run?.stats?.activity_status;
|
const withSummary = run?.stats?.activity_status;
|
||||||
const withReviews = run?.status === AgentExecutionStatus.REVIEW;
|
const withReviews = run?.status === AgentExecutionStatus.REVIEW;
|
||||||
|
|
||||||
function scrollToSection(id: string) {
|
|
||||||
const element = document.getElementById(id);
|
|
||||||
if (element) {
|
|
||||||
element.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (responseError || httpError) {
|
if (responseError || httpError) {
|
||||||
return (
|
return (
|
||||||
<ErrorCard
|
<ErrorCard
|
||||||
@@ -89,7 +80,11 @@ export function SelectedRunView({
|
|||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full gap-4">
|
<div className="flex h-full w-full gap-4">
|
||||||
<div className="flex min-h-0 min-w-0 flex-1 flex-col">
|
<div className="flex min-h-0 min-w-0 flex-1 flex-col">
|
||||||
<SelectedViewLayout agentName={agent.name} agentId={agent.id}>
|
<SelectedViewLayout
|
||||||
|
agentName={agent.name}
|
||||||
|
agentId={agent.id}
|
||||||
|
banner={banner}
|
||||||
|
>
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<RunDetailHeader agent={agent} run={run} />
|
<RunDetailHeader agent={agent} run={run} />
|
||||||
|
|
||||||
@@ -112,118 +107,116 @@ export function SelectedRunView({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Navigation Links */}
|
<ScrollableTabs
|
||||||
<AnchorLinksWrap>
|
defaultValue="output"
|
||||||
{withSummary && (
|
className="-mt-2 flex flex-col"
|
||||||
<button
|
>
|
||||||
onClick={() => scrollToSection("summary")}
|
<ScrollableTabsList className="px-4">
|
||||||
className={anchorStyles}
|
{withSummary && (
|
||||||
>
|
<ScrollableTabsTrigger value="summary">
|
||||||
Summary
|
Summary
|
||||||
</button>
|
</ScrollableTabsTrigger>
|
||||||
)}
|
|
||||||
<button
|
|
||||||
onClick={() => scrollToSection("output")}
|
|
||||||
className={anchorStyles}
|
|
||||||
>
|
|
||||||
Output
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => scrollToSection("input")}
|
|
||||||
className={anchorStyles}
|
|
||||||
>
|
|
||||||
Your input
|
|
||||||
</button>
|
|
||||||
{withReviews && (
|
|
||||||
<button
|
|
||||||
onClick={() => scrollToSection("reviews")}
|
|
||||||
className={anchorStyles}
|
|
||||||
>
|
|
||||||
Reviews ({pendingReviews.length})
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</AnchorLinksWrap>
|
|
||||||
|
|
||||||
{/* Summary Section */}
|
|
||||||
{withSummary && (
|
|
||||||
<div id="summary" className="scroll-mt-4">
|
|
||||||
<RunDetailCard
|
|
||||||
title={
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Text variant="lead-semibold">Summary</Text>
|
|
||||||
<TooltipProvider>
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<InfoIcon
|
|
||||||
size={16}
|
|
||||||
className="cursor-help text-neutral-500 hover:text-neutral-700"
|
|
||||||
/>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>
|
|
||||||
<p className="max-w-xs">
|
|
||||||
This AI-generated summary describes how the agent
|
|
||||||
handled your task. It's an experimental
|
|
||||||
feature and may occasionally be inaccurate.
|
|
||||||
</p>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</TooltipProvider>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<RunSummary run={run} />
|
|
||||||
</RunDetailCard>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Output Section */}
|
|
||||||
<div id="output" className="scroll-mt-4">
|
|
||||||
<RunDetailCard title="Output">
|
|
||||||
{isLoading ? (
|
|
||||||
<div className="text-neutral-500">
|
|
||||||
<LoadingSpinner />
|
|
||||||
</div>
|
|
||||||
) : run && "outputs" in run ? (
|
|
||||||
<RunOutputs outputs={run.outputs as any} />
|
|
||||||
) : (
|
|
||||||
<Text variant="body" className="text-neutral-600">
|
|
||||||
No output from this run.
|
|
||||||
</Text>
|
|
||||||
)}
|
)}
|
||||||
</RunDetailCard>
|
<ScrollableTabsTrigger value="output">
|
||||||
</div>
|
Output
|
||||||
|
</ScrollableTabsTrigger>
|
||||||
{/* Input Section */}
|
<ScrollableTabsTrigger value="input">
|
||||||
<div id="input" className="scroll-mt-4">
|
Your input
|
||||||
<RunDetailCard title="Your input">
|
</ScrollableTabsTrigger>
|
||||||
<AgentInputsReadOnly
|
{withReviews && (
|
||||||
agent={agent}
|
<ScrollableTabsTrigger value="reviews">
|
||||||
inputs={run?.inputs}
|
Reviews ({pendingReviews.length})
|
||||||
credentialInputs={run?.credential_inputs}
|
</ScrollableTabsTrigger>
|
||||||
/>
|
)}
|
||||||
</RunDetailCard>
|
</ScrollableTabsList>
|
||||||
</div>
|
<div className="my-6 flex flex-col gap-6">
|
||||||
|
{/* Summary Section */}
|
||||||
{/* Reviews Section */}
|
{withSummary && (
|
||||||
{withReviews && (
|
<ScrollableTabsContent value="summary">
|
||||||
<div id="reviews" className="scroll-mt-4">
|
<div className="scroll-mt-4">
|
||||||
<RunDetailCard>
|
<RunDetailCard
|
||||||
{reviewsLoading ? (
|
title={
|
||||||
<div className="text-neutral-500">Loading reviews…</div>
|
<div className="flex items-center gap-1">
|
||||||
) : pendingReviews.length > 0 ? (
|
<Text variant="lead-semibold">Summary</Text>
|
||||||
<PendingReviewsList
|
<InformationTooltip
|
||||||
reviews={pendingReviews}
|
iconSize={20}
|
||||||
onReviewComplete={refetchReviews}
|
description="This AI-generated summary describes how the agent handled your task. It's an experimental feature and may occasionally be inaccurate."
|
||||||
emptyMessage="No pending reviews for this execution"
|
/>
|
||||||
/>
|
</div>
|
||||||
) : (
|
}
|
||||||
<div className="text-neutral-600">
|
>
|
||||||
No pending reviews for this execution
|
<RunSummary run={run} />
|
||||||
|
</RunDetailCard>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</ScrollableTabsContent>
|
||||||
</RunDetailCard>
|
)}
|
||||||
|
|
||||||
|
{/* Output Section */}
|
||||||
|
<ScrollableTabsContent value="output">
|
||||||
|
<div className="scroll-mt-4">
|
||||||
|
<RunDetailCard title="Output">
|
||||||
|
{isLoading ? (
|
||||||
|
<div className="text-neutral-500">
|
||||||
|
<LoadingSpinner />
|
||||||
|
</div>
|
||||||
|
) : run && "outputs" in run ? (
|
||||||
|
<RunOutputs outputs={run.outputs as any} />
|
||||||
|
) : (
|
||||||
|
<Text variant="body" className="text-neutral-600">
|
||||||
|
No output from this run.
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</RunDetailCard>
|
||||||
|
</div>
|
||||||
|
</ScrollableTabsContent>
|
||||||
|
|
||||||
|
{/* Input Section */}
|
||||||
|
<ScrollableTabsContent value="input">
|
||||||
|
<div id="input" className="scroll-mt-4">
|
||||||
|
<RunDetailCard
|
||||||
|
title={
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Text variant="lead-semibold">Your input</Text>
|
||||||
|
<InformationTooltip
|
||||||
|
iconSize={20}
|
||||||
|
description="This is the input that was provided to the agent for running this task."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<AgentInputsReadOnly
|
||||||
|
agent={agent}
|
||||||
|
inputs={run?.inputs}
|
||||||
|
credentialInputs={run?.credential_inputs}
|
||||||
|
/>
|
||||||
|
</RunDetailCard>
|
||||||
|
</div>
|
||||||
|
</ScrollableTabsContent>
|
||||||
|
|
||||||
|
{/* Reviews Section */}
|
||||||
|
{withReviews && (
|
||||||
|
<ScrollableTabsContent value="reviews">
|
||||||
|
<div className="scroll-mt-4">
|
||||||
|
<RunDetailCard>
|
||||||
|
{reviewsLoading ? (
|
||||||
|
<LoadingSpinner size="small" />
|
||||||
|
) : pendingReviews.length > 0 ? (
|
||||||
|
<PendingReviewsList
|
||||||
|
reviews={pendingReviews}
|
||||||
|
onReviewComplete={refetchReviews}
|
||||||
|
emptyMessage="No pending reviews for this execution"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Text variant="body" className="text-zinc-700">
|
||||||
|
No pending reviews for this execution
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</RunDetailCard>
|
||||||
|
</div>
|
||||||
|
</ScrollableTabsContent>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
</ScrollableTabs>
|
||||||
</div>
|
</div>
|
||||||
</SelectedViewLayout>
|
</SelectedViewLayout>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import { humanizeCronExpression } from "@/lib/cron-expression-utils";
|
|||||||
import { isLargeScreen, useBreakpoint } from "@/lib/hooks/useBreakpoint";
|
import { isLargeScreen, useBreakpoint } from "@/lib/hooks/useBreakpoint";
|
||||||
import { formatInTimezone, getTimezoneDisplayName } from "@/lib/timezone-utils";
|
import { formatInTimezone, getTimezoneDisplayName } from "@/lib/timezone-utils";
|
||||||
import { AgentInputsReadOnly } from "../../modals/AgentInputsReadOnly/AgentInputsReadOnly";
|
import { AgentInputsReadOnly } from "../../modals/AgentInputsReadOnly/AgentInputsReadOnly";
|
||||||
import { AnchorLinksWrap } from "../AnchorLinksWrap";
|
|
||||||
import { LoadingSelectedContent } from "../LoadingSelectedContent";
|
import { LoadingSelectedContent } from "../LoadingSelectedContent";
|
||||||
import { RunDetailCard } from "../RunDetailCard/RunDetailCard";
|
import { RunDetailCard } from "../RunDetailCard/RunDetailCard";
|
||||||
import { RunDetailHeader } from "../RunDetailHeader/RunDetailHeader";
|
import { RunDetailHeader } from "../RunDetailHeader/RunDetailHeader";
|
||||||
@@ -17,19 +16,18 @@ import { SelectedViewLayout } from "../SelectedViewLayout";
|
|||||||
import { SelectedScheduleActions } from "./components/SelectedScheduleActions";
|
import { SelectedScheduleActions } from "./components/SelectedScheduleActions";
|
||||||
import { useSelectedScheduleView } from "./useSelectedScheduleView";
|
import { useSelectedScheduleView } from "./useSelectedScheduleView";
|
||||||
|
|
||||||
const anchorStyles =
|
|
||||||
"border-b-2 border-transparent pb-1 text-sm font-medium text-slate-600 transition-colors hover:text-slate-900 hover:border-slate-900";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
agent: LibraryAgent;
|
agent: LibraryAgent;
|
||||||
scheduleId: string;
|
scheduleId: string;
|
||||||
onClearSelectedRun?: () => void;
|
onClearSelectedRun?: () => void;
|
||||||
|
banner?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SelectedScheduleView({
|
export function SelectedScheduleView({
|
||||||
agent,
|
agent,
|
||||||
scheduleId,
|
scheduleId,
|
||||||
onClearSelectedRun,
|
onClearSelectedRun,
|
||||||
|
banner,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const { schedule, isLoading, error } = useSelectedScheduleView(
|
const { schedule, isLoading, error } = useSelectedScheduleView(
|
||||||
agent.graph_id,
|
agent.graph_id,
|
||||||
@@ -45,13 +43,6 @@ export function SelectedScheduleView({
|
|||||||
const breakpoint = useBreakpoint();
|
const breakpoint = useBreakpoint();
|
||||||
const isLgScreenUp = isLargeScreen(breakpoint);
|
const isLgScreenUp = isLargeScreen(breakpoint);
|
||||||
|
|
||||||
function scrollToSection(id: string) {
|
|
||||||
const element = document.getElementById(id);
|
|
||||||
if (element) {
|
|
||||||
element.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<ErrorCard
|
<ErrorCard
|
||||||
@@ -85,7 +76,11 @@ export function SelectedScheduleView({
|
|||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full gap-4">
|
<div className="flex h-full w-full gap-4">
|
||||||
<div className="flex min-h-0 min-w-0 flex-1 flex-col">
|
<div className="flex min-h-0 min-w-0 flex-1 flex-col">
|
||||||
<SelectedViewLayout agentName={agent.name} agentId={agent.id}>
|
<SelectedViewLayout
|
||||||
|
agentName={agent.name}
|
||||||
|
agentId={agent.id}
|
||||||
|
banner={banner}
|
||||||
|
>
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="flex w-full flex-col gap-0">
|
<div className="flex w-full flex-col gap-0">
|
||||||
<RunDetailHeader
|
<RunDetailHeader
|
||||||
@@ -108,22 +103,6 @@ export function SelectedScheduleView({
|
|||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Navigation Links */}
|
|
||||||
<AnchorLinksWrap>
|
|
||||||
<button
|
|
||||||
onClick={() => scrollToSection("schedule")}
|
|
||||||
className={anchorStyles}
|
|
||||||
>
|
|
||||||
Schedule
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => scrollToSection("input")}
|
|
||||||
className={anchorStyles}
|
|
||||||
>
|
|
||||||
Your input
|
|
||||||
</button>
|
|
||||||
</AnchorLinksWrap>
|
|
||||||
|
|
||||||
{/* Schedule Section */}
|
{/* Schedule Section */}
|
||||||
<div id="schedule" className="scroll-mt-4">
|
<div id="schedule" className="scroll-mt-4">
|
||||||
<RunDetailCard title="Schedule">
|
<RunDetailCard title="Schedule">
|
||||||
|
|||||||
@@ -1,84 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import type { GraphExecutionJobInfo } from "@/app/api/__generated__/models/graphExecutionJobInfo";
|
|
||||||
import type { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
|
|
||||||
import { Button } from "@/components/atoms/Button/Button";
|
|
||||||
import { Text } from "@/components/atoms/Text/Text";
|
|
||||||
import { Dialog } from "@/components/molecules/Dialog/Dialog";
|
|
||||||
import { PencilSimpleIcon } from "@phosphor-icons/react";
|
|
||||||
import { RunAgentInputs } from "../../../../modals/RunAgentInputs/RunAgentInputs";
|
|
||||||
import { useEditInputsModal } from "./useEditInputsModal";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
agent: LibraryAgent;
|
|
||||||
schedule: GraphExecutionJobInfo;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function EditInputsModal({ agent, schedule }: Props) {
|
|
||||||
const {
|
|
||||||
isOpen,
|
|
||||||
setIsOpen,
|
|
||||||
inputFields,
|
|
||||||
values,
|
|
||||||
setValues,
|
|
||||||
handleSave,
|
|
||||||
isSaving,
|
|
||||||
} = useEditInputsModal(agent, schedule);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog
|
|
||||||
controlled={{ isOpen, set: setIsOpen }}
|
|
||||||
styling={{ maxWidth: "32rem" }}
|
|
||||||
>
|
|
||||||
<Dialog.Trigger>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="small"
|
|
||||||
className="absolute -right-2 -top-2"
|
|
||||||
>
|
|
||||||
<PencilSimpleIcon className="size-4" /> Edit inputs
|
|
||||||
</Button>
|
|
||||||
</Dialog.Trigger>
|
|
||||||
<Dialog.Content>
|
|
||||||
<div className="flex flex-col gap-4">
|
|
||||||
<Text variant="h3">Edit inputs</Text>
|
|
||||||
<div className="flex flex-col gap-4">
|
|
||||||
{Object.entries(inputFields).map(([key, fieldSchema]) => (
|
|
||||||
<div key={key} className="flex flex-col gap-1.5">
|
|
||||||
<label className="text-sm font-medium">
|
|
||||||
{fieldSchema?.title || key}
|
|
||||||
</label>
|
|
||||||
<RunAgentInputs
|
|
||||||
schema={fieldSchema as any}
|
|
||||||
value={values[key]}
|
|
||||||
onChange={(v) => setValues((prev) => ({ ...prev, [key]: v }))}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Dialog.Footer>
|
|
||||||
<div className="flex w-full justify-end gap-2">
|
|
||||||
<Button
|
|
||||||
variant="secondary"
|
|
||||||
size="small"
|
|
||||||
onClick={() => setIsOpen(false)}
|
|
||||||
className="min-w-32"
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="primary"
|
|
||||||
size="small"
|
|
||||||
onClick={handleSave}
|
|
||||||
loading={isSaving}
|
|
||||||
className="min-w-32"
|
|
||||||
>
|
|
||||||
{isSaving ? "Saving…" : "Save"}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Dialog.Footer>
|
|
||||||
</Dialog.Content>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useMemo, useState } from "react";
|
|
||||||
import { useQueryClient } from "@tanstack/react-query";
|
|
||||||
import { getGetV1ListExecutionSchedulesForAGraphQueryKey } from "@/app/api/__generated__/endpoints/schedules/schedules";
|
|
||||||
import type { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
|
|
||||||
import type { GraphExecutionJobInfo } from "@/app/api/__generated__/models/graphExecutionJobInfo";
|
|
||||||
import { useToast } from "@/components/molecules/Toast/use-toast";
|
|
||||||
|
|
||||||
function getAgentInputFields(agent: LibraryAgent): Record<string, any> {
|
|
||||||
const schema = agent.input_schema as unknown as {
|
|
||||||
properties?: Record<string, any>;
|
|
||||||
} | null;
|
|
||||||
if (!schema || !schema.properties) return {};
|
|
||||||
const properties = schema.properties as Record<string, any>;
|
|
||||||
const visibleEntries = Object.entries(properties).filter(
|
|
||||||
([, sub]) => !sub?.hidden,
|
|
||||||
);
|
|
||||||
return Object.fromEntries(visibleEntries);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useEditInputsModal(
|
|
||||||
agent: LibraryAgent,
|
|
||||||
schedule: GraphExecutionJobInfo,
|
|
||||||
) {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
const { toast } = useToast();
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
|
||||||
const inputFields = useMemo(() => getAgentInputFields(agent), [agent]);
|
|
||||||
const [values, setValues] = useState<Record<string, any>>({
|
|
||||||
...(schedule.input_data as Record<string, any>),
|
|
||||||
});
|
|
||||||
|
|
||||||
async function handleSave() {
|
|
||||||
setIsSaving(true);
|
|
||||||
try {
|
|
||||||
const res = await fetch(`/api/schedules/${schedule.id}`, {
|
|
||||||
method: "PATCH",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify({ inputs: values }),
|
|
||||||
});
|
|
||||||
if (!res.ok) {
|
|
||||||
let message = "Failed to update schedule inputs";
|
|
||||||
const data = await res.json();
|
|
||||||
message = data?.message || data?.detail || message;
|
|
||||||
throw new Error(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
await queryClient.invalidateQueries({
|
|
||||||
queryKey: getGetV1ListExecutionSchedulesForAGraphQueryKey(
|
|
||||||
schedule.graph_id,
|
|
||||||
),
|
|
||||||
});
|
|
||||||
toast({
|
|
||||||
title: "Schedule inputs updated",
|
|
||||||
});
|
|
||||||
setIsOpen(false);
|
|
||||||
} catch (error: any) {
|
|
||||||
toast({
|
|
||||||
title: "Failed to update schedule inputs",
|
|
||||||
description: error?.message || "An unexpected error occurred.",
|
|
||||||
variant: "destructive",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
setIsSaving(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
isOpen,
|
|
||||||
setIsOpen,
|
|
||||||
inputFields,
|
|
||||||
values,
|
|
||||||
setValues,
|
|
||||||
handleSave,
|
|
||||||
isSaving,
|
|
||||||
} as const;
|
|
||||||
}
|
|
||||||
@@ -25,9 +25,10 @@ export function SelectedScheduleActions({ agent, scheduleId }: Props) {
|
|||||||
<Button
|
<Button
|
||||||
variant="icon"
|
variant="icon"
|
||||||
size="icon"
|
size="icon"
|
||||||
aria-label="Open in builder"
|
|
||||||
as="NextLink"
|
as="NextLink"
|
||||||
href={openInBuilderHref}
|
href={openInBuilderHref}
|
||||||
|
target="_blank"
|
||||||
|
aria-label="View scheduled task details"
|
||||||
>
|
>
|
||||||
<EyeIcon weight="bold" size={18} className="text-zinc-700" />
|
<EyeIcon weight="bold" size={18} className="text-zinc-700" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import type { GraphExecutionMeta } from "@/app/api/__generated__/models/graphExe
|
|||||||
import type { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
|
import type { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
|
||||||
import { Input } from "@/components/atoms/Input/Input";
|
import { Input } from "@/components/atoms/Input/Input";
|
||||||
import { ErrorCard } from "@/components/molecules/ErrorCard/ErrorCard";
|
import { ErrorCard } from "@/components/molecules/ErrorCard/ErrorCard";
|
||||||
import { InformationTooltip } from "@/components/molecules/InformationTooltip/InformationTooltip";
|
|
||||||
import {
|
import {
|
||||||
getAgentCredentialsFields,
|
getAgentCredentialsFields,
|
||||||
getAgentInputFields,
|
getAgentInputFields,
|
||||||
@@ -25,6 +24,7 @@ interface Props {
|
|||||||
onClearSelectedRun?: () => void;
|
onClearSelectedRun?: () => void;
|
||||||
onRunCreated?: (execution: GraphExecutionMeta) => void;
|
onRunCreated?: (execution: GraphExecutionMeta) => void;
|
||||||
onSwitchToRunsTab?: () => void;
|
onSwitchToRunsTab?: () => void;
|
||||||
|
banner?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SelectedTemplateView({
|
export function SelectedTemplateView({
|
||||||
@@ -33,6 +33,7 @@ export function SelectedTemplateView({
|
|||||||
onClearSelectedRun,
|
onClearSelectedRun,
|
||||||
onRunCreated,
|
onRunCreated,
|
||||||
onSwitchToRunsTab,
|
onSwitchToRunsTab,
|
||||||
|
banner,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const {
|
const {
|
||||||
template,
|
template,
|
||||||
@@ -101,7 +102,11 @@ export function SelectedTemplateView({
|
|||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full gap-4">
|
<div className="flex h-full w-full gap-4">
|
||||||
<div className="flex min-h-0 min-w-0 flex-1 flex-col">
|
<div className="flex min-h-0 min-w-0 flex-1 flex-col">
|
||||||
<SelectedViewLayout agentName={agent.name} agentId={agent.id}>
|
<SelectedViewLayout
|
||||||
|
agentName={agent.name}
|
||||||
|
agentId={agent.id}
|
||||||
|
banner={banner}
|
||||||
|
>
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<RunDetailHeader agent={agent} run={undefined} />
|
<RunDetailHeader agent={agent} run={undefined} />
|
||||||
|
|
||||||
@@ -138,25 +143,13 @@ export function SelectedTemplateView({
|
|||||||
<RunDetailCard title="Your Input">
|
<RunDetailCard title="Your Input">
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
{inputFields.map(([key, inputSubSchema]) => (
|
{inputFields.map(([key, inputSubSchema]) => (
|
||||||
<div
|
<RunAgentInputs
|
||||||
key={key}
|
key={key}
|
||||||
className="flex w-full flex-col gap-0 space-y-2"
|
schema={inputSubSchema}
|
||||||
>
|
value={inputs[key] ?? inputSubSchema.default}
|
||||||
<label className="flex items-center gap-1 text-sm font-medium">
|
placeholder={inputSubSchema.description}
|
||||||
{inputSubSchema.title || key}
|
onChange={(value) => setInputValue(key, value)}
|
||||||
{inputSubSchema.description && (
|
/>
|
||||||
<InformationTooltip
|
|
||||||
description={inputSubSchema.description}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</label>
|
|
||||||
<RunAgentInputs
|
|
||||||
schema={inputSubSchema}
|
|
||||||
value={inputs[key] ?? inputSubSchema.default}
|
|
||||||
placeholder={inputSubSchema.description}
|
|
||||||
onChange={(value) => setInputValue(key, value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</RunDetailCard>
|
</RunDetailCard>
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
import type { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
|
import type { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
|
||||||
import { Input } from "@/components/atoms/Input/Input";
|
import { Input } from "@/components/atoms/Input/Input";
|
||||||
import { ErrorCard } from "@/components/molecules/ErrorCard/ErrorCard";
|
import { ErrorCard } from "@/components/molecules/ErrorCard/ErrorCard";
|
||||||
import { InformationTooltip } from "@/components/molecules/InformationTooltip/InformationTooltip";
|
|
||||||
import {
|
import {
|
||||||
getAgentCredentialsFields,
|
getAgentCredentialsFields,
|
||||||
getAgentInputFields,
|
getAgentInputFields,
|
||||||
@@ -23,6 +22,7 @@ interface Props {
|
|||||||
triggerId: string;
|
triggerId: string;
|
||||||
onClearSelectedRun?: () => void;
|
onClearSelectedRun?: () => void;
|
||||||
onSwitchToRunsTab?: () => void;
|
onSwitchToRunsTab?: () => void;
|
||||||
|
banner?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SelectedTriggerView({
|
export function SelectedTriggerView({
|
||||||
@@ -30,6 +30,7 @@ export function SelectedTriggerView({
|
|||||||
triggerId,
|
triggerId,
|
||||||
onClearSelectedRun,
|
onClearSelectedRun,
|
||||||
onSwitchToRunsTab,
|
onSwitchToRunsTab,
|
||||||
|
banner,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const {
|
const {
|
||||||
trigger,
|
trigger,
|
||||||
@@ -94,7 +95,11 @@ export function SelectedTriggerView({
|
|||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full gap-4">
|
<div className="flex h-full w-full gap-4">
|
||||||
<div className="flex min-h-0 min-w-0 flex-1 flex-col">
|
<div className="flex min-h-0 min-w-0 flex-1 flex-col">
|
||||||
<SelectedViewLayout agentName={agent.name} agentId={agent.id}>
|
<SelectedViewLayout
|
||||||
|
agentName={agent.name}
|
||||||
|
agentId={agent.id}
|
||||||
|
banner={banner}
|
||||||
|
>
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<RunDetailHeader agent={agent} run={undefined} />
|
<RunDetailHeader agent={agent} run={undefined} />
|
||||||
|
|
||||||
@@ -131,25 +136,13 @@ export function SelectedTriggerView({
|
|||||||
<RunDetailCard title="Your Input">
|
<RunDetailCard title="Your Input">
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
{inputFields.map(([key, inputSubSchema]) => (
|
{inputFields.map(([key, inputSubSchema]) => (
|
||||||
<div
|
<RunAgentInputs
|
||||||
key={key}
|
key={key}
|
||||||
className="flex w-full flex-col gap-0 space-y-2"
|
schema={inputSubSchema}
|
||||||
>
|
value={inputs[key] ?? inputSubSchema.default}
|
||||||
<label className="flex items-center gap-1 text-sm font-medium">
|
placeholder={inputSubSchema.description}
|
||||||
{inputSubSchema.title || key}
|
onChange={(value) => setInputValue(key, value)}
|
||||||
{inputSubSchema.description && (
|
/>
|
||||||
<InformationTooltip
|
|
||||||
description={inputSubSchema.description}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</label>
|
|
||||||
<RunAgentInputs
|
|
||||||
schema={inputSubSchema}
|
|
||||||
value={inputs[key] ?? inputSubSchema.default}
|
|
||||||
placeholder={inputSubSchema.description}
|
|
||||||
onChange={(value) => setInputValue(key, value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</RunDetailCard>
|
</RunDetailCard>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ interface Props {
|
|||||||
agentName: string;
|
agentName: string;
|
||||||
agentId: string;
|
agentId: string;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
banner?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SelectedViewLayout(props: Props) {
|
export function SelectedViewLayout(props: Props) {
|
||||||
@@ -14,10 +15,15 @@ export function SelectedViewLayout(props: Props) {
|
|||||||
<div
|
<div
|
||||||
className={`${AGENT_LIBRARY_SECTION_PADDING_X} flex-shrink-0 border-b border-zinc-100 pb-0 lg:pb-4`}
|
className={`${AGENT_LIBRARY_SECTION_PADDING_X} flex-shrink-0 border-b border-zinc-100 pb-0 lg:pb-4`}
|
||||||
>
|
>
|
||||||
|
{props.banner && <div className="mb-4">{props.banner}</div>}
|
||||||
<Breadcrumbs
|
<Breadcrumbs
|
||||||
items={[
|
items={[
|
||||||
{ name: "My Library", link: "/library" },
|
{ name: "My Library", link: "/library" },
|
||||||
{ name: props.agentName, link: `/library/agents/${props.agentId}` },
|
{
|
||||||
|
name: props.agentName,
|
||||||
|
link: `/library/agents/${props.agentId}`,
|
||||||
|
testId: "agent-title"
|
||||||
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,163 @@
|
|||||||
|
import {
|
||||||
|
useGetV2GetSpecificAgent,
|
||||||
|
useGetV2ListMySubmissions,
|
||||||
|
} from "@/app/api/__generated__/endpoints/store/store";
|
||||||
|
import {
|
||||||
|
usePatchV2UpdateLibraryAgent,
|
||||||
|
getGetV2GetLibraryAgentQueryKey,
|
||||||
|
} from "@/app/api/__generated__/endpoints/library/library";
|
||||||
|
import { useToast } from "@/components/molecules/Toast/use-toast";
|
||||||
|
import type { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
|
||||||
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
|
import { useSupabaseStore } from "@/lib/supabase/hooks/useSupabaseStore";
|
||||||
|
import { okData } from "@/app/api/helpers";
|
||||||
|
import type { StoreSubmission } from "@/app/api/__generated__/models/storeSubmission";
|
||||||
|
import * as React from "react";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
interface UseMarketplaceUpdateProps {
|
||||||
|
agent: LibraryAgent | null | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useMarketplaceUpdate({ agent }: UseMarketplaceUpdateProps) {
|
||||||
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
|
const { toast } = useToast();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const user = useSupabaseStore((state) => state.user);
|
||||||
|
|
||||||
|
// Get marketplace data if agent has marketplace listing
|
||||||
|
const { data: storeAgentData } = useGetV2GetSpecificAgent(
|
||||||
|
agent?.marketplace_listing?.creator.slug || "",
|
||||||
|
agent?.marketplace_listing?.slug || "",
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
query: {
|
||||||
|
enabled: !!(
|
||||||
|
agent?.marketplace_listing?.creator.slug &&
|
||||||
|
agent?.marketplace_listing?.slug
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get user's submissions to check for pending submissions
|
||||||
|
const { data: submissionsData } = useGetV2ListMySubmissions(
|
||||||
|
{ page: 1, page_size: 50 }, // Get enough to cover recent submissions
|
||||||
|
{
|
||||||
|
query: {
|
||||||
|
enabled: !!user?.id, // Only fetch if user is authenticated
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const updateToLatestMutation = usePatchV2UpdateLibraryAgent({
|
||||||
|
mutation: {
|
||||||
|
onError: (err) => {
|
||||||
|
toast({
|
||||||
|
title: "Update Failed",
|
||||||
|
description: "Failed to update agent to latest version",
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
console.error("Failed to update agent:", err);
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
toast({
|
||||||
|
title: "Agent Updated",
|
||||||
|
description: "Agent updated to latest version successfully",
|
||||||
|
});
|
||||||
|
// Invalidate to get the updated agent data from the server
|
||||||
|
if (agent?.id) {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: getGetV2GetLibraryAgentQueryKey(agent.id),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if marketplace has a newer version than user's current version
|
||||||
|
const marketplaceUpdateInfo = React.useMemo(() => {
|
||||||
|
const storeAgent = okData(storeAgentData) as any;
|
||||||
|
if (!agent || !storeAgent) {
|
||||||
|
return {
|
||||||
|
hasUpdate: false,
|
||||||
|
latestVersion: undefined,
|
||||||
|
isUserCreator: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the latest version from the marketplace
|
||||||
|
// agentGraphVersions array contains graph version numbers as strings, get the highest one
|
||||||
|
const latestMarketplaceVersion =
|
||||||
|
storeAgent.agentGraphVersions?.length > 0
|
||||||
|
? Math.max(
|
||||||
|
...storeAgent.agentGraphVersions.map((v: string) =>
|
||||||
|
parseInt(v, 10),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
// Determine if the user is the creator of this agent
|
||||||
|
// Compare current user ID with the marketplace listing creator ID
|
||||||
|
const isUserCreator =
|
||||||
|
user?.id && agent.marketplace_listing?.creator.id === user.id;
|
||||||
|
|
||||||
|
// Check if there's a pending submission for this specific agent version
|
||||||
|
const submissionsResponse = okData(submissionsData) as any;
|
||||||
|
const hasPendingSubmissionForCurrentVersion =
|
||||||
|
isUserCreator &&
|
||||||
|
submissionsResponse?.submissions?.some(
|
||||||
|
(submission: StoreSubmission) =>
|
||||||
|
submission.agent_id === agent.graph_id &&
|
||||||
|
submission.agent_version === agent.graph_version &&
|
||||||
|
submission.status === "PENDING",
|
||||||
|
);
|
||||||
|
|
||||||
|
// If user is creator and their version is newer than marketplace, show publish update banner
|
||||||
|
// BUT only if there's no pending submission for this version
|
||||||
|
const hasPublishUpdate =
|
||||||
|
isUserCreator &&
|
||||||
|
!hasPendingSubmissionForCurrentVersion &&
|
||||||
|
latestMarketplaceVersion !== undefined &&
|
||||||
|
agent.graph_version > latestMarketplaceVersion;
|
||||||
|
|
||||||
|
// If marketplace version is newer than user's version, show update banner
|
||||||
|
// This applies to both creators and non-creators
|
||||||
|
const hasMarketplaceUpdate =
|
||||||
|
latestMarketplaceVersion !== undefined &&
|
||||||
|
latestMarketplaceVersion > agent.graph_version;
|
||||||
|
|
||||||
|
return {
|
||||||
|
hasUpdate: hasMarketplaceUpdate,
|
||||||
|
latestVersion: latestMarketplaceVersion,
|
||||||
|
isUserCreator,
|
||||||
|
hasPublishUpdate,
|
||||||
|
};
|
||||||
|
}, [agent, storeAgentData, user, submissionsData]);
|
||||||
|
|
||||||
|
const handlePublishUpdate = () => {
|
||||||
|
setModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUpdateToLatest = () => {
|
||||||
|
if (!agent || marketplaceUpdateInfo.latestVersion === undefined) return;
|
||||||
|
// Update to the specific marketplace version using the new graph_version parameter
|
||||||
|
updateToLatestMutation.mutate({
|
||||||
|
libraryAgentId: agent.id,
|
||||||
|
data: {
|
||||||
|
graph_version: marketplaceUpdateInfo.latestVersion,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
hasAgentMarketplaceUpdate: marketplaceUpdateInfo.hasPublishUpdate,
|
||||||
|
hasMarketplaceUpdate: marketplaceUpdateInfo.hasUpdate,
|
||||||
|
latestMarketplaceVersion: marketplaceUpdateInfo.latestVersion,
|
||||||
|
isUpdating: updateToLatestMutation.isPending,
|
||||||
|
modalOpen,
|
||||||
|
setModalOpen,
|
||||||
|
handlePublishUpdate,
|
||||||
|
handleUpdateToLatest,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -680,28 +680,20 @@ export function AgentRunDraftView({
|
|||||||
|
|
||||||
{/* Regular inputs */}
|
{/* Regular inputs */}
|
||||||
{Object.entries(agentInputFields).map(([key, inputSubSchema]) => (
|
{Object.entries(agentInputFields).map(([key, inputSubSchema]) => (
|
||||||
<div key={key} className="flex flex-col space-y-2">
|
<RunAgentInputs
|
||||||
<label className="flex items-center gap-1 text-sm font-medium">
|
key={key}
|
||||||
{inputSubSchema.title || key}
|
schema={inputSubSchema}
|
||||||
<InformationTooltip
|
value={inputValues[key] ?? inputSubSchema.default}
|
||||||
description={inputSubSchema.description}
|
placeholder={inputSubSchema.description}
|
||||||
/>
|
onChange={(value) => {
|
||||||
</label>
|
setInputValues((obj) => ({
|
||||||
|
...obj,
|
||||||
<RunAgentInputs
|
[key]: value,
|
||||||
schema={inputSubSchema}
|
}));
|
||||||
value={inputValues[key] ?? inputSubSchema.default}
|
setChangedPresetAttributes((prev) => prev.add("inputs"));
|
||||||
placeholder={inputSubSchema.description}
|
}}
|
||||||
onChange={(value) => {
|
data-testid={`agent-input-${key}`}
|
||||||
setInputValues((obj) => ({
|
/>
|
||||||
...obj,
|
|
||||||
[key]: value,
|
|
||||||
}));
|
|
||||||
setChangedPresetAttributes((prev) => prev.add("inputs"));
|
|
||||||
}}
|
|
||||||
data-testid={`agent-input-${key}`}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -5,7 +5,20 @@ import { Separator } from "@/components/__legacy__/ui/separator";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { User } from "@supabase/supabase-js";
|
import { User } from "@supabase/supabase-js";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
import { okData } from "@/app/api/helpers";
|
||||||
|
import type { StoreAgentDetails } from "@/app/api/__generated__/models/storeAgentDetails";
|
||||||
|
import type { ChangelogEntry } from "@/app/api/__generated__/models/changelogEntry";
|
||||||
import { useAgentInfo } from "./useAgentInfo";
|
import { useAgentInfo } from "./useAgentInfo";
|
||||||
|
import { useGetV2GetSpecificAgent } from "@/app/api/__generated__/endpoints/store/store";
|
||||||
|
import { Text } from "@/components/atoms/Text/Text";
|
||||||
|
import { useSupabaseStore } from "@/lib/supabase/hooks/useSupabaseStore";
|
||||||
|
import * as React from "react";
|
||||||
|
import { MarketplaceBanners } from "../../../components/MarketplaceBanners/MarketplaceBanners";
|
||||||
|
import {
|
||||||
|
getLatestMarketplaceVersion,
|
||||||
|
isUserCreator as checkIsUserCreator,
|
||||||
|
calculateUpdateStatus,
|
||||||
|
} from "@/components/contextual/marketplaceHelpers";
|
||||||
|
|
||||||
interface AgentInfoProps {
|
interface AgentInfoProps {
|
||||||
user: User | null;
|
user: User | null;
|
||||||
@@ -21,6 +34,8 @@ interface AgentInfoProps {
|
|||||||
version: string;
|
version: string;
|
||||||
storeListingVersionId: string;
|
storeListingVersionId: string;
|
||||||
isAgentAddedToLibrary: boolean;
|
isAgentAddedToLibrary: boolean;
|
||||||
|
creatorSlug?: string;
|
||||||
|
agentSlug?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AgentInfo = ({
|
export const AgentInfo = ({
|
||||||
@@ -37,6 +52,8 @@ export const AgentInfo = ({
|
|||||||
version,
|
version,
|
||||||
storeListingVersionId,
|
storeListingVersionId,
|
||||||
isAgentAddedToLibrary,
|
isAgentAddedToLibrary,
|
||||||
|
creatorSlug,
|
||||||
|
agentSlug,
|
||||||
}: AgentInfoProps) => {
|
}: AgentInfoProps) => {
|
||||||
const {
|
const {
|
||||||
handleDownload,
|
handleDownload,
|
||||||
@@ -45,6 +62,137 @@ export const AgentInfo = ({
|
|||||||
isAddingAgentToLibrary,
|
isAddingAgentToLibrary,
|
||||||
} = useAgentInfo({ storeListingVersionId });
|
} = useAgentInfo({ storeListingVersionId });
|
||||||
|
|
||||||
|
// Get current user for update detection
|
||||||
|
const currentUser = useSupabaseStore((state) => state.user);
|
||||||
|
|
||||||
|
// State for expanding version list - start with 3, then show 3 more each time
|
||||||
|
const [visibleVersionCount, setVisibleVersionCount] = React.useState(3);
|
||||||
|
|
||||||
|
// Get store agent data for version history
|
||||||
|
const { data: storeAgentData } = useGetV2GetSpecificAgent(
|
||||||
|
creatorSlug || "",
|
||||||
|
agentSlug || "",
|
||||||
|
{ include_changelog: true },
|
||||||
|
{
|
||||||
|
query: {
|
||||||
|
enabled: !!(creatorSlug && agentSlug),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Calculate update information using simple helper functions
|
||||||
|
const storeData = okData<StoreAgentDetails>(storeAgentData);
|
||||||
|
const latestMarketplaceVersion = getLatestMarketplaceVersion(
|
||||||
|
storeData?.agentGraphVersions,
|
||||||
|
);
|
||||||
|
const currentVersion = parseInt(version, 10);
|
||||||
|
const isCreator = checkIsUserCreator(creator, currentUser);
|
||||||
|
const updateStatus = calculateUpdateStatus({
|
||||||
|
latestMarketplaceVersion,
|
||||||
|
currentVersion,
|
||||||
|
isUserCreator: isCreator,
|
||||||
|
isAgentAddedToLibrary,
|
||||||
|
});
|
||||||
|
|
||||||
|
const updateInfo = {
|
||||||
|
...updateStatus,
|
||||||
|
latestVersion: latestMarketplaceVersion,
|
||||||
|
isUserCreator: isCreator,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Process version data for display - use store listing versions (not agentGraphVersions)
|
||||||
|
const allVersions = storeData?.versions
|
||||||
|
? storeData.versions
|
||||||
|
.map((versionStr: string) => parseInt(versionStr, 10))
|
||||||
|
.sort((a: number, b: number) => b - a)
|
||||||
|
.map((versionNum: number) => ({
|
||||||
|
version: versionNum,
|
||||||
|
isCurrentVersion: false, // We'll update this logic if needed
|
||||||
|
}))
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const agentVersions = allVersions.slice(0, visibleVersionCount);
|
||||||
|
const hasMoreVersions = allVersions.length > visibleVersionCount;
|
||||||
|
|
||||||
|
const formatDate = (version: number) => {
|
||||||
|
// Generate sample dates based on version
|
||||||
|
const baseDate = new Date("2025-12-18");
|
||||||
|
baseDate.setDate(baseDate.getDate() - (19 - version) * 7);
|
||||||
|
return baseDate.toLocaleDateString("en-US", {
|
||||||
|
year: "numeric",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderVersionItem = (versionInfo: {
|
||||||
|
version: number;
|
||||||
|
isCurrentVersion: boolean;
|
||||||
|
}) => {
|
||||||
|
// Find real changelog data for this version
|
||||||
|
const storeData = okData<StoreAgentDetails>(storeAgentData);
|
||||||
|
const changelogEntry = storeData?.changelog?.find(
|
||||||
|
(entry: ChangelogEntry) =>
|
||||||
|
entry.version === versionInfo.version.toString(),
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={versionInfo.version} className="mb-6 last:mb-0">
|
||||||
|
{/* Version Header */}
|
||||||
|
<div className="mb-2 flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Text
|
||||||
|
variant="body"
|
||||||
|
className="font-semibold text-neutral-900 dark:text-neutral-100"
|
||||||
|
>
|
||||||
|
Version {versionInfo.version}.0
|
||||||
|
</Text>
|
||||||
|
{versionInfo.isCurrentVersion && (
|
||||||
|
<span className="rounded bg-blue-100 px-1.5 py-0.5 text-xs font-medium text-blue-800 dark:bg-blue-900 dark:text-blue-100">
|
||||||
|
Current
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<Text
|
||||||
|
variant="small"
|
||||||
|
className="text-neutral-500 dark:text-neutral-400"
|
||||||
|
>
|
||||||
|
{changelogEntry
|
||||||
|
? new Date(changelogEntry.date).toLocaleDateString("en-US", {
|
||||||
|
year: "numeric",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
})
|
||||||
|
: formatDate(versionInfo.version)}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Real Changelog Content */}
|
||||||
|
{changelogEntry && (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Text
|
||||||
|
variant="body"
|
||||||
|
className="text-neutral-700 dark:text-neutral-300"
|
||||||
|
>
|
||||||
|
{changelogEntry.changes_summary}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderMarketplaceBanners = () => {
|
||||||
|
return (
|
||||||
|
<MarketplaceBanners
|
||||||
|
hasUpdate={updateInfo.hasUpdate}
|
||||||
|
latestVersion={updateInfo.latestVersion}
|
||||||
|
hasUnpublishedChanges={updateInfo.hasUnpublishedChanges}
|
||||||
|
currentVersion={parseInt(version, 10)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full max-w-[396px] px-4 sm:px-6 lg:w-[396px] lg:px-0">
|
<div className="w-full max-w-[396px] px-4 sm:px-6 lg:w-[396px] lg:px-0">
|
||||||
{/* Title */}
|
{/* Title */}
|
||||||
@@ -158,17 +306,51 @@ export const AgentInfo = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Version History */}
|
{/* Update/Unpublished Changes Banners */}
|
||||||
<div className="flex w-full flex-col gap-0.5 sm:gap-1">
|
{renderMarketplaceBanners()}
|
||||||
|
|
||||||
|
{/* Changelog */}
|
||||||
|
<div className="flex w-full flex-col gap-1.5 sm:gap-2">
|
||||||
<div className="decoration-skip-ink-none mb-1.5 text-base font-medium leading-6 text-neutral-800 dark:text-neutral-200 sm:mb-2">
|
<div className="decoration-skip-ink-none mb-1.5 text-base font-medium leading-6 text-neutral-800 dark:text-neutral-200 sm:mb-2">
|
||||||
Version history
|
Changelog
|
||||||
</div>
|
</div>
|
||||||
<div className="decoration-skip-ink-none text-base font-normal leading-6 text-neutral-600 underline-offset-[from-font] dark:text-neutral-400">
|
<div className="decoration-skip-ink-none text-base font-normal leading-6 text-neutral-600 underline-offset-[from-font] dark:text-neutral-400">
|
||||||
Last updated {lastUpdated}
|
Last updated {lastUpdated}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-neutral-600 dark:text-neutral-400 sm:text-sm">
|
|
||||||
Version {version}
|
{/* Version List */}
|
||||||
</div>
|
{agentVersions.length > 0 ? (
|
||||||
|
<div className="mt-4">
|
||||||
|
{agentVersions.map(renderVersionItem)}
|
||||||
|
{hasMoreVersions && (
|
||||||
|
<button
|
||||||
|
onClick={() => setVisibleVersionCount((prev) => prev + 3)}
|
||||||
|
className="mt-2 flex items-center gap-1 text-sm font-medium text-neutral-900 hover:text-neutral-700 dark:text-neutral-100 dark:hover:text-neutral-300"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M4 6l4 4 4-4"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
fill="none"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span>Read more</span>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="text-xs text-neutral-600 dark:text-neutral-400 sm:text-sm">
|
||||||
|
Version {version}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
import { Separator } from "@/components/__legacy__/ui/separator";
|
import { Separator } from "@/components/__legacy__/ui/separator";
|
||||||
import { Breadcrumbs } from "@/components/molecules/Breadcrumbs/Breadcrumbs";
|
import { Breadcrumbs } from "@/components/molecules/Breadcrumbs/Breadcrumbs";
|
||||||
import { ErrorCard } from "@/components/molecules/ErrorCard/ErrorCard";
|
import { ErrorCard } from "@/components/molecules/ErrorCard/ErrorCard";
|
||||||
|
import { okData } from "@/app/api/helpers";
|
||||||
|
import type { StoreAgentDetails } from "@/app/api/__generated__/models/storeAgentDetails";
|
||||||
import { MarketplaceAgentPageParams } from "../../agent/[creator]/[slug]/page";
|
import { MarketplaceAgentPageParams } from "../../agent/[creator]/[slug]/page";
|
||||||
import { AgentImages } from "../AgentImages/AgentImage";
|
import { AgentImages } from "../AgentImages/AgentImage";
|
||||||
import { AgentInfo } from "../AgentInfo/AgentInfo";
|
import { AgentInfo } from "../AgentInfo/AgentInfo";
|
||||||
@@ -46,7 +48,8 @@ export const MainAgentPage = ({ params }: MainAgentPageProps) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!agent) {
|
const agentData = okData<StoreAgentDetails>(agent);
|
||||||
|
if (!agentData) {
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto w-full max-w-[1360px]">
|
<div className="mx-auto w-full max-w-[1360px]">
|
||||||
<main className="px-4">
|
<main className="px-4">
|
||||||
@@ -67,10 +70,10 @@ export const MainAgentPage = ({ params }: MainAgentPageProps) => {
|
|||||||
const breadcrumbs = [
|
const breadcrumbs = [
|
||||||
{ name: "Marketplace", link: "/marketplace" },
|
{ name: "Marketplace", link: "/marketplace" },
|
||||||
{
|
{
|
||||||
name: agent.creator,
|
name: agentData.creator ?? "",
|
||||||
link: `/marketplace/creator/${encodeURIComponent(agent.creator)}`,
|
link: `/marketplace/creator/${encodeURIComponent(agentData.creator ?? "")}`,
|
||||||
},
|
},
|
||||||
{ name: agent.agent_name, link: "#" },
|
{ name: agentData.agent_name ?? "", link: "#" },
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -82,18 +85,29 @@ export const MainAgentPage = ({ params }: MainAgentPageProps) => {
|
|||||||
<div className="w-full md:w-auto md:shrink-0">
|
<div className="w-full md:w-auto md:shrink-0">
|
||||||
<AgentInfo
|
<AgentInfo
|
||||||
user={user}
|
user={user}
|
||||||
agentId={agent.active_version_id ?? "–"}
|
agentId={agentData.active_version_id ?? "–"}
|
||||||
name={agent.agent_name}
|
name={agentData.agent_name ?? ""}
|
||||||
creator={agent.creator}
|
creator={agentData.creator ?? ""}
|
||||||
shortDescription={agent.sub_heading}
|
shortDescription={agentData.sub_heading ?? ""}
|
||||||
longDescription={agent.description}
|
longDescription={agentData.description ?? ""}
|
||||||
rating={agent.rating}
|
rating={agentData.rating ?? 0}
|
||||||
runs={agent.runs}
|
runs={agentData.runs ?? 0}
|
||||||
categories={agent.categories}
|
categories={agentData.categories ?? []}
|
||||||
lastUpdated={agent.last_updated.toISOString()}
|
lastUpdated={
|
||||||
version={agent.versions[agent.versions.length - 1]}
|
agentData.last_updated?.toISOString() ??
|
||||||
storeListingVersionId={agent.store_listing_version_id}
|
new Date().toISOString()
|
||||||
|
}
|
||||||
|
version={
|
||||||
|
agentData.versions
|
||||||
|
? Math.max(
|
||||||
|
...agentData.versions.map((v: string) => parseInt(v, 10)),
|
||||||
|
).toString()
|
||||||
|
: "1"
|
||||||
|
}
|
||||||
|
storeListingVersionId={agentData.store_listing_version_id ?? ""}
|
||||||
isAgentAddedToLibrary={Boolean(libraryAgent)}
|
isAgentAddedToLibrary={Boolean(libraryAgent)}
|
||||||
|
creatorSlug={params.creator}
|
||||||
|
agentSlug={params.slug}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<AgentImages
|
<AgentImages
|
||||||
@@ -101,23 +115,23 @@ export const MainAgentPage = ({ params }: MainAgentPageProps) => {
|
|||||||
const orderedImages: string[] = [];
|
const orderedImages: string[] = [];
|
||||||
|
|
||||||
// 1. YouTube/Overview video (if it exists)
|
// 1. YouTube/Overview video (if it exists)
|
||||||
if (agent.agent_video) {
|
if (agentData.agent_video) {
|
||||||
orderedImages.push(agent.agent_video);
|
orderedImages.push(agentData.agent_video);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. First image (hero)
|
// 2. First image (hero)
|
||||||
if (agent.agent_image.length > 0) {
|
if (agentData.agent_image?.length > 0) {
|
||||||
orderedImages.push(agent.agent_image[0]);
|
orderedImages.push(agentData.agent_image[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Agent Output Demo (if it exists)
|
// 3. Agent Output Demo (if it exists)
|
||||||
if ((agent as any).agent_output_demo) {
|
if (agentData.agent_output_demo) {
|
||||||
orderedImages.push((agent as any).agent_output_demo);
|
orderedImages.push(agentData.agent_output_demo);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Additional images
|
// 4. Additional images
|
||||||
if (agent.agent_image.length > 1) {
|
if (agentData.agent_image && agentData.agent_image.length > 1) {
|
||||||
orderedImages.push(...agent.agent_image.slice(1));
|
orderedImages.push(...agentData.agent_image.slice(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
return orderedImages;
|
return orderedImages;
|
||||||
@@ -129,7 +143,7 @@ export const MainAgentPage = ({ params }: MainAgentPageProps) => {
|
|||||||
<AgentsSection
|
<AgentsSection
|
||||||
margin="32px"
|
margin="32px"
|
||||||
agents={otherAgents.agents}
|
agents={otherAgents.agents}
|
||||||
sectionTitle={`Other agents by ${agent.creator}`}
|
sectionTitle={`Other agents by ${agentData.creator ?? ""}`}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Separator className="mb-[25px] mt-[60px]" />
|
<Separator className="mb-[25px] mt-[60px]" />
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { useGetV2GetAgentByStoreId } from "@/app/api/__generated__/endpoints/lib
|
|||||||
import { StoreAgentsResponse } from "@/app/api/__generated__/models/storeAgentsResponse";
|
import { StoreAgentsResponse } from "@/app/api/__generated__/models/storeAgentsResponse";
|
||||||
import { StoreAgentDetails } from "@/app/api/__generated__/models/storeAgentDetails";
|
import { StoreAgentDetails } from "@/app/api/__generated__/models/storeAgentDetails";
|
||||||
import { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
|
import { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
|
||||||
|
import { okData } from "@/app/api/helpers";
|
||||||
import { useSupabase } from "@/lib/supabase/hooks/useSupabase";
|
import { useSupabase } from "@/lib/supabase/hooks/useSupabase";
|
||||||
|
|
||||||
export const useMainAgentPage = ({
|
export const useMainAgentPage = ({
|
||||||
@@ -20,13 +21,7 @@ export const useMainAgentPage = ({
|
|||||||
data: agent,
|
data: agent,
|
||||||
isLoading: isAgentLoading,
|
isLoading: isAgentLoading,
|
||||||
isError: isAgentError,
|
isError: isAgentError,
|
||||||
} = useGetV2GetSpecificAgent(creator_lower, params.slug, {
|
} = useGetV2GetSpecificAgent(creator_lower, params.slug);
|
||||||
query: {
|
|
||||||
select: (x) => {
|
|
||||||
return x.data as StoreAgentDetails;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const {
|
const {
|
||||||
data: otherAgents,
|
data: otherAgents,
|
||||||
isLoading: isOtherAgentsLoading,
|
isLoading: isOtherAgentsLoading,
|
||||||
@@ -59,14 +54,18 @@ export const useMainAgentPage = ({
|
|||||||
data: libraryAgent,
|
data: libraryAgent,
|
||||||
isLoading: isLibraryAgentLoading,
|
isLoading: isLibraryAgentLoading,
|
||||||
isError: isLibraryAgentError,
|
isError: isLibraryAgentError,
|
||||||
} = useGetV2GetAgentByStoreId(agent?.active_version_id ?? "", {
|
} = useGetV2GetAgentByStoreId(
|
||||||
query: {
|
okData<StoreAgentDetails>(agent)?.active_version_id ?? "",
|
||||||
select: (x) => {
|
{
|
||||||
return x.data as LibraryAgent;
|
query: {
|
||||||
|
select: (x) => {
|
||||||
|
return x.data as LibraryAgent;
|
||||||
|
},
|
||||||
|
enabled:
|
||||||
|
!!user && !!okData<StoreAgentDetails>(agent)?.active_version_id,
|
||||||
},
|
},
|
||||||
enabled: !!user && !!agent?.active_version_id,
|
|
||||||
},
|
},
|
||||||
});
|
);
|
||||||
|
|
||||||
const isLoading =
|
const isLoading =
|
||||||
isAgentLoading ||
|
isAgentLoading ||
|
||||||
|
|||||||
@@ -2796,6 +2796,16 @@
|
|||||||
"in": "path",
|
"in": "path",
|
||||||
"required": true,
|
"required": true,
|
||||||
"schema": { "type": "string", "title": "Agent Name" }
|
"schema": { "type": "string", "title": "Agent Name" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "include_changelog",
|
||||||
|
"in": "query",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"title": "Include Changelog"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@@ -5983,6 +5993,16 @@
|
|||||||
"required": ["file"],
|
"required": ["file"],
|
||||||
"title": "Body_postV2Upload submission media"
|
"title": "Body_postV2Upload submission media"
|
||||||
},
|
},
|
||||||
|
"ChangelogEntry": {
|
||||||
|
"properties": {
|
||||||
|
"version": { "type": "string", "title": "Version" },
|
||||||
|
"changes_summary": { "type": "string", "title": "Changes Summary" },
|
||||||
|
"date": { "type": "string", "format": "date-time", "title": "Date" }
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
"required": ["version", "changes_summary", "date"],
|
||||||
|
"title": "ChangelogEntry"
|
||||||
|
},
|
||||||
"ChatRequest": {
|
"ChatRequest": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"query": { "type": "string", "title": "Query" },
|
"query": { "type": "string", "title": "Query" },
|
||||||
@@ -7426,6 +7446,11 @@
|
|||||||
"title": "Auto Update Version",
|
"title": "Auto Update Version",
|
||||||
"description": "Auto-update the agent version"
|
"description": "Auto-update the agent version"
|
||||||
},
|
},
|
||||||
|
"graph_version": {
|
||||||
|
"anyOf": [{ "type": "integer" }, { "type": "null" }],
|
||||||
|
"title": "Graph Version",
|
||||||
|
"description": "Specific graph version to update to"
|
||||||
|
},
|
||||||
"is_favorite": {
|
"is_favorite": {
|
||||||
"anyOf": [{ "type": "boolean" }, { "type": "null" }],
|
"anyOf": [{ "type": "boolean" }, { "type": "null" }],
|
||||||
"title": "Is Favorite",
|
"title": "Is Favorite",
|
||||||
@@ -8902,6 +8927,12 @@
|
|||||||
"type": "array",
|
"type": "array",
|
||||||
"title": "Versions"
|
"title": "Versions"
|
||||||
},
|
},
|
||||||
|
"agentGraphVersions": {
|
||||||
|
"items": { "type": "string" },
|
||||||
|
"type": "array",
|
||||||
|
"title": "Agentgraphversions"
|
||||||
|
},
|
||||||
|
"agentGraphId": { "type": "string", "title": "Agentgraphid" },
|
||||||
"last_updated": {
|
"last_updated": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "date-time",
|
"format": "date-time",
|
||||||
@@ -8919,6 +8950,16 @@
|
|||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"title": "Has Approved Version",
|
"title": "Has Approved Version",
|
||||||
"default": false
|
"default": false
|
||||||
|
},
|
||||||
|
"changelog": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"items": { "$ref": "#/components/schemas/ChangelogEntry" },
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
{ "type": "null" }
|
||||||
|
],
|
||||||
|
"title": "Changelog"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -8937,6 +8978,8 @@
|
|||||||
"runs",
|
"runs",
|
||||||
"rating",
|
"rating",
|
||||||
"versions",
|
"versions",
|
||||||
|
"agentGraphVersions",
|
||||||
|
"agentGraphId",
|
||||||
"last_updated"
|
"last_updated"
|
||||||
],
|
],
|
||||||
"title": "StoreAgentDetails"
|
"title": "StoreAgentDetails"
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ import {
|
|||||||
import { environment } from "@/services/environment";
|
import { environment } from "@/services/environment";
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
|
||||||
|
// Increase body size limit to 256MB to match backend file upload limit
|
||||||
|
export const maxDuration = 300; // 5 minutes timeout for large uploads
|
||||||
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
function buildBackendUrl(path: string[], queryString: string): string {
|
function buildBackendUrl(path: string[], queryString: string): string {
|
||||||
const backendPath = path.join("/");
|
const backendPath = path.join("/");
|
||||||
return `${environment.getAGPTServerBaseUrl()}/${backendPath}${queryString}`;
|
return `${environment.getAGPTServerBaseUrl()}/${backendPath}${queryString}`;
|
||||||
|
|||||||
@@ -1,36 +1,33 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { LaunchDarklyProvider } from "@/services/feature-flags/feature-flag-provider";
|
import { TooltipProvider } from "@/components/atoms/Tooltip/BaseTooltip";
|
||||||
import OnboardingProvider from "@/providers/onboarding/onboarding-provider";
|
import { SentryUserTracker } from "@/components/monitor/SentryUserTracker";
|
||||||
import { BackendAPIProvider } from "@/lib/autogpt-server-api/context";
|
import { BackendAPIProvider } from "@/lib/autogpt-server-api/context";
|
||||||
import { getQueryClient } from "@/lib/react-query/queryClient";
|
import { getQueryClient } from "@/lib/react-query/queryClient";
|
||||||
import { QueryClientProvider } from "@tanstack/react-query";
|
|
||||||
import {
|
|
||||||
ThemeProvider as NextThemesProvider,
|
|
||||||
ThemeProviderProps,
|
|
||||||
} from "next-themes";
|
|
||||||
import { NuqsAdapter } from "nuqs/adapters/next/app";
|
|
||||||
import { TooltipProvider } from "@/components/atoms/Tooltip/BaseTooltip";
|
|
||||||
import CredentialsProvider from "@/providers/agent-credentials/credentials-provider";
|
import CredentialsProvider from "@/providers/agent-credentials/credentials-provider";
|
||||||
import { SentryUserTracker } from "@/components/monitor/SentryUserTracker";
|
import OnboardingProvider from "@/providers/onboarding/onboarding-provider";
|
||||||
|
import { LaunchDarklyProvider } from "@/services/feature-flags/feature-flag-provider";
|
||||||
|
import { QueryClientProvider } from "@tanstack/react-query";
|
||||||
|
import { ThemeProvider, ThemeProviderProps } from "next-themes";
|
||||||
|
import { NuqsAdapter } from "nuqs/adapters/next/app";
|
||||||
|
|
||||||
export function Providers({ children, ...props }: ThemeProviderProps) {
|
export function Providers({ children, ...props }: ThemeProviderProps) {
|
||||||
const queryClient = getQueryClient();
|
const queryClient = getQueryClient();
|
||||||
return (
|
return (
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<NuqsAdapter>
|
<NuqsAdapter>
|
||||||
<NextThemesProvider {...props}>
|
<BackendAPIProvider>
|
||||||
<BackendAPIProvider>
|
<SentryUserTracker />
|
||||||
<SentryUserTracker />
|
<CredentialsProvider>
|
||||||
<CredentialsProvider>
|
<LaunchDarklyProvider>
|
||||||
<LaunchDarklyProvider>
|
<OnboardingProvider>
|
||||||
<OnboardingProvider>
|
<ThemeProvider forcedTheme="light" {...props}>
|
||||||
<TooltipProvider>{children}</TooltipProvider>
|
<TooltipProvider>{children}</TooltipProvider>
|
||||||
</OnboardingProvider>
|
</ThemeProvider>
|
||||||
</LaunchDarklyProvider>
|
</OnboardingProvider>
|
||||||
</CredentialsProvider>
|
</LaunchDarklyProvider>
|
||||||
</BackendAPIProvider>
|
</CredentialsProvider>
|
||||||
</NextThemesProvider>
|
</BackendAPIProvider>
|
||||||
</NuqsAdapter>
|
</NuqsAdapter>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,157 @@
|
|||||||
|
import type { Meta, StoryObj } from "@storybook/nextjs";
|
||||||
|
import { OverflowText } from "./OverflowText";
|
||||||
|
|
||||||
|
const meta: Meta<typeof OverflowText> = {
|
||||||
|
title: "Atoms/OverflowText",
|
||||||
|
component: OverflowText,
|
||||||
|
tags: ["autodocs"],
|
||||||
|
parameters: {
|
||||||
|
layout: "centered",
|
||||||
|
docs: {
|
||||||
|
description: {
|
||||||
|
component:
|
||||||
|
"Text component that automatically truncates overflowing content with ellipsis and shows a tooltip on hover when truncated. Supports both string and ReactNode values.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
argTypes: {
|
||||||
|
value: {
|
||||||
|
control: "text",
|
||||||
|
description: "The text content to display (string or ReactNode)",
|
||||||
|
},
|
||||||
|
className: {
|
||||||
|
control: "text",
|
||||||
|
description: "Additional CSS classes to customize styling",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
args: {
|
||||||
|
value: "This is a sample text that may overflow",
|
||||||
|
className: "",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
render: function DefaultOverflowText(args) {
|
||||||
|
return (
|
||||||
|
<div className="w-64">
|
||||||
|
<OverflowText {...args} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ShortText: Story = {
|
||||||
|
args: {
|
||||||
|
value: "Short text",
|
||||||
|
},
|
||||||
|
render: function ShortTextStory(args) {
|
||||||
|
return (
|
||||||
|
<div className="w-64">
|
||||||
|
<OverflowText {...args} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LongText: Story = {
|
||||||
|
args: {
|
||||||
|
value:
|
||||||
|
"This is a very long text that will definitely overflow and show a tooltip when you hover over it",
|
||||||
|
},
|
||||||
|
render: function LongTextStory(args) {
|
||||||
|
return (
|
||||||
|
<div className="w-64">
|
||||||
|
<OverflowText {...args} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CustomStyling: Story = {
|
||||||
|
args: {
|
||||||
|
value: "Text with custom styling",
|
||||||
|
className: "text-lg font-semibold text-indigo-600",
|
||||||
|
},
|
||||||
|
render: function CustomStylingStory(args) {
|
||||||
|
return (
|
||||||
|
<div className="w-64">
|
||||||
|
<OverflowText {...args} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithReactNode: Story = {
|
||||||
|
args: {
|
||||||
|
value: (
|
||||||
|
<span>
|
||||||
|
Text with <strong>bold</strong> and <em>italic</em> content
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
render: function WithReactNodeStory(args) {
|
||||||
|
return (
|
||||||
|
<div className="w-64">
|
||||||
|
<OverflowText {...args} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DifferentWidths: Story = {
|
||||||
|
render: function DifferentWidthsStory() {
|
||||||
|
const longText =
|
||||||
|
"This text will truncate differently depending on the container width";
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-8">
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<span className="text-xs text-zinc-500">Width: 200px</span>
|
||||||
|
<div className="w-[200px]">
|
||||||
|
<OverflowText value={longText} variant="body" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<span className="text-xs text-zinc-500">Width: 300px</span>
|
||||||
|
<div className="w-[300px]">
|
||||||
|
<OverflowText value={longText} variant="body" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<span className="text-xs text-zinc-500">Width: 400px</span>
|
||||||
|
<div className="w-[400px]">
|
||||||
|
<OverflowText value={longText} variant="body" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FilePathExample: Story = {
|
||||||
|
args: {
|
||||||
|
value: "/very/long/path/to/a/file/that/might/overflow/in/the/ui.tsx",
|
||||||
|
},
|
||||||
|
render: function FilePathExampleStory(args) {
|
||||||
|
return (
|
||||||
|
<div className="w-64">
|
||||||
|
<OverflowText {...args} className="font-mono text-sm" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const URLExample: Story = {
|
||||||
|
args: {
|
||||||
|
value: "https://example.com/very/long/url/path/that/might/overflow",
|
||||||
|
},
|
||||||
|
render: function URLExampleStory(args) {
|
||||||
|
return (
|
||||||
|
<div className="w-64">
|
||||||
|
<OverflowText {...args} className="text-blue-600" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
import { Text, type TextProps } from "@/components/atoms/Text/Text";
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipProvider,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from "@/components/atoms/Tooltip/BaseTooltip";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import type { ReactNode } from "react";
|
||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
|
interface Props extends Omit<TextProps, "children"> {
|
||||||
|
value: string | ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OverflowText(props: Props) {
|
||||||
|
const elementRef = useRef<HTMLSpanElement | null>(null);
|
||||||
|
const [isTruncated, setIsTruncated] = useState(false);
|
||||||
|
|
||||||
|
function updateTruncation() {
|
||||||
|
const element = elementRef.current;
|
||||||
|
|
||||||
|
if (!element) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasOverflow = element.scrollWidth > element.clientWidth;
|
||||||
|
|
||||||
|
setIsTruncated(hasOverflow);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupResizeListener() {
|
||||||
|
function handleResize() {
|
||||||
|
updateTruncation();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("resize", handleResize);
|
||||||
|
|
||||||
|
return function cleanupResizeListener() {
|
||||||
|
window.removeEventListener("resize", handleResize);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupObserver() {
|
||||||
|
const element = elementRef.current;
|
||||||
|
|
||||||
|
if (!element || typeof ResizeObserver === "undefined") {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleResizeObserver() {
|
||||||
|
updateTruncation();
|
||||||
|
}
|
||||||
|
|
||||||
|
const observer = new ResizeObserver(handleResizeObserver);
|
||||||
|
|
||||||
|
observer.observe(element);
|
||||||
|
|
||||||
|
return function disconnectObserver() {
|
||||||
|
observer.disconnect();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof props.value === "string") updateTruncation();
|
||||||
|
}, [props.value]);
|
||||||
|
|
||||||
|
useEffect(setupResizeListener, []);
|
||||||
|
useEffect(setupObserver, []);
|
||||||
|
|
||||||
|
const { value, className, variant = "body", ...restProps } = props;
|
||||||
|
|
||||||
|
const content = (
|
||||||
|
<span
|
||||||
|
ref={elementRef}
|
||||||
|
className={cn(
|
||||||
|
"block min-w-0 overflow-hidden text-ellipsis whitespace-nowrap",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Text variant={variant} className={className} {...restProps}>
|
||||||
|
{value}
|
||||||
|
</Text>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isTruncated) {
|
||||||
|
return (
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>{content}</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
{typeof value === "string" ? <p>{value}</p> : value}
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return content;
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ import { Cross2Icon } from "@radix-ui/react-icons";
|
|||||||
import React, { useCallback } from "react";
|
import React, { useCallback } from "react";
|
||||||
import { GoogleDrivePicker } from "./GoogleDrivePicker";
|
import { GoogleDrivePicker } from "./GoogleDrivePicker";
|
||||||
|
|
||||||
export interface GoogleDrivePickerInputProps {
|
export interface Props {
|
||||||
config: GoogleDrivePickerConfig;
|
config: GoogleDrivePickerConfig;
|
||||||
value: any;
|
value: any;
|
||||||
onChange: (value: any) => void;
|
onChange: (value: any) => void;
|
||||||
@@ -21,7 +21,7 @@ export function GoogleDrivePickerInput({
|
|||||||
error,
|
error,
|
||||||
className,
|
className,
|
||||||
showRemoveButton = true,
|
showRemoveButton = true,
|
||||||
}: GoogleDrivePickerInputProps) {
|
}: Props) {
|
||||||
const [pickerError, setPickerError] = React.useState<string | null>(null);
|
const [pickerError, setPickerError] = React.useState<string | null>(null);
|
||||||
const isMultiSelect = config.multiselect || false;
|
const isMultiSelect = config.multiselect || false;
|
||||||
const hasAutoCredentials = !!config.auto_credentials;
|
const hasAutoCredentials = !!config.auto_credentials;
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ export function PublishAgentModal({
|
|||||||
trigger,
|
trigger,
|
||||||
targetState,
|
targetState,
|
||||||
onStateChange,
|
onStateChange,
|
||||||
|
preSelectedAgentId,
|
||||||
|
preSelectedAgentVersion,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const {
|
const {
|
||||||
// State
|
// State
|
||||||
@@ -34,7 +36,12 @@ export function PublishAgentModal({
|
|||||||
handleGoToBuilder,
|
handleGoToBuilder,
|
||||||
handleSuccessFromInfo,
|
handleSuccessFromInfo,
|
||||||
handleBack,
|
handleBack,
|
||||||
} = usePublishAgentModal({ targetState, onStateChange });
|
} = usePublishAgentModal({
|
||||||
|
targetState,
|
||||||
|
onStateChange,
|
||||||
|
preSelectedAgentId,
|
||||||
|
preSelectedAgentVersion,
|
||||||
|
});
|
||||||
|
|
||||||
const { user, isUserLoading } = useSupabase();
|
const { user, isUserLoading } = useSupabase();
|
||||||
|
|
||||||
@@ -65,6 +72,7 @@ export function PublishAgentModal({
|
|||||||
selectedAgentId={selectedAgentId}
|
selectedAgentId={selectedAgentId}
|
||||||
selectedAgentVersion={selectedAgentVersion}
|
selectedAgentVersion={selectedAgentVersion}
|
||||||
initialData={initialData}
|
initialData={initialData}
|
||||||
|
isMarketplaceUpdate={!!currentState.submissionData}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
case "review":
|
case "review":
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export function AgentInfoStep({
|
|||||||
selectedAgentId,
|
selectedAgentId,
|
||||||
selectedAgentVersion,
|
selectedAgentVersion,
|
||||||
initialData,
|
initialData,
|
||||||
|
isMarketplaceUpdate,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const {
|
const {
|
||||||
form,
|
form,
|
||||||
@@ -34,6 +35,7 @@ export function AgentInfoStep({
|
|||||||
selectedAgentId,
|
selectedAgentId,
|
||||||
selectedAgentVersion,
|
selectedAgentVersion,
|
||||||
initialData,
|
initialData,
|
||||||
|
isMarketplaceUpdate,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [cronScheduleDialogOpen, setCronScheduleDialogOpen] =
|
const [cronScheduleDialogOpen, setCronScheduleDialogOpen] =
|
||||||
@@ -65,6 +67,41 @@ export function AgentInfoStep({
|
|||||||
|
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form onSubmit={handleSubmit} className="flex-grow overflow-y-auto p-6">
|
<form onSubmit={handleSubmit} className="flex-grow overflow-y-auto p-6">
|
||||||
|
{/* Changes summary field - only shown for updates */}
|
||||||
|
{isMarketplaceUpdate && (
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="changesSummary"
|
||||||
|
render={({ field }) => (
|
||||||
|
<div className="mb-6">
|
||||||
|
<Input
|
||||||
|
id={field.name}
|
||||||
|
label="What changed?"
|
||||||
|
type="textarea"
|
||||||
|
placeholder="Describe what's new or improved in this version..."
|
||||||
|
error={form.formState.errors.changesSummary?.message}
|
||||||
|
required
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
<Text variant="small" className="mt-1 text-gray-600">
|
||||||
|
This is required to help users understand what's
|
||||||
|
different in this update.
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Optional section label for updates */}
|
||||||
|
{isMarketplaceUpdate && (
|
||||||
|
<div className="mb-4">
|
||||||
|
<Text variant="body" className="font-medium text-gray-700">
|
||||||
|
Optional: Update any of the following details (or leave them
|
||||||
|
as-is)
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="title"
|
name="title"
|
||||||
|
|||||||
@@ -25,6 +25,17 @@ export function useThumbnailImages({
|
|||||||
const thumbnailsContainerRef = useRef<HTMLDivElement | null>(null);
|
const thumbnailsContainerRef = useRef<HTMLDivElement | null>(null);
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
|
// Memoize the stringified version to detect actual changes
|
||||||
|
const initialImagesKey = JSON.stringify(initialImages);
|
||||||
|
|
||||||
|
// Update images when initialImages prop changes (by value, not reference)
|
||||||
|
useEffect(() => {
|
||||||
|
if (initialImages.length > 0) {
|
||||||
|
setImages(initialImages);
|
||||||
|
setSelectedImage(initialSelectedImage || initialImages[0]);
|
||||||
|
}
|
||||||
|
}, [initialImagesKey, initialSelectedImage]); // Use stringified key instead of array reference
|
||||||
|
|
||||||
// Notify parent when images change
|
// Notify parent when images change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onImagesChange(images);
|
onImagesChange(images);
|
||||||
|
|||||||
@@ -1,45 +1,113 @@
|
|||||||
import z from "zod";
|
import z from "zod";
|
||||||
import { validateYouTubeUrl } from "@/lib/utils";
|
import { validateYouTubeUrl } from "@/lib/utils";
|
||||||
|
|
||||||
export const publishAgentSchema = z.object({
|
// Create conditional schema that changes based on whether it's a marketplace update
|
||||||
title: z
|
export const publishAgentSchemaFactory = (
|
||||||
.string()
|
isMarketplaceUpdate: boolean = false,
|
||||||
.min(1, "Title is required")
|
) => {
|
||||||
.max(100, "Title must be less than 100 characters"),
|
const baseSchema = {
|
||||||
subheader: z
|
changesSummary: isMarketplaceUpdate
|
||||||
.string()
|
? z
|
||||||
.min(1, "Subheader is required")
|
.string()
|
||||||
.max(200, "Subheader must be less than 200 characters"),
|
.min(1, "Changes summary is required for updates")
|
||||||
slug: z
|
.max(500, "Changes summary must be less than 500 characters")
|
||||||
.string()
|
: z.string().optional(),
|
||||||
.min(1, "Slug is required")
|
title: isMarketplaceUpdate
|
||||||
.max(50, "Slug must be less than 50 characters")
|
? z
|
||||||
.regex(
|
.string()
|
||||||
/^[a-z0-9-]+$/,
|
.optional()
|
||||||
"Slug can only contain lowercase letters, numbers, and hyphens",
|
.refine(
|
||||||
),
|
(val) => !val || val.length <= 100,
|
||||||
youtubeLink: z
|
"Title must be less than 100 characters",
|
||||||
.string()
|
)
|
||||||
.refine(validateYouTubeUrl, "Please enter a valid YouTube URL"),
|
: z
|
||||||
category: z.string().min(1, "Category is required"),
|
.string()
|
||||||
description: z
|
.min(1, "Title is required")
|
||||||
.string()
|
.max(100, "Title must be less than 100 characters"),
|
||||||
.min(1, "Description is required")
|
subheader: isMarketplaceUpdate
|
||||||
.max(1000, "Description must be less than 1000 characters"),
|
? z
|
||||||
recommendedScheduleCron: z.string().optional(),
|
.string()
|
||||||
instructions: z
|
.optional()
|
||||||
.string()
|
.refine(
|
||||||
.optional()
|
(val) => !val || val.length <= 200,
|
||||||
.refine(
|
"Subheader must be less than 200 characters",
|
||||||
(val) => !val || val.length <= 2000,
|
)
|
||||||
"Instructions must be less than 2000 characters",
|
: z
|
||||||
),
|
.string()
|
||||||
agentOutputDemo: z
|
.min(1, "Subheader is required")
|
||||||
.string()
|
.max(200, "Subheader must be less than 200 characters"),
|
||||||
.refine(validateYouTubeUrl, "Please enter a valid YouTube URL"),
|
slug: isMarketplaceUpdate
|
||||||
});
|
? z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.refine(
|
||||||
|
(val) => !val || (val.length <= 50 && /^[a-z0-9-]+$/.test(val)),
|
||||||
|
"Slug can only contain lowercase letters, numbers, and hyphens",
|
||||||
|
)
|
||||||
|
: z
|
||||||
|
.string()
|
||||||
|
.min(1, "Slug is required")
|
||||||
|
.max(50, "Slug must be less than 50 characters")
|
||||||
|
.regex(
|
||||||
|
/^[a-z0-9-]+$/,
|
||||||
|
"Slug can only contain lowercase letters, numbers, and hyphens",
|
||||||
|
),
|
||||||
|
youtubeLink: isMarketplaceUpdate
|
||||||
|
? z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.refine(
|
||||||
|
(val) => !val || validateYouTubeUrl(val),
|
||||||
|
"Please enter a valid YouTube URL",
|
||||||
|
)
|
||||||
|
: z
|
||||||
|
.string()
|
||||||
|
.refine(validateYouTubeUrl, "Please enter a valid YouTube URL"),
|
||||||
|
category: isMarketplaceUpdate
|
||||||
|
? z.string().optional()
|
||||||
|
: z.string().min(1, "Category is required"),
|
||||||
|
description: isMarketplaceUpdate
|
||||||
|
? z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.refine(
|
||||||
|
(val) => !val || val.length <= 1000,
|
||||||
|
"Description must be less than 1000 characters",
|
||||||
|
)
|
||||||
|
: z
|
||||||
|
.string()
|
||||||
|
.min(1, "Description is required")
|
||||||
|
.max(1000, "Description must be less than 1000 characters"),
|
||||||
|
recommendedScheduleCron: z.string().optional(),
|
||||||
|
instructions: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.refine(
|
||||||
|
(val) => !val || val.length <= 2000,
|
||||||
|
"Instructions must be less than 2000 characters",
|
||||||
|
),
|
||||||
|
agentOutputDemo: isMarketplaceUpdate
|
||||||
|
? z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.refine(
|
||||||
|
(val) => !val || validateYouTubeUrl(val),
|
||||||
|
"Please enter a valid YouTube URL",
|
||||||
|
)
|
||||||
|
: z
|
||||||
|
.string()
|
||||||
|
.refine(validateYouTubeUrl, "Please enter a valid YouTube URL"),
|
||||||
|
};
|
||||||
|
|
||||||
export type PublishAgentFormData = z.infer<typeof publishAgentSchema>;
|
return z.object(baseSchema);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Default schema for backwards compatibility
|
||||||
|
export const publishAgentSchema = publishAgentSchemaFactory(false);
|
||||||
|
|
||||||
|
export type PublishAgentFormData = z.infer<
|
||||||
|
ReturnType<typeof publishAgentSchemaFactory>
|
||||||
|
>;
|
||||||
|
|
||||||
export interface PublishAgentInfoInitialData {
|
export interface PublishAgentInfoInitialData {
|
||||||
agent_id: string;
|
agent_id: string;
|
||||||
@@ -54,4 +122,5 @@ export interface PublishAgentInfoInitialData {
|
|||||||
recommendedScheduleCron?: string;
|
recommendedScheduleCron?: string;
|
||||||
instructions?: string;
|
instructions?: string;
|
||||||
agentOutputDemo?: string;
|
agentOutputDemo?: string;
|
||||||
|
changesSummary?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import * as Sentry from "@sentry/nextjs";
|
|||||||
import {
|
import {
|
||||||
PublishAgentFormData,
|
PublishAgentFormData,
|
||||||
PublishAgentInfoInitialData,
|
PublishAgentInfoInitialData,
|
||||||
publishAgentSchema,
|
publishAgentSchemaFactory,
|
||||||
} from "./helpers";
|
} from "./helpers";
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
@@ -18,6 +18,7 @@ export interface Props {
|
|||||||
selectedAgentId: string | null;
|
selectedAgentId: string | null;
|
||||||
selectedAgentVersion: number | null;
|
selectedAgentVersion: number | null;
|
||||||
initialData?: PublishAgentInfoInitialData;
|
initialData?: PublishAgentInfoInitialData;
|
||||||
|
isMarketplaceUpdate?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useAgentInfoStep({
|
export function useAgentInfoStep({
|
||||||
@@ -26,6 +27,7 @@ export function useAgentInfoStep({
|
|||||||
selectedAgentId,
|
selectedAgentId,
|
||||||
selectedAgentVersion,
|
selectedAgentVersion,
|
||||||
initialData,
|
initialData,
|
||||||
|
isMarketplaceUpdate = false,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const [agentId, setAgentId] = useState<string | null>(null);
|
const [agentId, setAgentId] = useState<string | null>(null);
|
||||||
const [images, setImages] = useState<string[]>([]);
|
const [images, setImages] = useState<string[]>([]);
|
||||||
@@ -36,8 +38,9 @@ export function useAgentInfoStep({
|
|||||||
const api = useBackendAPI();
|
const api = useBackendAPI();
|
||||||
|
|
||||||
const form = useForm<PublishAgentFormData>({
|
const form = useForm<PublishAgentFormData>({
|
||||||
resolver: zodResolver(publishAgentSchema),
|
resolver: zodResolver(publishAgentSchemaFactory(isMarketplaceUpdate)),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
|
changesSummary: "",
|
||||||
title: "",
|
title: "",
|
||||||
subheader: "",
|
subheader: "",
|
||||||
slug: "",
|
slug: "",
|
||||||
@@ -61,6 +64,7 @@ export function useAgentInfoStep({
|
|||||||
|
|
||||||
// Update form with initial data
|
// Update form with initial data
|
||||||
form.reset({
|
form.reset({
|
||||||
|
changesSummary: initialData.changesSummary || "",
|
||||||
title: initialData.title,
|
title: initialData.title,
|
||||||
subheader: initialData.subheader,
|
subheader: initialData.subheader,
|
||||||
slug: initialData.slug.toLocaleLowerCase().trim(),
|
slug: initialData.slug.toLocaleLowerCase().trim(),
|
||||||
@@ -104,9 +108,10 @@ export function useAgentInfoStep({
|
|||||||
agent_output_demo_url: data.agentOutputDemo || "",
|
agent_output_demo_url: data.agentOutputDemo || "",
|
||||||
agent_id: selectedAgentId || "",
|
agent_id: selectedAgentId || "",
|
||||||
agent_version: selectedAgentVersion || 0,
|
agent_version: selectedAgentVersion || 0,
|
||||||
slug: data.slug.replace(/\s+/g, "-"),
|
slug: (data.slug || "").replace(/\s+/g, "-"),
|
||||||
categories: filteredCategories,
|
categories: filteredCategories,
|
||||||
recommended_schedule_cron: data.recommendedScheduleCron || null,
|
recommended_schedule_cron: data.recommendedScheduleCron || null,
|
||||||
|
changes_summary: data.changesSummary || null,
|
||||||
} as any);
|
} as any);
|
||||||
|
|
||||||
await queryClient.invalidateQueries({
|
await queryClient.invalidateQueries({
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export function AgentReviewStep({
|
|||||||
</Text>
|
</Text>
|
||||||
<Text
|
<Text
|
||||||
variant="large"
|
variant="large"
|
||||||
className="line-clamp-1 text-ellipsis text-center !text-neutral-500"
|
className="line-clamp-1 text-ellipsis text-center text-neutral-500"
|
||||||
>
|
>
|
||||||
{subheader}
|
{subheader}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -80,7 +80,7 @@ export function AgentReviewStep({
|
|||||||
{description ? (
|
{description ? (
|
||||||
<Text
|
<Text
|
||||||
variant="large"
|
variant="large"
|
||||||
className="line-clamp-1 text-ellipsis pt-2 text-center !text-neutral-500"
|
className="line-clamp-1 text-ellipsis pt-2 text-center text-neutral-500"
|
||||||
>
|
>
|
||||||
{description}
|
{description}
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { Skeleton } from "@/components/__legacy__/ui/skeleton";
|
|||||||
import { useAgentSelectStep } from "./useAgentSelectStep";
|
import { useAgentSelectStep } from "./useAgentSelectStep";
|
||||||
import { scrollbarStyles } from "@/components/styles/scrollbars";
|
import { scrollbarStyles } from "@/components/styles/scrollbars";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
import type { StoreSubmission } from "@/app/api/__generated__/models/storeSubmission";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onSelect: (agentId: string, agentVersion: number) => void;
|
onSelect: (agentId: string, agentVersion: number) => void;
|
||||||
@@ -22,6 +23,7 @@ interface Props {
|
|||||||
imageSrc: string;
|
imageSrc: string;
|
||||||
recommendedScheduleCron: string | null;
|
recommendedScheduleCron: string | null;
|
||||||
},
|
},
|
||||||
|
publishedSubmissionData?: StoreSubmission | null,
|
||||||
) => void;
|
) => void;
|
||||||
onOpenBuilder: () => void;
|
onOpenBuilder: () => void;
|
||||||
}
|
}
|
||||||
@@ -42,6 +44,8 @@ export function AgentSelectStep({
|
|||||||
// Handlers
|
// Handlers
|
||||||
handleAgentClick,
|
handleAgentClick,
|
||||||
handleNext,
|
handleNext,
|
||||||
|
// Utils
|
||||||
|
getPublishedVersion,
|
||||||
// Computed
|
// Computed
|
||||||
isNextDisabled,
|
isNextDisabled,
|
||||||
} = useAgentSelectStep({ onSelect, onNext });
|
} = useAgentSelectStep({ onSelect, onNext });
|
||||||
@@ -131,26 +135,17 @@ export function AgentSelectStep({
|
|||||||
<div className="p-2">
|
<div className="p-2">
|
||||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
{agents.map((agent) => (
|
{agents.map((agent) => (
|
||||||
<div
|
<button
|
||||||
key={agent.id}
|
key={agent.id}
|
||||||
data-testid="agent-card"
|
data-testid="agent-card"
|
||||||
className={`cursor-pointer select-none overflow-hidden rounded-2xl border border-neutral-200 shadow-sm transition-all ${
|
onClick={() =>
|
||||||
|
handleAgentClick(agent.name, agent.id, agent.version)
|
||||||
|
}
|
||||||
|
className={`w-full select-none overflow-hidden rounded-2xl border border-neutral-200 text-left shadow-sm transition-all ${
|
||||||
selectedAgentId === agent.id
|
selectedAgentId === agent.id
|
||||||
? "border-transparent shadow-none ring-4 ring-violet-600"
|
? "border-transparent shadow-none ring-4 ring-violet-600"
|
||||||
: "hover:shadow-md"
|
: "hover:shadow-md"
|
||||||
}`}
|
}`}
|
||||||
onClick={() =>
|
|
||||||
handleAgentClick(agent.name, agent.id, agent.version)
|
|
||||||
}
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === "Enter" || e.key === " ") {
|
|
||||||
e.preventDefault();
|
|
||||||
handleAgentClick(agent.name, agent.id, agent.version);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
tabIndex={0}
|
|
||||||
role="button"
|
|
||||||
aria-pressed={selectedAgentId === agent.id}
|
|
||||||
>
|
>
|
||||||
<div className="relative h-32 bg-zinc-400 sm:h-40">
|
<div className="relative h-32 bg-zinc-400 sm:h-40">
|
||||||
<Image
|
<Image
|
||||||
@@ -162,12 +157,44 @@ export function AgentSelectStep({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-2 p-3">
|
<div className="flex flex-col gap-2 p-3">
|
||||||
<Text variant="large-medium">{agent.name}</Text>
|
<Text variant="large-medium" className="line-clamp-2">
|
||||||
<Text variant="small" className="!text-neutral-500">
|
{agent.name}
|
||||||
Edited {agent.lastEdited}
|
|
||||||
</Text>
|
</Text>
|
||||||
|
<div className="flex items-center justify-between gap-2">
|
||||||
|
<div className="flex-1">
|
||||||
|
<Text variant="small" className="text-neutral-500">
|
||||||
|
Edited {agent.lastEdited}
|
||||||
|
</Text>
|
||||||
|
{agent.isMarketplaceUpdate &&
|
||||||
|
(() => {
|
||||||
|
const publishedVersion = getPublishedVersion(
|
||||||
|
agent.id,
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
publishedVersion && (
|
||||||
|
<Text
|
||||||
|
variant="small"
|
||||||
|
className="block text-neutral-500"
|
||||||
|
>
|
||||||
|
v{publishedVersion} → v{agent.version}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
</div>
|
||||||
|
{agent.isMarketplaceUpdate && (
|
||||||
|
<span className="shrink-0 rounded-full bg-blue-100 px-2 py-1 text-xs font-medium text-blue-800 dark:bg-blue-900 dark:text-blue-200">
|
||||||
|
Update
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{!agent.isMarketplaceUpdate && (
|
||||||
|
<span className="shrink-0 rounded-full bg-green-100 px-2 py-1 text-xs font-medium text-green-800 dark:bg-green-900 dark:text-green-200">
|
||||||
|
New
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useGetV2GetMyAgents } from "@/app/api/__generated__/endpoints/store/store";
|
import {
|
||||||
|
useGetV2GetMyAgents,
|
||||||
|
useGetV2ListMySubmissions,
|
||||||
|
} from "@/app/api/__generated__/endpoints/store/store";
|
||||||
|
import { okData } from "@/app/api/helpers";
|
||||||
|
import type { MyAgent } from "@/app/api/__generated__/models/myAgent";
|
||||||
|
import type { StoreSubmission } from "@/app/api/__generated__/models/storeSubmission";
|
||||||
|
|
||||||
export interface Agent {
|
export interface Agent {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -9,6 +15,7 @@ export interface Agent {
|
|||||||
imageSrc: string;
|
imageSrc: string;
|
||||||
description: string;
|
description: string;
|
||||||
recommendedScheduleCron: string | null;
|
recommendedScheduleCron: string | null;
|
||||||
|
isMarketplaceUpdate: boolean; // true if this is an update to existing published agent
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UseAgentSelectStepProps {
|
interface UseAgentSelectStepProps {
|
||||||
@@ -22,6 +29,7 @@ interface UseAgentSelectStepProps {
|
|||||||
imageSrc: string;
|
imageSrc: string;
|
||||||
recommendedScheduleCron: string | null;
|
recommendedScheduleCron: string | null;
|
||||||
},
|
},
|
||||||
|
publishedSubmissionData?: StoreSubmission | null, // For pre-filling updates
|
||||||
) => void;
|
) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,27 +44,88 @@ export function useAgentSelectStep({
|
|||||||
number | null
|
number | null
|
||||||
>(null);
|
>(null);
|
||||||
|
|
||||||
const { data: myAgents, isLoading, error } = useGetV2GetMyAgents();
|
const {
|
||||||
|
data: myAgents,
|
||||||
|
isLoading: agentsLoading,
|
||||||
|
error: agentsError,
|
||||||
|
} = useGetV2GetMyAgents();
|
||||||
|
const {
|
||||||
|
data: mySubmissions,
|
||||||
|
isLoading: submissionsLoading,
|
||||||
|
error: submissionsError,
|
||||||
|
} = useGetV2ListMySubmissions();
|
||||||
|
|
||||||
const agents: Agent[] =
|
const isLoading = agentsLoading || submissionsLoading;
|
||||||
(myAgents?.status === 200 &&
|
const error = agentsError || submissionsError;
|
||||||
myAgents.data.agents
|
|
||||||
.map(
|
const agents: Agent[] = React.useMemo(() => {
|
||||||
(agent): Agent => ({
|
// Properly handle API responses with okData helper
|
||||||
name: agent.agent_name,
|
const agentsData = (okData(myAgents) as any)?.agents || [];
|
||||||
id: agent.agent_id,
|
const submissionsData = (okData(mySubmissions) as any)?.submissions || [];
|
||||||
version: agent.agent_version,
|
|
||||||
lastEdited: agent.last_edited.toLocaleDateString(),
|
if (agentsData.length === 0) {
|
||||||
imageSrc: agent.agent_image || "https://picsum.photos/300/200",
|
return [];
|
||||||
description: agent.description || "",
|
}
|
||||||
recommendedScheduleCron: agent.recommended_schedule_cron ?? null,
|
|
||||||
}),
|
return agentsData
|
||||||
)
|
.map((agent: MyAgent): Agent | null => {
|
||||||
.sort(
|
// Find the highest published agent_version for this agent from approved submissions
|
||||||
(a: Agent, b: Agent) =>
|
const publishedVersion = submissionsData
|
||||||
new Date(b.lastEdited).getTime() - new Date(a.lastEdited).getTime(),
|
.filter(
|
||||||
)) ||
|
(s: StoreSubmission) =>
|
||||||
[];
|
s.status === "APPROVED" && s.agent_id === agent.agent_id,
|
||||||
|
)
|
||||||
|
.reduce(
|
||||||
|
(max: number | undefined, s: StoreSubmission) =>
|
||||||
|
max === undefined || s.agent_version > max
|
||||||
|
? s.agent_version
|
||||||
|
: max,
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
const isMarketplaceUpdate =
|
||||||
|
publishedVersion !== undefined &&
|
||||||
|
agent.agent_version > publishedVersion;
|
||||||
|
const isNewAgent = publishedVersion === undefined;
|
||||||
|
|
||||||
|
// Only include agents that are either new or have newer versions than published
|
||||||
|
if (!isNewAgent && !isMarketplaceUpdate) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: agent.agent_name,
|
||||||
|
id: agent.agent_id,
|
||||||
|
version: agent.agent_version,
|
||||||
|
lastEdited: agent.last_edited.toLocaleDateString(),
|
||||||
|
imageSrc: agent.agent_image || "https://picsum.photos/300/200",
|
||||||
|
description: agent.description || "",
|
||||||
|
recommendedScheduleCron: agent.recommended_schedule_cron ?? null,
|
||||||
|
isMarketplaceUpdate,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter((agent: Agent | null): agent is Agent => agent !== null)
|
||||||
|
.sort(
|
||||||
|
(a: Agent, b: Agent) =>
|
||||||
|
new Date(b.lastEdited).getTime() - new Date(a.lastEdited).getTime(),
|
||||||
|
);
|
||||||
|
}, [myAgents, mySubmissions]);
|
||||||
|
|
||||||
|
// Function to get published submission data for pre-filling updates
|
||||||
|
const getPublishedSubmissionData = (agentId: string) => {
|
||||||
|
const submissionsData = (okData(mySubmissions) as any)?.submissions || [];
|
||||||
|
|
||||||
|
const approvedSubmissions = submissionsData
|
||||||
|
.filter(
|
||||||
|
(submission: StoreSubmission) =>
|
||||||
|
submission.agent_id === agentId && submission.status === "APPROVED",
|
||||||
|
)
|
||||||
|
.sort(
|
||||||
|
(a: StoreSubmission, b: StoreSubmission) =>
|
||||||
|
b.agent_version - a.agent_version,
|
||||||
|
);
|
||||||
|
|
||||||
|
return approvedSubmissions[0] || null;
|
||||||
|
};
|
||||||
|
|
||||||
const handleAgentClick = (
|
const handleAgentClick = (
|
||||||
_: string,
|
_: string,
|
||||||
@@ -74,16 +143,42 @@ export function useAgentSelectStep({
|
|||||||
(agent) => agent.id === selectedAgentId,
|
(agent) => agent.id === selectedAgentId,
|
||||||
);
|
);
|
||||||
if (selectedAgent) {
|
if (selectedAgent) {
|
||||||
onNext(selectedAgentId, selectedAgentVersion, {
|
// Get published submission data for pre-filling if this is an update
|
||||||
name: selectedAgent.name,
|
const publishedSubmissionData = selectedAgent.isMarketplaceUpdate
|
||||||
description: selectedAgent.description,
|
? getPublishedSubmissionData(selectedAgentId)
|
||||||
imageSrc: selectedAgent.imageSrc,
|
: undefined;
|
||||||
recommendedScheduleCron: selectedAgent.recommendedScheduleCron,
|
|
||||||
});
|
onNext(
|
||||||
|
selectedAgentId,
|
||||||
|
selectedAgentVersion,
|
||||||
|
{
|
||||||
|
name: selectedAgent.name,
|
||||||
|
description: selectedAgent.description,
|
||||||
|
imageSrc: selectedAgent.imageSrc,
|
||||||
|
recommendedScheduleCron: selectedAgent.recommendedScheduleCron,
|
||||||
|
},
|
||||||
|
publishedSubmissionData,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Helper to get published version for an agent
|
||||||
|
const getPublishedVersion = (agentId: string): number | undefined => {
|
||||||
|
const submissionsData = (okData(mySubmissions) as any)?.submissions || [];
|
||||||
|
|
||||||
|
return submissionsData
|
||||||
|
.filter(
|
||||||
|
(s: StoreSubmission) =>
|
||||||
|
s.status === "APPROVED" && s.agent_id === agentId,
|
||||||
|
)
|
||||||
|
.reduce(
|
||||||
|
(max: number | undefined, s: StoreSubmission) =>
|
||||||
|
max === undefined || s.agent_version > max ? s.agent_version : max,
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// Data
|
// Data
|
||||||
agents,
|
agents,
|
||||||
@@ -94,6 +189,9 @@ export function useAgentSelectStep({
|
|||||||
// Handlers
|
// Handlers
|
||||||
handleAgentClick,
|
handleAgentClick,
|
||||||
handleNext,
|
handleNext,
|
||||||
|
// Utils
|
||||||
|
getPublishedSubmissionData,
|
||||||
|
getPublishedVersion,
|
||||||
// Computed
|
// Computed
|
||||||
isNextDisabled: !selectedAgentId || !selectedAgentVersion,
|
isNextDisabled: !selectedAgentId || !selectedAgentVersion,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,4 +8,8 @@ export const emptyModalState = {
|
|||||||
category: "",
|
category: "",
|
||||||
description: "",
|
description: "",
|
||||||
recommendedScheduleCron: "",
|
recommendedScheduleCron: "",
|
||||||
|
instructions: "",
|
||||||
|
agentOutputDemo: "",
|
||||||
|
changesSummary: "",
|
||||||
|
additionalImages: [],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,12 @@ import { useCallback, useEffect, useState } from "react";
|
|||||||
import { PublishAgentInfoInitialData } from "./components/AgentInfoStep/helpers";
|
import { PublishAgentInfoInitialData } from "./components/AgentInfoStep/helpers";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { emptyModalState } from "./helpers";
|
import { emptyModalState } from "./helpers";
|
||||||
|
import {
|
||||||
|
useGetV2GetMyAgents,
|
||||||
|
useGetV2ListMySubmissions,
|
||||||
|
} from "@/app/api/__generated__/endpoints/store/store";
|
||||||
|
import { okData } from "@/app/api/helpers";
|
||||||
|
import type { MyAgent } from "@/app/api/__generated__/models/myAgent";
|
||||||
|
|
||||||
const defaultTargetState: PublishState = {
|
const defaultTargetState: PublishState = {
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
@@ -22,9 +28,16 @@ export interface Props {
|
|||||||
trigger?: React.ReactNode;
|
trigger?: React.ReactNode;
|
||||||
targetState?: PublishState;
|
targetState?: PublishState;
|
||||||
onStateChange?: (state: PublishState) => void;
|
onStateChange?: (state: PublishState) => void;
|
||||||
|
preSelectedAgentId?: string;
|
||||||
|
preSelectedAgentVersion?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function usePublishAgentModal({ targetState, onStateChange }: Props) {
|
export function usePublishAgentModal({
|
||||||
|
targetState,
|
||||||
|
onStateChange,
|
||||||
|
preSelectedAgentId,
|
||||||
|
preSelectedAgentVersion,
|
||||||
|
}: Props) {
|
||||||
const [currentState, setCurrentState] = useState<PublishState>(
|
const [currentState, setCurrentState] = useState<PublishState>(
|
||||||
targetState || defaultTargetState,
|
targetState || defaultTargetState,
|
||||||
);
|
);
|
||||||
@@ -42,14 +55,20 @@ export function usePublishAgentModal({ targetState, onStateChange }: Props) {
|
|||||||
|
|
||||||
const [_, setSelectedAgent] = useState<string | null>(null);
|
const [_, setSelectedAgent] = useState<string | null>(null);
|
||||||
|
|
||||||
const [selectedAgentId, setSelectedAgentId] = useState<string | null>(null);
|
const [selectedAgentId, setSelectedAgentId] = useState<string | null>(
|
||||||
|
preSelectedAgentId || null,
|
||||||
|
);
|
||||||
|
|
||||||
const [selectedAgentVersion, setSelectedAgentVersion] = useState<
|
const [selectedAgentVersion, setSelectedAgentVersion] = useState<
|
||||||
number | null
|
number | null
|
||||||
>(null);
|
>(preSelectedAgentVersion || null);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
// Fetch agent data for pre-populating form when agent is pre-selected
|
||||||
|
const { data: myAgents } = useGetV2GetMyAgents();
|
||||||
|
const { data: mySubmissions } = useGetV2ListMySubmissions();
|
||||||
|
|
||||||
// Sync currentState with targetState when it changes from outside
|
// Sync currentState with targetState when it changes from outside
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (targetState) {
|
if (targetState) {
|
||||||
@@ -60,13 +79,90 @@ export function usePublishAgentModal({ targetState, onStateChange }: Props) {
|
|||||||
// Reset internal state when modal opens
|
// Reset internal state when modal opens
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!targetState) return;
|
if (!targetState) return;
|
||||||
if (targetState.isOpen && targetState.step === "select") {
|
if (targetState.isOpen) {
|
||||||
setSelectedAgent(null);
|
setSelectedAgent(null);
|
||||||
setSelectedAgentId(null);
|
setSelectedAgentId(preSelectedAgentId || null);
|
||||||
setSelectedAgentVersion(null);
|
setSelectedAgentVersion(preSelectedAgentVersion || null);
|
||||||
setInitialData(emptyModalState);
|
setInitialData(emptyModalState);
|
||||||
}
|
}
|
||||||
}, [targetState]);
|
}, [targetState, preSelectedAgentId, preSelectedAgentVersion]);
|
||||||
|
|
||||||
|
// Pre-populate form data when modal opens with info step and pre-selected agent
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
!targetState?.isOpen ||
|
||||||
|
targetState.step !== "info" ||
|
||||||
|
!preSelectedAgentId ||
|
||||||
|
!preSelectedAgentVersion
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
const agentsData = okData(myAgents) as any;
|
||||||
|
const submissionsData = okData(mySubmissions) as any;
|
||||||
|
|
||||||
|
if (!agentsData || !submissionsData) return;
|
||||||
|
|
||||||
|
// Find the agent data
|
||||||
|
const agent = agentsData.agents?.find(
|
||||||
|
(a: MyAgent) => a.agent_id === preSelectedAgentId,
|
||||||
|
);
|
||||||
|
if (!agent) return;
|
||||||
|
|
||||||
|
// Find published submission data for this agent (for updates)
|
||||||
|
const publishedSubmissionData = submissionsData.submissions
|
||||||
|
?.filter(
|
||||||
|
(s: StoreSubmission) =>
|
||||||
|
s.status === "APPROVED" && s.agent_id === preSelectedAgentId,
|
||||||
|
)
|
||||||
|
.sort(
|
||||||
|
(a: StoreSubmission, b: StoreSubmission) =>
|
||||||
|
b.agent_version - a.agent_version,
|
||||||
|
)[0];
|
||||||
|
|
||||||
|
// Populate initial data (same logic as handleNextFromSelect)
|
||||||
|
const initialFormData: PublishAgentInfoInitialData = publishedSubmissionData
|
||||||
|
? {
|
||||||
|
agent_id: preSelectedAgentId,
|
||||||
|
title: publishedSubmissionData.name,
|
||||||
|
subheader: publishedSubmissionData.sub_heading || "",
|
||||||
|
description: publishedSubmissionData.description,
|
||||||
|
instructions: publishedSubmissionData.instructions || "",
|
||||||
|
youtubeLink: publishedSubmissionData.video_url || "",
|
||||||
|
agentOutputDemo: publishedSubmissionData.agent_output_demo_url || "",
|
||||||
|
additionalImages: [
|
||||||
|
...new Set(publishedSubmissionData.image_urls || []),
|
||||||
|
].filter(Boolean) as string[],
|
||||||
|
category: publishedSubmissionData.categories?.[0] || "",
|
||||||
|
thumbnailSrc: agent.agent_image || "https://picsum.photos/300/200",
|
||||||
|
slug: publishedSubmissionData.slug,
|
||||||
|
recommendedScheduleCron: agent.recommended_schedule_cron || "",
|
||||||
|
changesSummary: "", // Empty for user to fill in what changed
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
...emptyModalState,
|
||||||
|
agent_id: preSelectedAgentId,
|
||||||
|
title: agent.agent_name,
|
||||||
|
description: agent.description || "",
|
||||||
|
thumbnailSrc: agent.agent_image || "https://picsum.photos/300/200",
|
||||||
|
slug: agent.agent_name.replace(/ /g, "-"),
|
||||||
|
recommendedScheduleCron: agent.recommended_schedule_cron || "",
|
||||||
|
};
|
||||||
|
|
||||||
|
setInitialData(initialFormData);
|
||||||
|
|
||||||
|
// Update the state with the submission data if this is an update
|
||||||
|
if (publishedSubmissionData) {
|
||||||
|
setCurrentState((prevState) => ({
|
||||||
|
...prevState,
|
||||||
|
submissionData: publishedSubmissionData,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
targetState,
|
||||||
|
preSelectedAgentId,
|
||||||
|
preSelectedAgentVersion,
|
||||||
|
myAgents,
|
||||||
|
mySubmissions,
|
||||||
|
]);
|
||||||
|
|
||||||
function handleClose() {
|
function handleClose() {
|
||||||
// Reset all internal state
|
// Reset all internal state
|
||||||
@@ -97,20 +193,43 @@ export function usePublishAgentModal({ targetState, onStateChange }: Props) {
|
|||||||
imageSrc: string;
|
imageSrc: string;
|
||||||
recommendedScheduleCron: string | null;
|
recommendedScheduleCron: string | null;
|
||||||
},
|
},
|
||||||
|
publishedSubmissionData?: StoreSubmission | null,
|
||||||
) {
|
) {
|
||||||
setInitialData({
|
// Pre-populate with published data if this is an update, otherwise use agent data
|
||||||
...emptyModalState,
|
const initialFormData: PublishAgentInfoInitialData = publishedSubmissionData
|
||||||
agent_id: agentId,
|
? {
|
||||||
title: agentData.name,
|
agent_id: agentId,
|
||||||
description: agentData.description,
|
title: publishedSubmissionData.name,
|
||||||
thumbnailSrc: agentData.imageSrc,
|
subheader: publishedSubmissionData.sub_heading || "",
|
||||||
slug: agentData.name.replace(/ /g, "-"),
|
description: publishedSubmissionData.description,
|
||||||
recommendedScheduleCron: agentData.recommendedScheduleCron || "",
|
instructions: publishedSubmissionData.instructions || "",
|
||||||
});
|
youtubeLink: publishedSubmissionData.video_url || "",
|
||||||
|
agentOutputDemo: publishedSubmissionData.agent_output_demo_url || "",
|
||||||
|
additionalImages: [
|
||||||
|
...new Set(publishedSubmissionData.image_urls || []),
|
||||||
|
].filter(Boolean) as string[],
|
||||||
|
category: publishedSubmissionData.categories?.[0] || "", // Take first category
|
||||||
|
thumbnailSrc: agentData.imageSrc, // Use current agent image
|
||||||
|
slug: publishedSubmissionData.slug,
|
||||||
|
recommendedScheduleCron: agentData.recommendedScheduleCron || "",
|
||||||
|
changesSummary: "", // Empty for user to fill in what changed
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
...emptyModalState,
|
||||||
|
agent_id: agentId,
|
||||||
|
title: agentData.name,
|
||||||
|
description: agentData.description,
|
||||||
|
thumbnailSrc: agentData.imageSrc,
|
||||||
|
slug: agentData.name.replace(/ /g, "-"),
|
||||||
|
recommendedScheduleCron: agentData.recommendedScheduleCron || "",
|
||||||
|
};
|
||||||
|
|
||||||
|
setInitialData(initialFormData);
|
||||||
|
|
||||||
updateState({
|
updateState({
|
||||||
...currentState,
|
...currentState,
|
||||||
step: "info",
|
step: "info",
|
||||||
|
submissionData: publishedSubmissionData || null,
|
||||||
});
|
});
|
||||||
|
|
||||||
setSelectedAgentId(agentId);
|
setSelectedAgentId(agentId);
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/**
|
||||||
|
* Marketplace-specific helper functions that can be reused across different marketplace screens
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the latest marketplace version from agent graph versions
|
||||||
|
*/
|
||||||
|
export function getLatestMarketplaceVersion(
|
||||||
|
agentGraphVersions?: string[],
|
||||||
|
): number | undefined {
|
||||||
|
if (!agentGraphVersions?.length) return undefined;
|
||||||
|
|
||||||
|
return Math.max(...agentGraphVersions.map((v: string) => parseInt(v, 10)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the current user is the creator of the agent
|
||||||
|
*/
|
||||||
|
export function isUserCreator(
|
||||||
|
creator: string,
|
||||||
|
currentUser: { email?: string } | null,
|
||||||
|
): boolean {
|
||||||
|
if (!currentUser?.email) return false;
|
||||||
|
|
||||||
|
const userHandle = currentUser.email.split("@")[0]?.toLowerCase() || "";
|
||||||
|
return creator.toLowerCase().includes(userHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate update status for an agent
|
||||||
|
*/
|
||||||
|
export function calculateUpdateStatus({
|
||||||
|
latestMarketplaceVersion,
|
||||||
|
currentVersion,
|
||||||
|
isUserCreator,
|
||||||
|
isAgentAddedToLibrary,
|
||||||
|
}: {
|
||||||
|
latestMarketplaceVersion?: number;
|
||||||
|
currentVersion: number;
|
||||||
|
isUserCreator: boolean;
|
||||||
|
isAgentAddedToLibrary: boolean;
|
||||||
|
}) {
|
||||||
|
if (!latestMarketplaceVersion) {
|
||||||
|
return { hasUpdate: false, hasUnpublishedChanges: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasUnpublishedChanges =
|
||||||
|
isUserCreator &&
|
||||||
|
isAgentAddedToLibrary &&
|
||||||
|
currentVersion > latestMarketplaceVersion;
|
||||||
|
|
||||||
|
const hasUpdate =
|
||||||
|
isAgentAddedToLibrary &&
|
||||||
|
!isUserCreator &&
|
||||||
|
latestMarketplaceVersion > currentVersion;
|
||||||
|
|
||||||
|
return { hasUpdate, hasUnpublishedChanges };
|
||||||
|
}
|
||||||
@@ -19,7 +19,7 @@ export function MobileNavbarMenuItem({
|
|||||||
onClick,
|
onClick,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const content = (
|
const content = (
|
||||||
<div className="inline-flex w-full items-center justify-start gap-4 hover:rounded hover:bg-[#e0e0e0]">
|
<div className="inline-flex w-full items-center justify-start gap-4 py-2 hover:rounded hover:bg-[#e0e0e0]">
|
||||||
{getAccountMenuOptionIcon(icon)}
|
{getAccountMenuOptionIcon(icon)}
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import * as React from "react";
|
|||||||
interface BreadcrumbItem {
|
interface BreadcrumbItem {
|
||||||
name: string;
|
name: string;
|
||||||
link: string;
|
link: string;
|
||||||
|
testId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -19,6 +20,7 @@ export function Breadcrumbs({ items }: Props) {
|
|||||||
<Link
|
<Link
|
||||||
href={item.link}
|
href={item.link}
|
||||||
className="text-[0.75rem] font-[400] text-zinc-600 transition-colors hover:text-zinc-900 hover:no-underline"
|
className="text-[0.75rem] font-[400] text-zinc-600 transition-colors hover:text-zinc-900 hover:no-underline"
|
||||||
|
data-testid={item.testId}
|
||||||
>
|
>
|
||||||
{item.name}
|
{item.name}
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ const commonStyles = {
|
|||||||
title: "font-poppins text-md md:text-lg leading-none",
|
title: "font-poppins text-md md:text-lg leading-none",
|
||||||
overlay:
|
overlay:
|
||||||
"fixed inset-0 z-50 bg-stone-500/20 dark:bg-black/50 backdrop-blur-md animate-fade-in",
|
"fixed inset-0 z-50 bg-stone-500/20 dark:bg-black/50 backdrop-blur-md animate-fade-in",
|
||||||
content:
|
content: "bg-white p-6 fixed rounded-2xlarge flex flex-col z-50 w-full",
|
||||||
"overflow-y-hidden bg-white p-6 fixed rounded-2xlarge flex flex-col z-50 w-full",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Modal specific styles
|
// Modal specific styles
|
||||||
|
|||||||
@@ -9,16 +9,20 @@ import ReactMarkdown from "react-markdown";
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
description?: string;
|
description?: string;
|
||||||
|
iconSize?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function InformationTooltip({ description }: Props) {
|
export function InformationTooltip({ description, iconSize = 24 }: Props) {
|
||||||
if (!description) return null;
|
if (!description) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TooltipProvider delayDuration={400}>
|
<TooltipProvider delayDuration={400}>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Info className="rounded-full p-1 hover:bg-slate-50" size={24} />
|
<Info
|
||||||
|
className="rounded-full p-1 hover:bg-slate-50"
|
||||||
|
size={iconSize}
|
||||||
|
/>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<ReactMarkdown
|
<ReactMarkdown
|
||||||
|
|||||||
@@ -0,0 +1,437 @@
|
|||||||
|
import type { Meta, StoryObj } from "@storybook/nextjs";
|
||||||
|
import {
|
||||||
|
ScrollableTabs,
|
||||||
|
ScrollableTabsContent,
|
||||||
|
ScrollableTabsList,
|
||||||
|
ScrollableTabsTrigger,
|
||||||
|
} from "./ScrollableTabs";
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: "Molecules/ScrollableTabs",
|
||||||
|
component: ScrollableTabs,
|
||||||
|
parameters: {
|
||||||
|
layout: "fullscreen",
|
||||||
|
},
|
||||||
|
tags: ["autodocs"],
|
||||||
|
argTypes: {},
|
||||||
|
} satisfies Meta<typeof ScrollableTabs>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
function ScrollableTabsDemo() {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-8 p-8">
|
||||||
|
<h2 className="text-2xl font-bold">ScrollableTabs Examples</h2>
|
||||||
|
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<h3 className="mb-4 text-lg font-semibold">
|
||||||
|
Short Content (Tabs Hidden)
|
||||||
|
</h3>
|
||||||
|
<div className="h-[300px] overflow-y-auto border border-zinc-200">
|
||||||
|
<ScrollableTabs defaultValue="tab1" className="h-full">
|
||||||
|
<ScrollableTabsList>
|
||||||
|
<ScrollableTabsTrigger value="tab1">
|
||||||
|
Account
|
||||||
|
</ScrollableTabsTrigger>
|
||||||
|
<ScrollableTabsTrigger value="tab2">
|
||||||
|
Password
|
||||||
|
</ScrollableTabsTrigger>
|
||||||
|
<ScrollableTabsTrigger value="tab3">
|
||||||
|
Settings
|
||||||
|
</ScrollableTabsTrigger>
|
||||||
|
</ScrollableTabsList>
|
||||||
|
<ScrollableTabsContent value="tab1">
|
||||||
|
<div className="p-4 text-sm">
|
||||||
|
Make changes to your account here. Click save when you're
|
||||||
|
done.
|
||||||
|
</div>
|
||||||
|
</ScrollableTabsContent>
|
||||||
|
<ScrollableTabsContent value="tab2">
|
||||||
|
<div className="p-4 text-sm">
|
||||||
|
Change your password here. After saving, you'll be logged
|
||||||
|
out.
|
||||||
|
</div>
|
||||||
|
</ScrollableTabsContent>
|
||||||
|
<ScrollableTabsContent value="tab3">
|
||||||
|
<div className="p-4 text-sm">
|
||||||
|
Update your preferences and settings here.
|
||||||
|
</div>
|
||||||
|
</ScrollableTabsContent>
|
||||||
|
</ScrollableTabs>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3 className="mb-4 text-lg font-semibold">
|
||||||
|
Long Content (Tabs Visible)
|
||||||
|
</h3>
|
||||||
|
<div className="h-[400px] overflow-y-auto border border-zinc-200">
|
||||||
|
<ScrollableTabs defaultValue="tab1" className="h-full">
|
||||||
|
<ScrollableTabsList>
|
||||||
|
<ScrollableTabsTrigger value="tab1">
|
||||||
|
Account
|
||||||
|
</ScrollableTabsTrigger>
|
||||||
|
<ScrollableTabsTrigger value="tab2">
|
||||||
|
Password
|
||||||
|
</ScrollableTabsTrigger>
|
||||||
|
<ScrollableTabsTrigger value="tab3">
|
||||||
|
Settings
|
||||||
|
</ScrollableTabsTrigger>
|
||||||
|
</ScrollableTabsList>
|
||||||
|
<ScrollableTabsContent value="tab1">
|
||||||
|
<div className="p-8 text-sm">
|
||||||
|
<h4 className="mb-4 text-lg font-semibold">
|
||||||
|
Account Settings
|
||||||
|
</h4>
|
||||||
|
<p className="mb-4">
|
||||||
|
Make changes to your account here. Click save when
|
||||||
|
you're done.
|
||||||
|
</p>
|
||||||
|
<p className="mb-4">
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed
|
||||||
|
do eiusmod tempor incididunt ut labore et dolore magna
|
||||||
|
aliqua. Ut enim ad minim veniam, quis nostrud exercitation
|
||||||
|
ullamco laboris.
|
||||||
|
</p>
|
||||||
|
<p className="mb-4">
|
||||||
|
Duis aute irure dolor in reprehenderit in voluptate velit
|
||||||
|
esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
|
||||||
|
occaecat cupidatat non proident.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Sed ut perspiciatis unde omnis iste natus error sit
|
||||||
|
voluptatem accusantium doloremque laudantium, totam rem
|
||||||
|
aperiam.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</ScrollableTabsContent>
|
||||||
|
<ScrollableTabsContent value="tab2">
|
||||||
|
<div className="p-8 text-sm">
|
||||||
|
<h4 className="mb-4 text-lg font-semibold">
|
||||||
|
Password Settings
|
||||||
|
</h4>
|
||||||
|
<p className="mb-4">
|
||||||
|
Change your password here. After saving, you'll be
|
||||||
|
logged out.
|
||||||
|
</p>
|
||||||
|
<p className="mb-4">
|
||||||
|
At vero eos et accusamus et iusto odio dignissimos ducimus
|
||||||
|
qui blanditiis praesentium voluptatum deleniti atque
|
||||||
|
corrupti quos dolores et quas molestias excepturi sint
|
||||||
|
occaecati cupiditate.
|
||||||
|
</p>
|
||||||
|
<p className="mb-4">
|
||||||
|
Et harum quidem rerum facilis est et expedita distinctio.
|
||||||
|
Nam libero tempore, cum soluta nobis est eligendi optio
|
||||||
|
cumque nihil impedit quo minus.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Temporibus autem quibusdam et aut officiis debitis aut rerum
|
||||||
|
necessitatibus saepe eveniet ut et voluptates repudiandae
|
||||||
|
sint.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</ScrollableTabsContent>
|
||||||
|
<ScrollableTabsContent value="tab3">
|
||||||
|
<div className="p-8 text-sm">
|
||||||
|
<h4 className="mb-4 text-lg font-semibold">
|
||||||
|
General Settings
|
||||||
|
</h4>
|
||||||
|
<p className="mb-4">
|
||||||
|
Update your preferences and settings here.
|
||||||
|
</p>
|
||||||
|
<p className="mb-4">
|
||||||
|
Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut
|
||||||
|
odit aut fugit, sed quia consequuntur magni dolores eos qui
|
||||||
|
ratione voluptatem sequi nesciunt.
|
||||||
|
</p>
|
||||||
|
<p className="mb-4">
|
||||||
|
Neque porro quisquam est, qui dolorem ipsum quia dolor sit
|
||||||
|
amet, consectetur, adipisci velit, sed quia non numquam eius
|
||||||
|
modi tempora incidunt ut labore et dolore magnam aliquam
|
||||||
|
quaerat voluptatem.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Ut enim ad minima veniam, quis nostrum exercitationem ullam
|
||||||
|
corporis suscipit laboriosam, nisi ut aliquid ex ea commodi
|
||||||
|
consequatur.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</ScrollableTabsContent>
|
||||||
|
</ScrollableTabs>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3 className="mb-4 text-lg font-semibold">Many Tabs</h3>
|
||||||
|
<div className="h-[500px] overflow-y-auto border border-zinc-200">
|
||||||
|
<ScrollableTabs defaultValue="overview" className="h-full">
|
||||||
|
<ScrollableTabsList>
|
||||||
|
<ScrollableTabsTrigger value="overview">
|
||||||
|
Overview
|
||||||
|
</ScrollableTabsTrigger>
|
||||||
|
<ScrollableTabsTrigger value="analytics">
|
||||||
|
Analytics
|
||||||
|
</ScrollableTabsTrigger>
|
||||||
|
<ScrollableTabsTrigger value="reports">
|
||||||
|
Reports
|
||||||
|
</ScrollableTabsTrigger>
|
||||||
|
<ScrollableTabsTrigger value="notifications">
|
||||||
|
Notifications
|
||||||
|
</ScrollableTabsTrigger>
|
||||||
|
<ScrollableTabsTrigger value="integrations">
|
||||||
|
Integrations
|
||||||
|
</ScrollableTabsTrigger>
|
||||||
|
<ScrollableTabsTrigger value="billing">
|
||||||
|
Billing
|
||||||
|
</ScrollableTabsTrigger>
|
||||||
|
</ScrollableTabsList>
|
||||||
|
<ScrollableTabsContent value="overview">
|
||||||
|
<div className="p-8 text-sm">
|
||||||
|
<h4 className="mb-4 text-lg font-semibold">
|
||||||
|
Dashboard Overview
|
||||||
|
</h4>
|
||||||
|
<p className="mb-4">
|
||||||
|
Dashboard overview with key metrics and recent activity.
|
||||||
|
</p>
|
||||||
|
<p className="mb-4">
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed
|
||||||
|
do eiusmod tempor incididunt ut labore et dolore magna
|
||||||
|
aliqua.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Ut enim ad minim veniam, quis nostrud exercitation ullamco
|
||||||
|
laboris nisi ut aliquip ex ea commodo consequat.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</ScrollableTabsContent>
|
||||||
|
<ScrollableTabsContent value="analytics">
|
||||||
|
<div className="p-8 text-sm">
|
||||||
|
<h4 className="mb-4 text-lg font-semibold">Analytics</h4>
|
||||||
|
<p className="mb-4">
|
||||||
|
Detailed analytics and performance metrics.
|
||||||
|
</p>
|
||||||
|
<p className="mb-4">
|
||||||
|
Duis aute irure dolor in reprehenderit in voluptate velit
|
||||||
|
esse cillum dolore eu fugiat nulla pariatur.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Excepteur sint occaecat cupidatat non proident, sunt in
|
||||||
|
culpa qui officia deserunt mollit anim id est laborum.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</ScrollableTabsContent>
|
||||||
|
<ScrollableTabsContent value="reports">
|
||||||
|
<div className="p-8 text-sm">
|
||||||
|
<h4 className="mb-4 text-lg font-semibold">Reports</h4>
|
||||||
|
<p className="mb-4">
|
||||||
|
Generate and view reports for your account.
|
||||||
|
</p>
|
||||||
|
<p className="mb-4">
|
||||||
|
Sed ut perspiciatis unde omnis iste natus error sit
|
||||||
|
voluptatem accusantium doloremque laudantium.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Totam rem aperiam, eaque ipsa quae ab illo inventore
|
||||||
|
veritatis et quasi architecto beatae vitae dicta sunt
|
||||||
|
explicabo.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</ScrollableTabsContent>
|
||||||
|
<ScrollableTabsContent value="notifications">
|
||||||
|
<div className="p-8 text-sm">
|
||||||
|
<h4 className="mb-4 text-lg font-semibold">Notifications</h4>
|
||||||
|
<p className="mb-4">Manage your notification preferences.</p>
|
||||||
|
<p className="mb-4">
|
||||||
|
Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut
|
||||||
|
odit aut fugit.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Sed quia consequuntur magni dolores eos qui ratione
|
||||||
|
voluptatem sequi nesciunt.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</ScrollableTabsContent>
|
||||||
|
<ScrollableTabsContent value="integrations">
|
||||||
|
<div className="p-8 text-sm">
|
||||||
|
<h4 className="mb-4 text-lg font-semibold">Integrations</h4>
|
||||||
|
<p className="mb-4">
|
||||||
|
Connect and manage third-party integrations.
|
||||||
|
</p>
|
||||||
|
<p className="mb-4">
|
||||||
|
Neque porro quisquam est, qui dolorem ipsum quia dolor sit
|
||||||
|
amet.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Consectetur, adipisci velit, sed quia non numquam eius modi
|
||||||
|
tempora incidunt.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</ScrollableTabsContent>
|
||||||
|
<ScrollableTabsContent value="billing">
|
||||||
|
<div className="p-8 text-sm">
|
||||||
|
<h4 className="mb-4 text-lg font-semibold">Billing</h4>
|
||||||
|
<p className="mb-4">
|
||||||
|
View and manage your billing information.
|
||||||
|
</p>
|
||||||
|
<p className="mb-4">
|
||||||
|
Ut enim ad minima veniam, quis nostrum exercitationem ullam
|
||||||
|
corporis suscipit laboriosam.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Nisi ut aliquid ex ea commodi consequatur? Quis autem vel
|
||||||
|
eum iure reprehenderit qui in ea voluptate velit esse.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</ScrollableTabsContent>
|
||||||
|
</ScrollableTabs>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Default = {
|
||||||
|
render: () => <ScrollableTabsDemo />,
|
||||||
|
} satisfies Story;
|
||||||
|
|
||||||
|
export const ShortContent = {
|
||||||
|
render: () => (
|
||||||
|
<div className="p-8">
|
||||||
|
<div className="h-[200px] overflow-y-auto border border-zinc-200">
|
||||||
|
<ScrollableTabs defaultValue="account" className="h-full">
|
||||||
|
<ScrollableTabsList>
|
||||||
|
<ScrollableTabsTrigger value="account">
|
||||||
|
Account
|
||||||
|
</ScrollableTabsTrigger>
|
||||||
|
<ScrollableTabsTrigger value="password">
|
||||||
|
Password
|
||||||
|
</ScrollableTabsTrigger>
|
||||||
|
</ScrollableTabsList>
|
||||||
|
<ScrollableTabsContent value="account">
|
||||||
|
<div className="p-4 text-sm">
|
||||||
|
Make changes to your account here. Click save when you're
|
||||||
|
done.
|
||||||
|
</div>
|
||||||
|
</ScrollableTabsContent>
|
||||||
|
<ScrollableTabsContent value="password">
|
||||||
|
<div className="p-4 text-sm">
|
||||||
|
Change your password here. After saving, you'll be logged
|
||||||
|
out.
|
||||||
|
</div>
|
||||||
|
</ScrollableTabsContent>
|
||||||
|
</ScrollableTabs>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
} satisfies Story;
|
||||||
|
|
||||||
|
export const LongContent = {
|
||||||
|
render: () => (
|
||||||
|
<div className="p-8">
|
||||||
|
<div className="h-[600px] overflow-y-auto border border-zinc-200">
|
||||||
|
<ScrollableTabs defaultValue="tab1" className="h-full">
|
||||||
|
<ScrollableTabsList>
|
||||||
|
<ScrollableTabsTrigger value="tab1">Account</ScrollableTabsTrigger>
|
||||||
|
<ScrollableTabsTrigger value="tab2">Password</ScrollableTabsTrigger>
|
||||||
|
<ScrollableTabsTrigger value="tab3">Settings</ScrollableTabsTrigger>
|
||||||
|
</ScrollableTabsList>
|
||||||
|
<ScrollableTabsContent value="tab1">
|
||||||
|
<div className="p-8 text-sm">
|
||||||
|
<h4 className="mb-4 text-lg font-semibold">Account Settings</h4>
|
||||||
|
<p className="mb-4">
|
||||||
|
Make changes to your account here. Click save when you're
|
||||||
|
done.
|
||||||
|
</p>
|
||||||
|
<p className="mb-4">
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do
|
||||||
|
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
|
||||||
|
enim ad minim veniam, quis nostrud exercitation ullamco laboris
|
||||||
|
nisi ut aliquip ex ea commodo consequat.
|
||||||
|
</p>
|
||||||
|
<p className="mb-4">
|
||||||
|
Duis aute irure dolor in reprehenderit in voluptate velit esse
|
||||||
|
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
|
||||||
|
cupidatat non proident, sunt in culpa qui officia deserunt
|
||||||
|
mollit anim id est laborum.
|
||||||
|
</p>
|
||||||
|
<p className="mb-4">
|
||||||
|
Sed ut perspiciatis unde omnis iste natus error sit voluptatem
|
||||||
|
accusantium doloremque laudantium, totam rem aperiam, eaque ipsa
|
||||||
|
quae ab illo inventore veritatis et quasi architecto beatae
|
||||||
|
vitae dicta sunt explicabo.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit
|
||||||
|
aut fugit, sed quia consequuntur magni dolores eos qui ratione
|
||||||
|
voluptatem sequi nesciunt.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</ScrollableTabsContent>
|
||||||
|
<ScrollableTabsContent value="tab2">
|
||||||
|
<div className="p-8 text-sm">
|
||||||
|
<h4 className="mb-4 text-lg font-semibold">Password Settings</h4>
|
||||||
|
<p className="mb-4">
|
||||||
|
Change your password here. After saving, you'll be logged
|
||||||
|
out.
|
||||||
|
</p>
|
||||||
|
<p className="mb-4">
|
||||||
|
At vero eos et accusamus et iusto odio dignissimos ducimus qui
|
||||||
|
blanditiis praesentium voluptatum deleniti atque corrupti quos
|
||||||
|
dolores et quas molestias excepturi sint occaecati cupiditate
|
||||||
|
non provident.
|
||||||
|
</p>
|
||||||
|
<p className="mb-4">
|
||||||
|
Similique sunt in culpa qui officia deserunt mollitia animi, id
|
||||||
|
est laborum et dolorum fuga. Et harum quidem rerum facilis est
|
||||||
|
et expedita distinctio.
|
||||||
|
</p>
|
||||||
|
<p className="mb-4">
|
||||||
|
Nam libero tempore, cum soluta nobis est eligendi optio cumque
|
||||||
|
nihil impedit quo minus id quod maxime placeat facere possimus,
|
||||||
|
omnis voluptas assumenda est, omnis dolor repellendus.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Temporibus autem quibusdam et aut officiis debitis aut rerum
|
||||||
|
necessitatibus saepe eveniet ut et voluptates repudiandae sint
|
||||||
|
et molestiae non recusandae.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</ScrollableTabsContent>
|
||||||
|
<ScrollableTabsContent value="tab3">
|
||||||
|
<div className="p-8 text-sm">
|
||||||
|
<h4 className="mb-4 text-lg font-semibold">General Settings</h4>
|
||||||
|
<p className="mb-4">Update your preferences and settings here.</p>
|
||||||
|
<p className="mb-4">
|
||||||
|
Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet,
|
||||||
|
consectetur, adipisci velit, sed quia non numquam eius modi
|
||||||
|
tempora incidunt ut labore et dolore magnam aliquam quaerat
|
||||||
|
voluptatem.
|
||||||
|
</p>
|
||||||
|
<p className="mb-4">
|
||||||
|
Ut enim ad minima veniam, quis nostrum exercitationem ullam
|
||||||
|
corporis suscipit laboriosam, nisi ut aliquid ex ea commodi
|
||||||
|
consequatur? Quis autem vel eum iure reprehenderit qui in ea
|
||||||
|
voluptate velit esse quam nihil molestiae consequatur.
|
||||||
|
</p>
|
||||||
|
<p className="mb-4">
|
||||||
|
Vel illum qui dolorem eum fugiat quo voluptas nulla pariatur? At
|
||||||
|
vero eos et accusamus et iusto odio dignissimos ducimus qui
|
||||||
|
blanditiis praesentium voluptatum deleniti atque corrupti quos
|
||||||
|
dolores.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Et quas molestias excepturi sint occaecati cupiditate non
|
||||||
|
provident, similique sunt in culpa qui officia deserunt mollitia
|
||||||
|
animi, id est laborum et dolorum fuga.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</ScrollableTabsContent>
|
||||||
|
</ScrollableTabs>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
} satisfies Story;
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { Children } from "react";
|
||||||
|
import { ScrollableTabsContent } from "./components/ScrollableTabsContent";
|
||||||
|
import { ScrollableTabsList } from "./components/ScrollableTabsList";
|
||||||
|
import { ScrollableTabsTrigger } from "./components/ScrollableTabsTrigger";
|
||||||
|
import { ScrollableTabsContext } from "./context";
|
||||||
|
import { findContentElements, findListElement } from "./helpers";
|
||||||
|
import { useScrollableTabsInternal } from "./useScrollableTabs";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
children?: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
defaultValue?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ScrollableTabs({ children, className, defaultValue }: Props) {
|
||||||
|
const {
|
||||||
|
activeValue,
|
||||||
|
setActiveValue,
|
||||||
|
registerContent,
|
||||||
|
scrollToSection,
|
||||||
|
scrollContainer,
|
||||||
|
contentContainerRef,
|
||||||
|
} = useScrollableTabsInternal({ defaultValue });
|
||||||
|
|
||||||
|
const childrenArray = Children.toArray(children);
|
||||||
|
const listElement = findListElement(childrenArray);
|
||||||
|
const contentElements = findContentElements(childrenArray);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ScrollableTabsContext.Provider
|
||||||
|
value={{
|
||||||
|
activeValue,
|
||||||
|
setActiveValue,
|
||||||
|
registerContent,
|
||||||
|
scrollToSection,
|
||||||
|
scrollContainer,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className={cn("relative flex flex-col", className)}>
|
||||||
|
{listElement}
|
||||||
|
<div
|
||||||
|
ref={(node) => {
|
||||||
|
if (contentContainerRef) {
|
||||||
|
contentContainerRef.current = node;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="max-h-[64rem] overflow-y-auto scrollbar-thin scrollbar-track-transparent scrollbar-thumb-zinc-300 dark:scrollbar-thumb-zinc-700"
|
||||||
|
>
|
||||||
|
<div className="min-h-full pb-[200px]">{contentElements}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ScrollableTabsContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { ScrollableTabsContent, ScrollableTabsList, ScrollableTabsTrigger };
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import * as React from "react";
|
||||||
|
import { useScrollableTabs } from "../context";
|
||||||
|
|
||||||
|
interface Props extends React.HTMLAttributes<HTMLDivElement> {
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ScrollableTabsContent = React.forwardRef<HTMLDivElement, Props>(
|
||||||
|
function ScrollableTabsContent(
|
||||||
|
{ className, value, children, ...props },
|
||||||
|
ref,
|
||||||
|
) {
|
||||||
|
const { registerContent } = useScrollableTabs();
|
||||||
|
const contentRef = React.useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (contentRef.current) {
|
||||||
|
registerContent(value, contentRef.current);
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
registerContent(value, null);
|
||||||
|
};
|
||||||
|
}, [value, registerContent]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={(node) => {
|
||||||
|
if (typeof ref === "function") ref(node);
|
||||||
|
else if (ref) ref.current = node;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
contentRef.current = node;
|
||||||
|
}}
|
||||||
|
data-scrollable-tab-content
|
||||||
|
data-value={value}
|
||||||
|
className={cn("focus-visible:outline-none", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
ScrollableTabsContent.displayName = "ScrollableTabsContent";
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import * as React from "react";
|
||||||
|
import { useScrollableTabs } from "../context";
|
||||||
|
|
||||||
|
export const ScrollableTabsList = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(function ScrollableTabsList({ className, children, ...props }, ref) {
|
||||||
|
const { activeValue } = useScrollableTabs();
|
||||||
|
const [activeTabElement, setActiveTabElement] =
|
||||||
|
React.useState<HTMLElement | null>(null);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const activeButton = Array.from(
|
||||||
|
document.querySelectorAll<HTMLElement>(
|
||||||
|
'[data-scrollable-tab-trigger][data-value="' + activeValue + '"]',
|
||||||
|
),
|
||||||
|
)[0];
|
||||||
|
|
||||||
|
if (activeButton) {
|
||||||
|
setActiveTabElement(activeButton);
|
||||||
|
}
|
||||||
|
}, [activeValue]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative" ref={ref}>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"inline-flex w-full items-center justify-start border-b border-zinc-100",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
{activeTabElement && (
|
||||||
|
<div
|
||||||
|
className="transition-left transition-right absolute bottom-0 h-0.5 bg-purple-600 duration-200 ease-in-out"
|
||||||
|
style={{
|
||||||
|
left: activeTabElement.offsetLeft,
|
||||||
|
width: activeTabElement.offsetWidth,
|
||||||
|
willChange: "left, width",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
ScrollableTabsList.displayName = "ScrollableTabsList";
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import * as React from "react";
|
||||||
|
import { useScrollableTabs } from "../context";
|
||||||
|
|
||||||
|
interface Props extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ScrollableTabsTrigger = React.forwardRef<HTMLButtonElement, Props>(
|
||||||
|
function ScrollableTabsTrigger(
|
||||||
|
{ className, value, children, ...props },
|
||||||
|
ref,
|
||||||
|
) {
|
||||||
|
const { activeValue, scrollToSection } = useScrollableTabs();
|
||||||
|
const elementRef = React.useRef<HTMLButtonElement>(null);
|
||||||
|
const isActive = activeValue === value;
|
||||||
|
|
||||||
|
function handleClick(e: React.MouseEvent<HTMLButtonElement>) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
scrollToSection(value);
|
||||||
|
props.onClick?.(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
ref={(node) => {
|
||||||
|
if (typeof ref === "function") ref(node);
|
||||||
|
else if (ref) ref.current = node;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
elementRef.current = node;
|
||||||
|
}}
|
||||||
|
data-scrollable-tab-trigger
|
||||||
|
data-value={value}
|
||||||
|
onClick={handleClick}
|
||||||
|
className={cn(
|
||||||
|
"relative inline-flex items-center justify-center whitespace-nowrap px-3 py-3 font-sans text-[0.875rem] 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",
|
||||||
|
isActive && "text-purple-600",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
ScrollableTabsTrigger.displayName = "ScrollableTabsTrigger";
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import { createContext, useContext } from "react";
|
||||||
|
|
||||||
|
interface ScrollableTabsContextValue {
|
||||||
|
activeValue: string | null;
|
||||||
|
setActiveValue: React.Dispatch<React.SetStateAction<string | null>>;
|
||||||
|
registerContent: (value: string, element: HTMLElement | null) => void;
|
||||||
|
scrollToSection: (value: string) => void;
|
||||||
|
scrollContainer: HTMLElement | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ScrollableTabsContext = createContext<
|
||||||
|
ScrollableTabsContextValue | undefined
|
||||||
|
>(undefined);
|
||||||
|
|
||||||
|
export function useScrollableTabs() {
|
||||||
|
const context = useContext(ScrollableTabsContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error("useScrollableTabs must be used within a ScrollableTabs");
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
const HEADER_OFFSET = 100;
|
||||||
|
|
||||||
|
export function calculateScrollPosition(
|
||||||
|
elementRect: DOMRect,
|
||||||
|
containerRect: DOMRect,
|
||||||
|
currentScrollTop: number,
|
||||||
|
): number {
|
||||||
|
const elementTopRelativeToContainer =
|
||||||
|
elementRect.top - containerRect.top + currentScrollTop - HEADER_OFFSET;
|
||||||
|
|
||||||
|
return Math.max(0, elementTopRelativeToContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasDisplayName(
|
||||||
|
type: unknown,
|
||||||
|
displayName: string,
|
||||||
|
): type is { displayName: string } {
|
||||||
|
return (
|
||||||
|
typeof type === "object" &&
|
||||||
|
type !== null &&
|
||||||
|
"displayName" in type &&
|
||||||
|
(type as { displayName: unknown }).displayName === displayName
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findListElement(
|
||||||
|
children: React.ReactNode[],
|
||||||
|
): React.ReactElement | undefined {
|
||||||
|
return children.find(
|
||||||
|
(child) =>
|
||||||
|
React.isValidElement(child) &&
|
||||||
|
hasDisplayName(child.type, "ScrollableTabsList"),
|
||||||
|
) as React.ReactElement | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findContentElements(
|
||||||
|
children: React.ReactNode[],
|
||||||
|
): React.ReactNode[] {
|
||||||
|
return children.filter(
|
||||||
|
(child) =>
|
||||||
|
!(
|
||||||
|
React.isValidElement(child) &&
|
||||||
|
hasDisplayName(child.type, "ScrollableTabsList")
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import { useCallback, useRef, useState } from "react";
|
||||||
|
import { calculateScrollPosition } from "./helpers";
|
||||||
|
|
||||||
|
interface Args {
|
||||||
|
defaultValue?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useScrollableTabsInternal({ defaultValue }: Args) {
|
||||||
|
const [activeValue, setActiveValue] = useState<string | null>(
|
||||||
|
defaultValue || null,
|
||||||
|
);
|
||||||
|
const contentRefs = useRef<Map<string, HTMLElement>>(new Map());
|
||||||
|
const contentContainerRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
|
function registerContent(value: string, element: HTMLElement | null) {
|
||||||
|
if (element) {
|
||||||
|
contentRefs.current.set(value, element);
|
||||||
|
} else {
|
||||||
|
contentRefs.current.delete(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrollToSection(value: string) {
|
||||||
|
const element = contentRefs.current.get(value);
|
||||||
|
const scrollContainer = contentContainerRef.current;
|
||||||
|
if (!element || !scrollContainer) return;
|
||||||
|
|
||||||
|
setActiveValue(value);
|
||||||
|
|
||||||
|
const containerRect = scrollContainer.getBoundingClientRect();
|
||||||
|
const elementRect = element.getBoundingClientRect();
|
||||||
|
const currentScrollTop = scrollContainer.scrollTop;
|
||||||
|
const scrollTop = calculateScrollPosition(
|
||||||
|
elementRect,
|
||||||
|
containerRect,
|
||||||
|
currentScrollTop,
|
||||||
|
);
|
||||||
|
|
||||||
|
const maxScrollTop =
|
||||||
|
scrollContainer.scrollHeight - scrollContainer.clientHeight;
|
||||||
|
const clampedScrollTop = Math.min(Math.max(0, scrollTop), maxScrollTop);
|
||||||
|
|
||||||
|
scrollContainer.scrollTo({
|
||||||
|
top: clampedScrollTop,
|
||||||
|
behavior: "smooth",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const memoizedRegisterContent = useCallback(registerContent, []);
|
||||||
|
const memoizedScrollToSection = useCallback(scrollToSection, []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
activeValue,
|
||||||
|
setActiveValue,
|
||||||
|
registerContent: memoizedRegisterContent,
|
||||||
|
scrollToSection: memoizedScrollToSection,
|
||||||
|
scrollContainer: contentContainerRef.current,
|
||||||
|
contentContainerRef,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/atoms/Tooltip/BaseTooltip";
|
} from "@/components/atoms/Tooltip/BaseTooltip";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
import { BlockUIType } from "@/app/(platform)/build/components/types";
|
||||||
|
|
||||||
type TypeOption = {
|
type TypeOption = {
|
||||||
type: string;
|
type: string;
|
||||||
@@ -47,7 +48,14 @@ export const AnyOfField = ({
|
|||||||
onBlur,
|
onBlur,
|
||||||
onFocus,
|
onFocus,
|
||||||
}: FieldProps) => {
|
}: FieldProps) => {
|
||||||
const handleId = generateHandleId(idSchema.$id ?? "");
|
const handleId =
|
||||||
|
formContext.uiType === BlockUIType.AGENT
|
||||||
|
? (idSchema.$id ?? "")
|
||||||
|
.split("_")
|
||||||
|
.filter((p) => p !== "root" && p !== "properties" && p.length > 0)
|
||||||
|
.join("_") || ""
|
||||||
|
: generateHandleId(idSchema.$id ?? "");
|
||||||
|
|
||||||
const updatedFormContexrt = { ...formContext, fromAnyOf: true };
|
const updatedFormContexrt = { ...formContext, fromAnyOf: true };
|
||||||
|
|
||||||
const { nodeId, showHandles = true } = updatedFormContexrt;
|
const { nodeId, showHandles = true } = updatedFormContexrt;
|
||||||
|
|||||||
@@ -58,7 +58,15 @@ const FieldTemplate: React.FC<FieldTemplateProps> = ({
|
|||||||
|
|
||||||
let handleId = null;
|
let handleId = null;
|
||||||
if (!isArrayItem) {
|
if (!isArrayItem) {
|
||||||
handleId = generateHandleId(fieldId);
|
if (uiType === BlockUIType.AGENT) {
|
||||||
|
const parts = fieldId.split("_");
|
||||||
|
const filtered = parts.filter(
|
||||||
|
(p) => p !== "root" && p !== "properties" && p.length > 0,
|
||||||
|
);
|
||||||
|
handleId = filtered.join("_") || "";
|
||||||
|
} else {
|
||||||
|
handleId = generateHandleId(fieldId);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
handleId = arrayFieldHandleId;
|
handleId = arrayFieldHandleId;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -910,7 +910,37 @@ export default class BackendAPI {
|
|||||||
reject(new Error("Invalid JSON response"));
|
reject(new Error("Invalid JSON response"));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
reject(new Error(`HTTP ${xhr.status}: ${xhr.statusText}`));
|
// Handle file size errors with user-friendly message
|
||||||
|
if (xhr.status === 413) {
|
||||||
|
reject(new Error("File is too large — max size is 256MB"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to parse error response for better messages
|
||||||
|
let errorMessage = `Upload failed (${xhr.status})`;
|
||||||
|
try {
|
||||||
|
const errorData = JSON.parse(xhr.responseText);
|
||||||
|
if (errorData.detail) {
|
||||||
|
if (
|
||||||
|
typeof errorData.detail === "string" &&
|
||||||
|
errorData.detail.includes("exceeds the maximum")
|
||||||
|
) {
|
||||||
|
const match = errorData.detail.match(
|
||||||
|
/maximum allowed size of (\d+)MB/,
|
||||||
|
);
|
||||||
|
const maxSize = match ? match[1] : "256";
|
||||||
|
errorMessage = `File is too large — max size is ${maxSize}MB`;
|
||||||
|
} else if (typeof errorData.detail === "string") {
|
||||||
|
errorMessage = errorData.detail;
|
||||||
|
}
|
||||||
|
} else if (errorData.error) {
|
||||||
|
errorMessage = errorData.error;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Keep default message if parsing fails
|
||||||
|
}
|
||||||
|
|
||||||
|
reject(new Error(errorMessage));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -184,6 +184,11 @@ export function serializeRequestBody(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function parseApiError(response: Response): Promise<string> {
|
export async function parseApiError(response: Response): Promise<string> {
|
||||||
|
// Handle 413 Payload Too Large with user-friendly message
|
||||||
|
if (response.status === 413) {
|
||||||
|
return "File is too large — max size is 256MB";
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const errorData = await response.clone().json();
|
const errorData = await response.clone().json();
|
||||||
|
|
||||||
@@ -205,6 +210,16 @@ export async function parseApiError(response: Response): Promise<string> {
|
|||||||
return response.statusText; // Fallback to status text if no message
|
return response.statusText; // Fallback to status text if no message
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for file size error from backend
|
||||||
|
if (
|
||||||
|
typeof errorData.detail === "string" &&
|
||||||
|
errorData.detail.includes("exceeds the maximum")
|
||||||
|
) {
|
||||||
|
const match = errorData.detail.match(/maximum allowed size of (\d+)MB/);
|
||||||
|
const maxSize = match ? match[1] : "256";
|
||||||
|
return `File is too large — max size is ${maxSize}MB`;
|
||||||
|
}
|
||||||
|
|
||||||
return errorData.detail || errorData.error || response.statusText;
|
return errorData.detail || errorData.error || response.statusText;
|
||||||
} catch {
|
} catch {
|
||||||
return response.statusText;
|
return response.statusText;
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ const mockFlags = {
|
|||||||
[Flag.AGENT_FAVORITING]: false,
|
[Flag.AGENT_FAVORITING]: false,
|
||||||
[Flag.MARKETPLACE_SEARCH_TERMS]: DEFAULT_SEARCH_TERMS,
|
[Flag.MARKETPLACE_SEARCH_TERMS]: DEFAULT_SEARCH_TERMS,
|
||||||
[Flag.ENABLE_PLATFORM_PAYMENT]: false,
|
[Flag.ENABLE_PLATFORM_PAYMENT]: false,
|
||||||
[Flag.CHAT]: true,
|
[Flag.CHAT]: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export function useGetFlag<T extends Flag>(flag: T): FlagValues[T] | null {
|
export function useGetFlag<T extends Flag>(flag: T): FlagValues[T] | null {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
|
import scrollbar from "tailwind-scrollbar";
|
||||||
import type { Config } from "tailwindcss";
|
import type { Config } from "tailwindcss";
|
||||||
import tailwindcssAnimate from "tailwindcss-animate";
|
import tailwindcssAnimate from "tailwindcss-animate";
|
||||||
import scrollbar from "tailwind-scrollbar";
|
|
||||||
import { colors } from "./src/components/styles/colors";
|
import { colors } from "./src/components/styles/colors";
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
darkMode: ["class"],
|
darkMode: ["class", ".dark-mode"], // ignore dark: prefix classes for now until we fully support dark mode
|
||||||
content: ["./src/**/*.{ts,tsx}"],
|
content: ["./src/**/*.{ts,tsx}"],
|
||||||
prefix: "",
|
prefix: "",
|
||||||
theme: {
|
theme: {
|
||||||
|
|||||||
Reference in New Issue
Block a user