mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
Compare commits
24 Commits
fix/execut
...
copilot/un
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a78ab9095 | ||
|
|
5681924fe4 | ||
|
|
b76b5a37c5 | ||
|
|
a4c473ebbe | ||
|
|
eed07b173a | ||
|
|
c5e8b0b08f | ||
|
|
cd3e35df9e | ||
|
|
4c474417bc | ||
|
|
99e2261254 | ||
|
|
cab498fa8c | ||
|
|
22078671df | ||
|
|
0082a72657 | ||
|
|
9a1d940677 | ||
|
|
e640d36265 | ||
|
|
cc9179178f | ||
|
|
e8d37ab116 | ||
|
|
7f7ef6a271 | ||
|
|
aefac541d9 | ||
|
|
89ce133e4e | ||
|
|
faccb78c6f | ||
|
|
9251424576 | ||
|
|
5e7b6b3da7 | ||
|
|
452fba179b | ||
|
|
5f3354230b |
@@ -11,7 +11,7 @@ jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
- uses: actions/stale@v10
|
||||
with:
|
||||
# operations-per-run: 5000
|
||||
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
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/labeler@v5
|
||||
- uses: actions/labeler@v6
|
||||
with:
|
||||
sync-labels: true
|
||||
|
||||
@@ -20,6 +20,7 @@ from backend.data.model import (
|
||||
SchemaField,
|
||||
)
|
||||
from backend.integrations.providers import ProviderName
|
||||
from backend.util.exceptions import BlockExecutionError
|
||||
from backend.util.request import Requests
|
||||
|
||||
TEST_CREDENTIALS = APIKeyCredentials(
|
||||
@@ -246,7 +247,11 @@ class AIShortformVideoCreatorBlock(Block):
|
||||
await asyncio.sleep(10)
|
||||
|
||||
logger.error("Video creation timed out")
|
||||
raise TimeoutError("Video creation timed out")
|
||||
raise BlockExecutionError(
|
||||
message="Video creation timed out",
|
||||
block_name=self.name,
|
||||
block_id=self.id,
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
@@ -422,7 +427,11 @@ class AIAdMakerVideoCreatorBlock(Block):
|
||||
await asyncio.sleep(10)
|
||||
|
||||
logger.error("Video creation timed out")
|
||||
raise TimeoutError("Video creation timed out")
|
||||
raise BlockExecutionError(
|
||||
message="Video creation timed out",
|
||||
block_name=self.name,
|
||||
block_id=self.id,
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
@@ -599,7 +608,11 @@ class AIScreenshotToVideoAdBlock(Block):
|
||||
await asyncio.sleep(10)
|
||||
|
||||
logger.error("Video creation timed out")
|
||||
raise TimeoutError("Video creation timed out")
|
||||
raise BlockExecutionError(
|
||||
message="Video creation timed out",
|
||||
block_name=self.name,
|
||||
block_id=self.id,
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
|
||||
@@ -106,7 +106,10 @@ class ConditionBlock(Block):
|
||||
ComparisonOperator.LESS_THAN_OR_EQUAL: lambda a, b: a <= b,
|
||||
}
|
||||
|
||||
result = comparison_funcs[operator](value1, value2)
|
||||
try:
|
||||
result = comparison_funcs[operator](value1, value2)
|
||||
except Exception as e:
|
||||
raise ValueError(f"Comparison failed: {e}") from e
|
||||
|
||||
yield "result", result
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ from backend.sdk import (
|
||||
SchemaField,
|
||||
cost,
|
||||
)
|
||||
from backend.util.exceptions import BlockExecutionError
|
||||
|
||||
from ._config import firecrawl
|
||||
|
||||
@@ -59,11 +60,18 @@ class FirecrawlExtractBlock(Block):
|
||||
) -> BlockOutput:
|
||||
app = FirecrawlApp(api_key=credentials.api_key.get_secret_value())
|
||||
|
||||
extract_result = app.extract(
|
||||
urls=input_data.urls,
|
||||
prompt=input_data.prompt,
|
||||
schema=input_data.output_schema,
|
||||
enable_web_search=input_data.enable_web_search,
|
||||
)
|
||||
try:
|
||||
extract_result = app.extract(
|
||||
urls=input_data.urls,
|
||||
prompt=input_data.prompt,
|
||||
schema=input_data.output_schema,
|
||||
enable_web_search=input_data.enable_web_search,
|
||||
)
|
||||
except Exception as e:
|
||||
raise BlockExecutionError(
|
||||
message=f"Extract failed: {e}",
|
||||
block_name=self.name,
|
||||
block_id=self.id,
|
||||
) from e
|
||||
|
||||
yield "data", extract_result.data
|
||||
|
||||
@@ -19,6 +19,7 @@ from backend.data.model import (
|
||||
SchemaField,
|
||||
)
|
||||
from backend.integrations.providers import ProviderName
|
||||
from backend.util.exceptions import ModerationError
|
||||
from backend.util.file import MediaFileType, store_media_file
|
||||
|
||||
TEST_CREDENTIALS = APIKeyCredentials(
|
||||
@@ -153,6 +154,8 @@ class AIImageEditorBlock(Block):
|
||||
),
|
||||
aspect_ratio=input_data.aspect_ratio.value,
|
||||
seed=input_data.seed,
|
||||
user_id=user_id,
|
||||
graph_exec_id=graph_exec_id,
|
||||
)
|
||||
yield "output_image", result
|
||||
|
||||
@@ -164,6 +167,8 @@ class AIImageEditorBlock(Block):
|
||||
input_image_b64: Optional[str],
|
||||
aspect_ratio: str,
|
||||
seed: Optional[int],
|
||||
user_id: str,
|
||||
graph_exec_id: str,
|
||||
) -> MediaFileType:
|
||||
client = ReplicateClient(api_token=api_key.get_secret_value())
|
||||
input_params = {
|
||||
@@ -173,11 +178,21 @@ class AIImageEditorBlock(Block):
|
||||
**({"seed": seed} if seed is not None else {}),
|
||||
}
|
||||
|
||||
output: FileOutput | list[FileOutput] = await client.async_run( # type: ignore
|
||||
model_name,
|
||||
input=input_params,
|
||||
wait=False,
|
||||
)
|
||||
try:
|
||||
output: FileOutput | list[FileOutput] = await client.async_run( # type: ignore
|
||||
model_name,
|
||||
input=input_params,
|
||||
wait=False,
|
||||
)
|
||||
except Exception as e:
|
||||
if "flagged as sensitive" in str(e).lower():
|
||||
raise ModerationError(
|
||||
message="Content was flagged as sensitive by the model provider",
|
||||
user_id=user_id,
|
||||
graph_exec_id=graph_exec_id,
|
||||
moderation_type="model_provider",
|
||||
)
|
||||
raise ValueError(f"Model execution failed: {e}") from e
|
||||
|
||||
if isinstance(output, list) and output:
|
||||
output = output[0]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import logging
|
||||
from typing import Any, Literal
|
||||
from typing import Any
|
||||
|
||||
from prisma.enums import ReviewStatus
|
||||
|
||||
@@ -45,11 +45,11 @@ class HumanInTheLoopBlock(Block):
|
||||
)
|
||||
|
||||
class Output(BlockSchemaOutput):
|
||||
reviewed_data: Any = SchemaField(
|
||||
description="The data after human review (may be modified)"
|
||||
approved_data: Any = SchemaField(
|
||||
description="The data when approved (may be modified by reviewer)"
|
||||
)
|
||||
status: Literal["approved", "rejected"] = SchemaField(
|
||||
description="Status of the review: 'approved' or 'rejected'"
|
||||
rejected_data: Any = SchemaField(
|
||||
description="The data when rejected (may be modified by reviewer)"
|
||||
)
|
||||
review_message: str = SchemaField(
|
||||
description="Any message provided by the reviewer", default=""
|
||||
@@ -69,8 +69,7 @@ class HumanInTheLoopBlock(Block):
|
||||
"editable": True,
|
||||
},
|
||||
test_output=[
|
||||
("status", "approved"),
|
||||
("reviewed_data", {"name": "John Doe", "age": 30}),
|
||||
("approved_data", {"name": "John Doe", "age": 30}),
|
||||
],
|
||||
test_mock={
|
||||
"get_or_create_human_review": lambda *_args, **_kwargs: ReviewResult(
|
||||
@@ -116,8 +115,7 @@ class HumanInTheLoopBlock(Block):
|
||||
logger.info(
|
||||
f"HITL block skipping review for node {node_exec_id} - safe mode disabled"
|
||||
)
|
||||
yield "status", "approved"
|
||||
yield "reviewed_data", input_data.data
|
||||
yield "approved_data", input_data.data
|
||||
yield "review_message", "Auto-approved (safe mode disabled)"
|
||||
return
|
||||
|
||||
@@ -158,12 +156,11 @@ class HumanInTheLoopBlock(Block):
|
||||
)
|
||||
|
||||
if result.status == ReviewStatus.APPROVED:
|
||||
yield "status", "approved"
|
||||
yield "reviewed_data", result.data
|
||||
yield "approved_data", result.data
|
||||
if result.message:
|
||||
yield "review_message", result.message
|
||||
|
||||
elif result.status == ReviewStatus.REJECTED:
|
||||
yield "status", "rejected"
|
||||
yield "rejected_data", result.data
|
||||
if result.message:
|
||||
yield "review_message", result.message
|
||||
|
||||
@@ -2,7 +2,6 @@ from enum import Enum
|
||||
from typing import Any, Dict, Literal, Optional
|
||||
|
||||
from pydantic import SecretStr
|
||||
from requests.exceptions import RequestException
|
||||
|
||||
from backend.data.block import (
|
||||
Block,
|
||||
@@ -332,8 +331,8 @@ class IdeogramModelBlock(Block):
|
||||
try:
|
||||
response = await Requests().post(url, headers=headers, json=data)
|
||||
return response.json()["data"][0]["url"]
|
||||
except RequestException as e:
|
||||
raise Exception(f"Failed to fetch image with V3 endpoint: {str(e)}")
|
||||
except Exception as e:
|
||||
raise ValueError(f"Failed to fetch image with V3 endpoint: {e}") from e
|
||||
|
||||
async def _run_model_legacy(
|
||||
self,
|
||||
@@ -385,8 +384,8 @@ class IdeogramModelBlock(Block):
|
||||
try:
|
||||
response = await Requests().post(url, headers=headers, json=data)
|
||||
return response.json()["data"][0]["url"]
|
||||
except RequestException as e:
|
||||
raise Exception(f"Failed to fetch image with legacy endpoint: {str(e)}")
|
||||
except Exception as e:
|
||||
raise ValueError(f"Failed to fetch image with legacy endpoint: {e}") from e
|
||||
|
||||
async def upscale_image(self, api_key: SecretStr, image_url: str):
|
||||
url = "https://api.ideogram.ai/upscale"
|
||||
@@ -413,5 +412,5 @@ class IdeogramModelBlock(Block):
|
||||
|
||||
return (response.json())["data"][0]["url"]
|
||||
|
||||
except RequestException as e:
|
||||
raise Exception(f"Failed to upscale image: {str(e)}")
|
||||
except Exception as e:
|
||||
raise ValueError(f"Failed to upscale image: {e}") from e
|
||||
|
||||
@@ -16,6 +16,7 @@ from backend.data.block import (
|
||||
BlockSchemaOutput,
|
||||
)
|
||||
from backend.data.model import SchemaField
|
||||
from backend.util.exceptions import BlockExecutionError
|
||||
|
||||
|
||||
class SearchTheWebBlock(Block, GetRequest):
|
||||
@@ -56,7 +57,17 @@ class SearchTheWebBlock(Block, GetRequest):
|
||||
|
||||
# Prepend the Jina Search URL to the encoded query
|
||||
jina_search_url = f"https://s.jina.ai/{encoded_query}"
|
||||
results = await self.get_request(jina_search_url, headers=headers, json=False)
|
||||
|
||||
try:
|
||||
results = await self.get_request(
|
||||
jina_search_url, headers=headers, json=False
|
||||
)
|
||||
except Exception as e:
|
||||
raise BlockExecutionError(
|
||||
message=f"Search failed: {e}",
|
||||
block_name=self.name,
|
||||
block_id=self.id,
|
||||
) from e
|
||||
|
||||
# Output the search results
|
||||
yield "results", results
|
||||
|
||||
@@ -18,6 +18,7 @@ from backend.data.block import (
|
||||
BlockSchemaOutput,
|
||||
)
|
||||
from backend.data.model import APIKeyCredentials, CredentialsField, SchemaField
|
||||
from backend.util.exceptions import BlockExecutionError, BlockInputError
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -111,9 +112,27 @@ class ReplicateModelBlock(Block):
|
||||
yield "status", "succeeded"
|
||||
yield "model_name", input_data.model_name
|
||||
except Exception as e:
|
||||
error_msg = f"Unexpected error running Replicate model: {str(e)}"
|
||||
logger.error(error_msg)
|
||||
raise RuntimeError(error_msg)
|
||||
error_msg = str(e)
|
||||
logger.error(f"Error running Replicate model: {error_msg}")
|
||||
|
||||
# Input validation errors (422, 400) → BlockInputError
|
||||
if (
|
||||
"422" in error_msg
|
||||
or "Input validation failed" in error_msg
|
||||
or "400" in error_msg
|
||||
):
|
||||
raise BlockInputError(
|
||||
message=f"Invalid model inputs: {error_msg}",
|
||||
block_name=self.name,
|
||||
block_id=self.id,
|
||||
) from e
|
||||
# Everything else → BlockExecutionError
|
||||
else:
|
||||
raise BlockExecutionError(
|
||||
message=f"Replicate model error: {error_msg}",
|
||||
block_name=self.name,
|
||||
block_id=self.id,
|
||||
) from e
|
||||
|
||||
async def run_model(self, model_ref: str, model_inputs: dict, api_key: SecretStr):
|
||||
"""
|
||||
|
||||
@@ -45,10 +45,16 @@ class GetWikipediaSummaryBlock(Block, GetRequest):
|
||||
async def run(self, input_data: Input, **kwargs) -> BlockOutput:
|
||||
topic = input_data.topic
|
||||
url = f"https://en.wikipedia.org/api/rest_v1/page/summary/{topic}"
|
||||
response = await self.get_request(url, json=True)
|
||||
if "extract" not in response:
|
||||
raise RuntimeError(f"Unable to parse Wikipedia response: {response}")
|
||||
yield "summary", response["extract"]
|
||||
|
||||
# Note: User-Agent is now automatically set by the request library
|
||||
# to comply with Wikimedia's robot policy (https://w.wiki/4wJS)
|
||||
try:
|
||||
response = await self.get_request(url, json=True)
|
||||
if "extract" not in response:
|
||||
raise ValueError(f"Unable to parse Wikipedia response: {response}")
|
||||
yield "summary", response["extract"]
|
||||
except Exception as e:
|
||||
raise ValueError(f"Failed to fetch Wikipedia summary: {e}") from e
|
||||
|
||||
|
||||
TEST_CREDENTIALS = APIKeyCredentials(
|
||||
|
||||
@@ -100,7 +100,7 @@ async def get_or_create_human_review(
|
||||
return None
|
||||
else:
|
||||
return ReviewResult(
|
||||
data=review.payload if review.status == ReviewStatus.APPROVED else None,
|
||||
data=review.payload,
|
||||
status=review.status,
|
||||
message=review.reviewMessage or "",
|
||||
processed=review.processed,
|
||||
|
||||
@@ -134,18 +134,14 @@ async def process_review_action(
|
||||
# Build review decisions map
|
||||
review_decisions = {}
|
||||
for review in request.reviews:
|
||||
if review.approved:
|
||||
review_decisions[review.node_exec_id] = (
|
||||
ReviewStatus.APPROVED,
|
||||
review.reviewed_data,
|
||||
review.message,
|
||||
)
|
||||
else:
|
||||
review_decisions[review.node_exec_id] = (
|
||||
ReviewStatus.REJECTED,
|
||||
None,
|
||||
review.message,
|
||||
)
|
||||
review_status = (
|
||||
ReviewStatus.APPROVED if review.approved else ReviewStatus.REJECTED
|
||||
)
|
||||
review_decisions[review.node_exec_id] = (
|
||||
review_status,
|
||||
review.reviewed_data,
|
||||
review.message,
|
||||
)
|
||||
|
||||
# Process all reviews
|
||||
updated_reviews = await process_all_reviews_for_execution(
|
||||
|
||||
@@ -14,12 +14,47 @@ from backend.util.virus_scanner import scan_content_safe
|
||||
|
||||
TEMP_DIR = Path(tempfile.gettempdir()).resolve()
|
||||
|
||||
# Maximum filename length (conservative limit for most filesystems)
|
||||
MAX_FILENAME_LENGTH = 200
|
||||
|
||||
|
||||
def sanitize_filename(filename: str) -> str:
|
||||
"""
|
||||
Sanitize and truncate filename to prevent filesystem errors.
|
||||
"""
|
||||
# Remove or replace invalid characters
|
||||
sanitized = re.sub(r'[<>:"/\\|?*\n\r\t]', "_", filename)
|
||||
|
||||
# Truncate if too long
|
||||
if len(sanitized) > MAX_FILENAME_LENGTH:
|
||||
# Keep the extension if possible
|
||||
if "." in sanitized:
|
||||
name, ext = sanitized.rsplit(".", 1)
|
||||
max_name_length = MAX_FILENAME_LENGTH - len(ext) - 1
|
||||
sanitized = name[:max_name_length] + "." + ext
|
||||
else:
|
||||
sanitized = sanitized[:MAX_FILENAME_LENGTH]
|
||||
|
||||
# Ensure it's not empty or just dots
|
||||
if not sanitized or sanitized.strip(".") == "":
|
||||
sanitized = f"file_{uuid.uuid4().hex[:8]}"
|
||||
|
||||
return sanitized
|
||||
|
||||
|
||||
def get_exec_file_path(graph_exec_id: str, path: str) -> str:
|
||||
"""
|
||||
Utility to build an absolute path in the {temp}/exec_file/{exec_id}/... folder.
|
||||
"""
|
||||
return str(TEMP_DIR / "exec_file" / graph_exec_id / path)
|
||||
try:
|
||||
full_path = TEMP_DIR / "exec_file" / graph_exec_id / path
|
||||
return str(full_path)
|
||||
except OSError as e:
|
||||
if "File name too long" in str(e):
|
||||
raise ValueError(
|
||||
f"File path too long: {len(path)} characters. Maximum path length exceeded."
|
||||
) from e
|
||||
raise ValueError(f"Invalid file path: {e}") from e
|
||||
|
||||
|
||||
def clean_exec_files(graph_exec_id: str, file: str = "") -> None:
|
||||
@@ -117,8 +152,11 @@ async def store_media_file(
|
||||
|
||||
# Generate filename from cloud path
|
||||
_, path_part = cloud_storage.parse_cloud_path(file)
|
||||
filename = Path(path_part).name or f"{uuid.uuid4()}.bin"
|
||||
target_path = _ensure_inside_base(base_path / filename, base_path)
|
||||
filename = sanitize_filename(Path(path_part).name or f"{uuid.uuid4()}.bin")
|
||||
try:
|
||||
target_path = _ensure_inside_base(base_path / filename, base_path)
|
||||
except OSError as e:
|
||||
raise ValueError(f"Invalid file path '{filename}': {e}") from e
|
||||
|
||||
# Check file size limit
|
||||
if len(cloud_content) > MAX_FILE_SIZE:
|
||||
@@ -144,7 +182,10 @@ async def store_media_file(
|
||||
# Generate filename and decode
|
||||
extension = _extension_from_mime(mime_type)
|
||||
filename = f"{uuid.uuid4()}{extension}"
|
||||
target_path = _ensure_inside_base(base_path / filename, base_path)
|
||||
try:
|
||||
target_path = _ensure_inside_base(base_path / filename, base_path)
|
||||
except OSError as e:
|
||||
raise ValueError(f"Invalid file path '{filename}': {e}") from e
|
||||
content = base64.b64decode(b64_content)
|
||||
|
||||
# Check file size limit
|
||||
@@ -160,8 +201,11 @@ async def store_media_file(
|
||||
elif file.startswith(("http://", "https://")):
|
||||
# URL
|
||||
parsed_url = urlparse(file)
|
||||
filename = Path(parsed_url.path).name or f"{uuid.uuid4()}"
|
||||
target_path = _ensure_inside_base(base_path / filename, base_path)
|
||||
filename = sanitize_filename(Path(parsed_url.path).name or f"{uuid.uuid4()}")
|
||||
try:
|
||||
target_path = _ensure_inside_base(base_path / filename, base_path)
|
||||
except OSError as e:
|
||||
raise ValueError(f"Invalid file path '{filename}': {e}") from e
|
||||
|
||||
# Download and save
|
||||
resp = await Requests().get(file)
|
||||
@@ -177,8 +221,12 @@ async def store_media_file(
|
||||
target_path.write_bytes(resp.content)
|
||||
|
||||
else:
|
||||
# Local path
|
||||
target_path = _ensure_inside_base(base_path / file, base_path)
|
||||
# Local path - sanitize the filename part to prevent long filename errors
|
||||
sanitized_file = sanitize_filename(file)
|
||||
try:
|
||||
target_path = _ensure_inside_base(base_path / sanitized_file, base_path)
|
||||
except OSError as e:
|
||||
raise ValueError(f"Invalid file path '{sanitized_file}': {e}") from e
|
||||
if not target_path.is_file():
|
||||
raise ValueError(f"Local file does not exist: {target_path}")
|
||||
|
||||
|
||||
@@ -21,6 +21,26 @@ from tenacity import (
|
||||
|
||||
from backend.util.json import loads
|
||||
|
||||
|
||||
class HTTPClientError(Exception):
|
||||
"""4xx client errors (400-499)"""
|
||||
|
||||
def __init__(self, message: str, status_code: int):
|
||||
super().__init__(message)
|
||||
self.status_code = status_code
|
||||
|
||||
|
||||
class HTTPServerError(Exception):
|
||||
"""5xx server errors (500-599)"""
|
||||
|
||||
def __init__(self, message: str, status_code: int):
|
||||
super().__init__(message)
|
||||
self.status_code = status_code
|
||||
|
||||
|
||||
# Default User-Agent for all requests
|
||||
DEFAULT_USER_AGENT = "AutoGPT-Platform/1.0 (https://github.com/Significant-Gravitas/AutoGPT; info@agpt.co) aiohttp"
|
||||
|
||||
# Retry status codes for which we will automatically retry the request
|
||||
THROTTLE_RETRY_STATUS_CODES: set[int] = {429, 500, 502, 503, 504, 408}
|
||||
|
||||
@@ -450,6 +470,10 @@ class Requests:
|
||||
if self.extra_headers is not None:
|
||||
req_headers.update(self.extra_headers)
|
||||
|
||||
# Set default User-Agent if not provided
|
||||
if "User-Agent" not in req_headers and "user-agent" not in req_headers:
|
||||
req_headers["User-Agent"] = DEFAULT_USER_AGENT
|
||||
|
||||
# Override Host header if using IP connection
|
||||
if connector:
|
||||
req_headers["Host"] = hostname
|
||||
@@ -476,9 +500,16 @@ class Requests:
|
||||
response.raise_for_status()
|
||||
except ClientResponseError as e:
|
||||
body = await response.read()
|
||||
raise Exception(
|
||||
f"HTTP {response.status} Error: {response.reason}, Body: {body.decode(errors='replace')}"
|
||||
) from e
|
||||
error_message = f"HTTP {response.status} Error: {response.reason}, Body: {body.decode(errors='replace')}"
|
||||
|
||||
# Raise specific exceptions based on status code range
|
||||
if 400 <= response.status <= 499:
|
||||
raise HTTPClientError(error_message, response.status) from e
|
||||
elif 500 <= response.status <= 599:
|
||||
raise HTTPServerError(error_message, response.status) from e
|
||||
else:
|
||||
# Generic fallback for other HTTP errors
|
||||
raise Exception(error_message) from e
|
||||
|
||||
# If allowed and a redirect is received, follow the redirect manually
|
||||
if allow_redirects and response.status in (301, 302, 303, 307, 308):
|
||||
|
||||
@@ -5,6 +5,13 @@ from typing import Any, Type, TypeVar, Union, cast, get_args, get_origin, overlo
|
||||
from prisma import Json as PrismaJson
|
||||
|
||||
|
||||
def _is_type_or_subclass(origin: Any, target_type: type) -> bool:
|
||||
"""Check if origin is exactly the target type or a subclass of it."""
|
||||
return origin is target_type or (
|
||||
isinstance(origin, type) and issubclass(origin, target_type)
|
||||
)
|
||||
|
||||
|
||||
class ConversionError(ValueError):
|
||||
pass
|
||||
|
||||
@@ -138,7 +145,11 @@ def _try_convert(value: Any, target_type: Any, raise_on_mismatch: bool) -> Any:
|
||||
|
||||
if origin is None:
|
||||
origin = target_type
|
||||
if origin not in [list, dict, tuple, str, set, int, float, bool]:
|
||||
# Early return for unsupported types (skip subclasses of supported types)
|
||||
supported_types = [list, dict, tuple, str, set, int, float, bool]
|
||||
if origin not in supported_types and not (
|
||||
isinstance(origin, type) and any(issubclass(origin, t) for t in supported_types)
|
||||
):
|
||||
return value
|
||||
|
||||
# Handle the case when value is already of the target type
|
||||
@@ -168,44 +179,47 @@ def _try_convert(value: Any, target_type: Any, raise_on_mismatch: bool) -> Any:
|
||||
raise TypeError(f"Value {value} is not of expected type {target_type}")
|
||||
else:
|
||||
# Need to convert value to the origin type
|
||||
if origin is list:
|
||||
value = __convert_list(value)
|
||||
if _is_type_or_subclass(origin, list):
|
||||
converted_list = __convert_list(value)
|
||||
if args:
|
||||
return [convert(v, args[0]) for v in value]
|
||||
else:
|
||||
return value
|
||||
elif origin is dict:
|
||||
value = __convert_dict(value)
|
||||
converted_list = [convert(v, args[0]) for v in converted_list]
|
||||
return origin(converted_list) if origin is not list else converted_list
|
||||
elif _is_type_or_subclass(origin, dict):
|
||||
converted_dict = __convert_dict(value)
|
||||
if args:
|
||||
key_type, val_type = args
|
||||
return {
|
||||
convert(k, key_type): convert(v, val_type) for k, v in value.items()
|
||||
converted_dict = {
|
||||
convert(k, key_type): convert(v, val_type)
|
||||
for k, v in converted_dict.items()
|
||||
}
|
||||
else:
|
||||
return value
|
||||
elif origin is tuple:
|
||||
value = __convert_tuple(value)
|
||||
return origin(converted_dict) if origin is not dict else converted_dict
|
||||
elif _is_type_or_subclass(origin, tuple):
|
||||
converted_tuple = __convert_tuple(value)
|
||||
if args:
|
||||
if len(args) == 1:
|
||||
return tuple(convert(v, args[0]) for v in value)
|
||||
converted_tuple = tuple(
|
||||
convert(v, args[0]) for v in converted_tuple
|
||||
)
|
||||
else:
|
||||
return tuple(convert(v, t) for v, t in zip(value, args))
|
||||
else:
|
||||
return value
|
||||
elif origin is str:
|
||||
return __convert_str(value)
|
||||
elif origin is set:
|
||||
converted_tuple = tuple(
|
||||
convert(v, t) for v, t in zip(converted_tuple, args)
|
||||
)
|
||||
return origin(converted_tuple) if origin is not tuple else converted_tuple
|
||||
elif _is_type_or_subclass(origin, str):
|
||||
converted_str = __convert_str(value)
|
||||
return origin(converted_str) if origin is not str else converted_str
|
||||
elif _is_type_or_subclass(origin, set):
|
||||
value = __convert_set(value)
|
||||
if args:
|
||||
return {convert(v, args[0]) for v in value}
|
||||
else:
|
||||
return value
|
||||
elif origin is int:
|
||||
return __convert_num(value, int)
|
||||
elif origin is float:
|
||||
return __convert_num(value, float)
|
||||
elif origin is bool:
|
||||
elif _is_type_or_subclass(origin, bool):
|
||||
return __convert_bool(value)
|
||||
elif _is_type_or_subclass(origin, int):
|
||||
return __convert_num(value, int)
|
||||
elif _is_type_or_subclass(origin, float):
|
||||
return __convert_num(value, float)
|
||||
else:
|
||||
return value
|
||||
|
||||
|
||||
@@ -32,3 +32,17 @@ def test_type_conversion():
|
||||
assert convert("5", List[int]) == [5]
|
||||
assert convert("[5,4,2]", List[int]) == [5, 4, 2]
|
||||
assert convert([5, 4, 2], List[str]) == ["5", "4", "2"]
|
||||
|
||||
# Test the specific case that was failing: empty list to Optional[str]
|
||||
assert convert([], Optional[str]) == "[]"
|
||||
assert convert([], str) == "[]"
|
||||
|
||||
# Test the actual failing case: empty list to ShortTextType
|
||||
from backend.util.type import ShortTextType
|
||||
|
||||
assert convert([], Optional[ShortTextType]) == "[]"
|
||||
assert convert([], ShortTextType) == "[]"
|
||||
|
||||
# Test other empty list conversions
|
||||
assert convert([], int) == 0 # len([]) = 0
|
||||
assert convert([], Optional[int]) == 0
|
||||
|
||||
@@ -3,6 +3,14 @@ import { withSentryConfig } from "@sentry/nextjs";
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
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: {
|
||||
domains: [
|
||||
// We dont need to maintain alphabetical order here
|
||||
|
||||
@@ -137,9 +137,8 @@
|
||||
"concurrently": "9.2.1",
|
||||
"cross-env": "10.1.0",
|
||||
"eslint": "8.57.1",
|
||||
"eslint-config-next": "15.5.2",
|
||||
"eslint-config-next": "15.5.7",
|
||||
"eslint-plugin-storybook": "9.1.5",
|
||||
"import-in-the-middle": "1.14.2",
|
||||
"msw": "2.11.6",
|
||||
"msw-storybook-addon": "2.0.6",
|
||||
"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
|
||||
version: 8.57.1
|
||||
eslint-config-next:
|
||||
specifier: 15.5.2
|
||||
version: 15.5.2(eslint@8.57.1)(typescript@5.9.3)
|
||||
specifier: 15.5.7
|
||||
version: 15.5.7(eslint@8.57.1)(typescript@5.9.3)
|
||||
eslint-plugin-storybook:
|
||||
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)
|
||||
import-in-the-middle:
|
||||
specifier: 1.14.2
|
||||
version: 1.14.2
|
||||
msw:
|
||||
specifier: 2.11.6
|
||||
version: 2.11.6(@types/node@24.10.0)(typescript@5.9.3)
|
||||
@@ -986,12 +983,15 @@ packages:
|
||||
'@date-fns/tz@1.4.1':
|
||||
resolution: {integrity: sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==}
|
||||
|
||||
'@emnapi/core@1.5.0':
|
||||
resolution: {integrity: sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==}
|
||||
'@emnapi/core@1.7.1':
|
||||
resolution: {integrity: sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==}
|
||||
|
||||
'@emnapi/runtime@1.5.0':
|
||||
resolution: {integrity: sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==}
|
||||
|
||||
'@emnapi/runtime@1.7.1':
|
||||
resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==}
|
||||
|
||||
'@emnapi/wasi-threads@1.1.0':
|
||||
resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==}
|
||||
|
||||
@@ -1329,6 +1329,10 @@ packages:
|
||||
resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==}
|
||||
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':
|
||||
resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
@@ -1605,8 +1609,8 @@ packages:
|
||||
'@next/env@15.4.10':
|
||||
resolution: {integrity: sha512-knhmoJ0Vv7VRf6pZEPSnciUG1S4bIhWx+qTYBW/AjxEtlzsiNORPk8sFDCEvqLfmKuey56UB9FL1UdHEV3uBrg==}
|
||||
|
||||
'@next/eslint-plugin-next@15.5.2':
|
||||
resolution: {integrity: sha512-lkLrRVxcftuOsJNhWatf1P2hNVfh98k/omQHrCEPPriUypR6RcS13IvLdIrEvkm9AH2Nu2YpR5vLqBuy6twH3Q==}
|
||||
'@next/eslint-plugin-next@15.5.7':
|
||||
resolution: {integrity: sha512-DtRU2N7BkGr8r+pExfuWHwMEPX5SD57FeA6pxdgCHODo+b/UgIgjE+rgWKtJAbEbGhVZ2jtHn4g3wNhWFoNBQQ==}
|
||||
|
||||
'@next/swc-darwin-arm64@15.4.8':
|
||||
resolution: {integrity: sha512-Pf6zXp7yyQEn7sqMxur6+kYcywx5up1J849psyET7/8pG2gQTVMjU3NzgIt8SeEP5to3If/SaWmaA6H6ysBr1A==}
|
||||
@@ -2622,8 +2626,8 @@ packages:
|
||||
'@rtsao/scc@1.1.0':
|
||||
resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
|
||||
|
||||
'@rushstack/eslint-patch@1.12.0':
|
||||
resolution: {integrity: sha512-5EwMtOqvJMMa3HbmxLlF74e+3/HhwBTMcvt3nqVJgGCozO6hzIPOBlwm8mGVNR9SN2IJpxSnlxczyDjcn7qIyw==}
|
||||
'@rushstack/eslint-patch@1.15.0':
|
||||
resolution: {integrity: sha512-ojSshQPKwVvSMR8yT2L/QtUkV5SXi/IfDiJ4/8d6UbTPjiHVmxZzUAzGD8Tzks1b9+qQkZa0isUOvYObedITaw==}
|
||||
|
||||
'@scarf/scarf@1.4.0':
|
||||
resolution: {integrity: sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==}
|
||||
@@ -3097,8 +3101,8 @@ packages:
|
||||
peerDependencies:
|
||||
'@testing-library/dom': '>=7.21.4'
|
||||
|
||||
'@tybys/wasm-util@0.10.0':
|
||||
resolution: {integrity: sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==}
|
||||
'@tybys/wasm-util@0.10.1':
|
||||
resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
|
||||
|
||||
'@types/aria-query@5.0.4':
|
||||
resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==}
|
||||
@@ -3288,16 +3292,16 @@ packages:
|
||||
'@types/ws@8.18.1':
|
||||
resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.43.0':
|
||||
resolution: {integrity: sha512-8tg+gt7ENL7KewsKMKDHXR1vm8tt9eMxjJBYINf6swonlWgkYn5NwyIgXpbbDxTNU5DgpDFfj95prcTq2clIQQ==}
|
||||
'@typescript-eslint/eslint-plugin@8.48.1':
|
||||
resolution: {integrity: sha512-X63hI1bxl5ohelzr0LY5coufyl0LJNthld+abwxpCoo6Gq+hSqhKwci7MUWkXo67mzgUK6YFByhmaHmUcuBJmA==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
'@typescript-eslint/parser': ^8.43.0
|
||||
'@typescript-eslint/parser': ^8.48.1
|
||||
eslint: ^8.57.0 || ^9.0.0
|
||||
typescript: '>=4.8.4 <6.0.0'
|
||||
|
||||
'@typescript-eslint/parser@8.43.0':
|
||||
resolution: {integrity: sha512-B7RIQiTsCBBmY+yW4+ILd6mF5h1FUwJsVvpqkrgpszYifetQ2Ke+Z4u6aZh0CblkUGIdR59iYVyXqqZGkZ3aBw==}
|
||||
'@typescript-eslint/parser@8.48.1':
|
||||
resolution: {integrity: sha512-PC0PDZfJg8sP7cmKe6L3QIL8GZwU5aRvUFedqSIpw3B+QjRSUZeeITC2M5XKeMXEzL6wccN196iy3JLwKNvDVA==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0
|
||||
@@ -3315,6 +3319,12 @@ packages:
|
||||
peerDependencies:
|
||||
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':
|
||||
resolution: {integrity: sha512-daSWlQ87ZhsjrbMLvpuuMAt3y4ba57AuvadcR7f3nl8eS3BjRc8L9VLxFLk92RL5xdXOg6IQ+qKjjqNEimGuAg==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
@@ -3323,6 +3333,10 @@ packages:
|
||||
resolution: {integrity: sha512-LF4b/NmGvdWEHD2H4MsHD8ny6JpiVNDzrSZr3CsckEgCbAGZbYM4Cqxvi9L+WqDMT+51Ozy7lt2M+d0JLEuBqA==}
|
||||
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':
|
||||
resolution: {integrity: sha512-ALC2prjZcj2YqqL5X/bwWQmHA2em6/94GcbB/KKu5SX3EBDOsqztmmX1kMkvAJHzxk7TazKzJfFiEIagNV3qEA==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
@@ -3335,8 +3349,14 @@ packages:
|
||||
peerDependencies:
|
||||
typescript: '>=4.8.4 <6.0.0'
|
||||
|
||||
'@typescript-eslint/type-utils@8.43.0':
|
||||
resolution: {integrity: sha512-qaH1uLBpBuBBuRf8c1mLJ6swOfzCXryhKND04Igr4pckzSEW9JX5Aw9AgW00kwfjWJF0kk0ps9ExKTfvXfw4Qg==}
|
||||
'@typescript-eslint/tsconfig-utils@8.48.1':
|
||||
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}
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0
|
||||
@@ -3350,6 +3370,10 @@ packages:
|
||||
resolution: {integrity: sha512-lNCWCbq7rpg7qDsQrd3D6NyWYu+gkTENkG5IKYhUIcxSb59SQC/hEQ+MrG4sTgBVghTonNWq42bA/d4yYumldQ==}
|
||||
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':
|
||||
resolution: {integrity: sha512-7Vv6zlAhPb+cvEpP06WXXy/ZByph9iL6BQRBDj4kmBsW98AqEeQHlj/13X+sZOrKSo9/rNKH4Ul4f6EICREFdw==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
@@ -3362,6 +3386,12 @@ packages:
|
||||
peerDependencies:
|
||||
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':
|
||||
resolution: {integrity: sha512-S1/tEmkUeeswxd0GGcnwuVQPFWo8NzZTOMxCvw8BX7OMxnNae+i8Tm7REQen/SwUIPoPqfKn7EaZ+YLpiB3k9g==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
@@ -3376,6 +3406,13 @@ packages:
|
||||
eslint: ^8.57.0 || ^9.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':
|
||||
resolution: {integrity: sha512-T+S1KqRD4sg/bHfLwrpF/K3gQLBM1n7Rp7OjjikjTEssI2YJzQpi5WXoynOaQ93ERIuq3O8RBTOUYDKszUCEHw==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
@@ -3384,6 +3421,10 @@ packages:
|
||||
resolution: {integrity: sha512-tUFMXI4gxzzMXt4xpGJEsBsTox0XbNQ1y94EwlD/CuZwFcQP79xfQqMhau9HsRc/J0cAPA/HZt1dZPtGn9V/7w==}
|
||||
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':
|
||||
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
|
||||
|
||||
@@ -4585,8 +4626,8 @@ packages:
|
||||
resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
eslint-config-next@15.5.2:
|
||||
resolution: {integrity: sha512-3hPZghsLupMxxZ2ggjIIrat/bPniM2yRpsVPVM40rp8ZMzKWOJp2CGWn7+EzoV2ddkUr5fxNfHpF+wU1hGt/3g==}
|
||||
eslint-config-next@15.5.7:
|
||||
resolution: {integrity: sha512-nU/TRGHHeG81NeLW5DeQT5t6BDUqbpsNQTvef1ld/tqHT+/zTx60/TIhKnmPISTTe++DVo+DLxDmk4rnwHaZVw==}
|
||||
peerDependencies:
|
||||
eslint: ^7.23.0 || ^8.0.0 || ^9.0.0
|
||||
typescript: '>=3.3.1'
|
||||
@@ -4918,6 +4959,10 @@ packages:
|
||||
peerDependencies:
|
||||
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:
|
||||
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -4946,8 +4991,8 @@ packages:
|
||||
resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
get-tsconfig@4.10.1:
|
||||
resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==}
|
||||
get-tsconfig@4.13.0:
|
||||
resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==}
|
||||
|
||||
github-slugger@2.0.0:
|
||||
resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==}
|
||||
@@ -5168,9 +5213,6 @@ packages:
|
||||
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
import-in-the-middle@1.14.2:
|
||||
resolution: {integrity: sha512-5tCuY9BV8ujfOpwtAGgsTx9CGUapcFMEEyByLv1B+v2+6DhAcw+Zr0nhQT7uwaZ7DiourxFEscghOR8e1aPLQw==}
|
||||
|
||||
import-in-the-middle@2.0.0:
|
||||
resolution: {integrity: sha512-yNZhyQYqXpkT0AKq3F3KLasUSK4fHvebNH5hOsKQw2dhGSALvQ4U0BqUc5suziKvydO5u5hgN2hy1RJaho8U5A==}
|
||||
|
||||
@@ -5282,6 +5324,10 @@ packages:
|
||||
resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==}
|
||||
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:
|
||||
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -5903,8 +5949,8 @@ packages:
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
hasBin: true
|
||||
|
||||
napi-postinstall@0.3.3:
|
||||
resolution: {integrity: sha512-uTp172LLXSxuSYHv/kou+f6KW3SMppU9ivthaVTXian9sOt3XM/zHYHpRZiLgQoxeWfYUnslNWQHF1+G71xcow==}
|
||||
napi-postinstall@0.3.4:
|
||||
resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==}
|
||||
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
|
||||
hasBin: true
|
||||
|
||||
@@ -6769,6 +6815,11 @@ packages:
|
||||
engines: {node: '>= 0.4'}
|
||||
hasBin: true
|
||||
|
||||
resolve@1.22.11:
|
||||
resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
hasBin: true
|
||||
|
||||
resolve@1.22.8:
|
||||
resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
|
||||
hasBin: true
|
||||
@@ -7858,7 +7909,7 @@ snapshots:
|
||||
'@babel/helper-plugin-utils': 7.27.1
|
||||
debug: 4.4.3
|
||||
lodash.debounce: 4.0.8
|
||||
resolve: 1.22.10
|
||||
resolve: 1.22.11
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -8550,7 +8601,7 @@ snapshots:
|
||||
|
||||
'@date-fns/tz@1.4.1': {}
|
||||
|
||||
'@emnapi/core@1.5.0':
|
||||
'@emnapi/core@1.7.1':
|
||||
dependencies:
|
||||
'@emnapi/wasi-threads': 1.1.0
|
||||
tslib: 2.8.1
|
||||
@@ -8561,6 +8612,11 @@ snapshots:
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@emnapi/runtime@1.7.1':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@emnapi/wasi-threads@1.1.0':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
@@ -8739,6 +8795,8 @@ snapshots:
|
||||
|
||||
'@eslint-community/regexpp@4.12.1': {}
|
||||
|
||||
'@eslint-community/regexpp@4.12.2': {}
|
||||
|
||||
'@eslint/eslintrc@2.1.4':
|
||||
dependencies:
|
||||
ajv: 6.12.6
|
||||
@@ -8996,16 +9054,16 @@ snapshots:
|
||||
|
||||
'@napi-rs/wasm-runtime@0.2.12':
|
||||
dependencies:
|
||||
'@emnapi/core': 1.5.0
|
||||
'@emnapi/runtime': 1.5.0
|
||||
'@tybys/wasm-util': 0.10.0
|
||||
'@emnapi/core': 1.7.1
|
||||
'@emnapi/runtime': 1.7.1
|
||||
'@tybys/wasm-util': 0.10.1
|
||||
optional: true
|
||||
|
||||
'@neoconfetti/react@1.0.0': {}
|
||||
|
||||
'@next/env@15.4.10': {}
|
||||
|
||||
'@next/eslint-plugin-next@15.5.2':
|
||||
'@next/eslint-plugin-next@15.5.7':
|
||||
dependencies:
|
||||
fast-glob: 3.3.1
|
||||
|
||||
@@ -10115,7 +10173,7 @@ snapshots:
|
||||
|
||||
'@rtsao/scc@1.1.0': {}
|
||||
|
||||
'@rushstack/eslint-patch@1.12.0': {}
|
||||
'@rushstack/eslint-patch@1.15.0': {}
|
||||
|
||||
'@scarf/scarf@1.4.0': {}
|
||||
|
||||
@@ -10867,7 +10925,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@testing-library/dom': 10.4.1
|
||||
|
||||
'@tybys/wasm-util@0.10.0':
|
||||
'@tybys/wasm-util@0.10.1':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
@@ -11065,14 +11123,14 @@ snapshots:
|
||||
dependencies:
|
||||
'@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:
|
||||
'@eslint-community/regexpp': 4.12.1
|
||||
'@typescript-eslint/parser': 8.43.0(eslint@8.57.1)(typescript@5.9.3)
|
||||
'@typescript-eslint/scope-manager': 8.43.0
|
||||
'@typescript-eslint/type-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)
|
||||
'@typescript-eslint/visitor-keys': 8.43.0
|
||||
'@eslint-community/regexpp': 4.12.2
|
||||
'@typescript-eslint/parser': 8.48.1(eslint@8.57.1)(typescript@5.9.3)
|
||||
'@typescript-eslint/scope-manager': 8.48.1
|
||||
'@typescript-eslint/type-utils': 8.48.1(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.48.1
|
||||
eslint: 8.57.1
|
||||
graphemer: 1.4.0
|
||||
ignore: 7.0.5
|
||||
@@ -11082,12 +11140,12 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- 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:
|
||||
'@typescript-eslint/scope-manager': 8.43.0
|
||||
'@typescript-eslint/types': 8.43.0
|
||||
'@typescript-eslint/typescript-estree': 8.43.0(typescript@5.9.3)
|
||||
'@typescript-eslint/visitor-keys': 8.43.0
|
||||
'@typescript-eslint/scope-manager': 8.48.1
|
||||
'@typescript-eslint/types': 8.48.1
|
||||
'@typescript-eslint/typescript-estree': 8.48.1(typescript@5.9.3)
|
||||
'@typescript-eslint/visitor-keys': 8.48.1
|
||||
debug: 4.4.3
|
||||
eslint: 8.57.1
|
||||
typescript: 5.9.3
|
||||
@@ -11097,7 +11155,7 @@ snapshots:
|
||||
'@typescript-eslint/project-service@8.43.0(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@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
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
@@ -11106,7 +11164,16 @@ snapshots:
|
||||
'@typescript-eslint/project-service@8.46.2(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@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
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
@@ -11122,6 +11189,11 @@ snapshots:
|
||||
'@typescript-eslint/types': 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)':
|
||||
dependencies:
|
||||
typescript: 5.9.3
|
||||
@@ -11130,11 +11202,15 @@ snapshots:
|
||||
dependencies:
|
||||
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:
|
||||
'@typescript-eslint/types': 8.43.0
|
||||
'@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: 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
|
||||
eslint: 8.57.1
|
||||
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.48.1': {}
|
||||
|
||||
'@typescript-eslint/typescript-estree@8.43.0(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@typescript-eslint/project-service': 8.43.0(typescript@5.9.3)
|
||||
@@ -11156,7 +11234,7 @@ snapshots:
|
||||
fast-glob: 3.3.3
|
||||
is-glob: 4.0.3
|
||||
minimatch: 9.0.5
|
||||
semver: 7.7.2
|
||||
semver: 7.7.3
|
||||
ts-api-utils: 2.1.0(typescript@5.9.3)
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
@@ -11178,6 +11256,21 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- 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)':
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.9.0(eslint@8.57.1)
|
||||
@@ -11200,6 +11293,17 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- 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':
|
||||
dependencies:
|
||||
'@typescript-eslint/types': 8.43.0
|
||||
@@ -11210,6 +11314,11 @@ snapshots:
|
||||
'@typescript-eslint/types': 8.46.2
|
||||
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': {}
|
||||
|
||||
'@unrs/resolver-binding-android-arm-eabi@1.11.1':
|
||||
@@ -12532,16 +12641,16 @@ snapshots:
|
||||
|
||||
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:
|
||||
'@next/eslint-plugin-next': 15.5.2
|
||||
'@rushstack/eslint-patch': 1.12.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/parser': 8.43.0(eslint@8.57.1)(typescript@5.9.3)
|
||||
'@next/eslint-plugin-next': 15.5.7
|
||||
'@rushstack/eslint-patch': 1.15.0
|
||||
'@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.48.1(eslint@8.57.1)(typescript@5.9.3)
|
||||
eslint: 8.57.1
|
||||
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-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-react: 7.37.5(eslint@8.57.1)
|
||||
eslint-plugin-react-hooks: 5.2.0(eslint@8.57.1)
|
||||
@@ -12556,7 +12665,7 @@ snapshots:
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
is-core-module: 2.16.1
|
||||
resolve: 1.22.10
|
||||
resolve: 1.22.11
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -12565,28 +12674,28 @@ snapshots:
|
||||
'@nolyfill/is-core-module': 1.0.39
|
||||
debug: 4.4.3
|
||||
eslint: 8.57.1
|
||||
get-tsconfig: 4.10.1
|
||||
get-tsconfig: 4.13.0
|
||||
is-bun-module: 2.0.0
|
||||
stable-hash: 0.0.5
|
||||
tinyglobby: 0.2.15
|
||||
unrs-resolver: 1.11.1
|
||||
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:
|
||||
- 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:
|
||||
debug: 3.2.7
|
||||
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-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1)
|
||||
transitivePeerDependencies:
|
||||
- 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:
|
||||
'@rtsao/scc': 1.1.0
|
||||
array-includes: 3.1.9
|
||||
@@ -12597,7 +12706,7 @@ snapshots:
|
||||
doctrine: 2.1.0
|
||||
eslint: 8.57.1
|
||||
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
|
||||
is-core-module: 2.16.1
|
||||
is-glob: 4.0.3
|
||||
@@ -12609,7 +12718,7 @@ snapshots:
|
||||
string.prototype.trimend: 1.0.9
|
||||
tsconfig-paths: 3.15.0
|
||||
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:
|
||||
- eslint-import-resolver-typescript
|
||||
- eslint-import-resolver-webpack
|
||||
@@ -12958,6 +13067,8 @@ snapshots:
|
||||
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)
|
||||
|
||||
generator-function@2.0.1: {}
|
||||
|
||||
gensync@1.0.0-beta.2: {}
|
||||
|
||||
get-caller-file@2.0.5: {}
|
||||
@@ -12990,7 +13101,7 @@ snapshots:
|
||||
es-errors: 1.3.0
|
||||
get-intrinsic: 1.3.0
|
||||
|
||||
get-tsconfig@4.10.1:
|
||||
get-tsconfig@4.13.0:
|
||||
dependencies:
|
||||
resolve-pkg-maps: 1.0.0
|
||||
|
||||
@@ -13274,13 +13385,6 @@ snapshots:
|
||||
parent-module: 1.0.1
|
||||
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:
|
||||
dependencies:
|
||||
acorn: 8.15.0
|
||||
@@ -13357,7 +13461,7 @@ snapshots:
|
||||
|
||||
is-bun-module@2.0.0:
|
||||
dependencies:
|
||||
semver: 7.7.2
|
||||
semver: 7.7.3
|
||||
|
||||
is-callable@1.2.7: {}
|
||||
|
||||
@@ -13395,6 +13499,14 @@ snapshots:
|
||||
has-tostringtag: 1.0.2
|
||||
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:
|
||||
dependencies:
|
||||
is-extglob: 2.1.1
|
||||
@@ -14215,7 +14327,7 @@ snapshots:
|
||||
|
||||
nanoid@3.3.11: {}
|
||||
|
||||
napi-postinstall@0.3.3: {}
|
||||
napi-postinstall@0.3.4: {}
|
||||
|
||||
natural-compare@1.4.0: {}
|
||||
|
||||
@@ -15185,6 +15297,12 @@ snapshots:
|
||||
path-parse: 1.0.7
|
||||
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:
|
||||
dependencies:
|
||||
is-core-module: 2.16.1
|
||||
@@ -15996,7 +16114,7 @@ snapshots:
|
||||
|
||||
unrs-resolver@1.11.1:
|
||||
dependencies:
|
||||
napi-postinstall: 0.3.3
|
||||
napi-postinstall: 0.3.4
|
||||
optionalDependencies:
|
||||
'@unrs/resolver-binding-android-arm-eabi': 1.11.1
|
||||
'@unrs/resolver-binding-android-arm64': 1.11.1
|
||||
@@ -16224,7 +16342,7 @@ snapshots:
|
||||
is-async-function: 2.1.1
|
||||
is-date-object: 1.1.0
|
||||
is-finalizationregistry: 1.1.1
|
||||
is-generator-function: 1.1.0
|
||||
is-generator-function: 1.1.2
|
||||
is-regex: 1.2.1
|
||||
is-weakref: 1.1.1
|
||||
isarray: 2.0.5
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
CardTitle,
|
||||
} from "@/components/__legacy__/ui/card";
|
||||
import { ErrorCard } from "@/components/molecules/ErrorCard/ErrorCard";
|
||||
import { InformationTooltip } from "@/components/molecules/InformationTooltip/InformationTooltip";
|
||||
import { CircleNotchIcon } from "@phosphor-icons/react/dist/ssr";
|
||||
import { Play } from "lucide-react";
|
||||
import OnboardingButton from "../components/OnboardingButton";
|
||||
@@ -79,20 +78,13 @@ export default function Page() {
|
||||
<CardContent className="flex flex-col gap-4">
|
||||
{Object.entries(agent?.input_schema.properties || {}).map(
|
||||
([key, inputSubSchema]) => (
|
||||
<div key={key} className="flex flex-col space-y-2">
|
||||
<label className="flex items-center gap-1 text-sm font-medium">
|
||||
{inputSubSchema.title || key}
|
||||
<InformationTooltip
|
||||
description={inputSubSchema.description}
|
||||
/>
|
||||
</label>
|
||||
<RunAgentInputs
|
||||
schema={inputSubSchema}
|
||||
value={onboarding.state?.agentInput?.[key]}
|
||||
placeholder={inputSubSchema.description}
|
||||
onChange={(value) => handleSetAgentInput(key, value)}
|
||||
/>
|
||||
</div>
|
||||
<RunAgentInputs
|
||||
key={key}
|
||||
schema={inputSubSchema}
|
||||
value={onboarding.state?.agentInput?.[key]}
|
||||
placeholder={inputSubSchema.description}
|
||||
onChange={(value) => handleSetAgentInput(key, value)}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
<AgentOnboardingCredentials
|
||||
|
||||
@@ -20,6 +20,7 @@ import { AgentExecutionStatus } from "@/app/api/__generated__/models/agentExecut
|
||||
|
||||
export const useFlow = () => {
|
||||
const [isLocked, setIsLocked] = useState(false);
|
||||
const [hasAutoFramed, setHasAutoFramed] = useState(false);
|
||||
const addNodes = useNodeStore(useShallow((state) => state.addNodes));
|
||||
const addLinks = useEdgeStore(useShallow((state) => state.addLinks));
|
||||
const updateNodeStatus = useNodeStore(
|
||||
@@ -187,9 +188,36 @@ export const useFlow = () => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const linkCount = graph?.links?.length ?? 0;
|
||||
|
||||
useEffect(() => {
|
||||
fitView({ padding: 0.2, duration: 800, maxZoom: 2 });
|
||||
}, [fitView]);
|
||||
if (isGraphLoading || isBlocksLoading) {
|
||||
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
|
||||
const onDragOver = useCallback((event: React.DragEvent) => {
|
||||
|
||||
@@ -106,7 +106,11 @@ export const CustomNode: React.FC<NodeProps<CustomNode>> = React.memo(
|
||||
/>
|
||||
<NodeAdvancedToggle nodeId={nodeId} />
|
||||
{data.uiType != BlockUIType.OUTPUT && (
|
||||
<OutputHandler outputSchema={outputSchema} nodeId={nodeId} />
|
||||
<OutputHandler
|
||||
uiType={data.uiType}
|
||||
outputSchema={outputSchema}
|
||||
nodeId={nodeId}
|
||||
/>
|
||||
)}
|
||||
<NodeDataRenderer nodeId={nodeId} />
|
||||
</div>
|
||||
|
||||
@@ -20,17 +20,32 @@ export const FormCreator = React.memo(
|
||||
className?: string;
|
||||
}) => {
|
||||
const updateNodeData = useNodeStore((state) => state.updateNodeData);
|
||||
|
||||
const getHardCodedValues = useNodeStore(
|
||||
(state) => state.getHardCodedValues,
|
||||
);
|
||||
|
||||
const handleChange = ({ formData }: any) => {
|
||||
if ("credentials" in formData && !formData.credentials?.id) {
|
||||
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 (
|
||||
<div className={className}>
|
||||
|
||||
@@ -14,13 +14,16 @@ import {
|
||||
import { useEdgeStore } from "@/app/(platform)/build/stores/edgeStore";
|
||||
import { getTypeDisplayInfo } from "./helpers";
|
||||
import { generateHandleId } from "../handlers/helpers";
|
||||
import { BlockUIType } from "../../types";
|
||||
|
||||
export const OutputHandler = ({
|
||||
outputSchema,
|
||||
nodeId,
|
||||
uiType,
|
||||
}: {
|
||||
outputSchema: RJSFSchema;
|
||||
nodeId: string;
|
||||
uiType: BlockUIType;
|
||||
}) => {
|
||||
const { isOutputConnected } = useEdgeStore();
|
||||
const properties = outputSchema?.properties || {};
|
||||
@@ -79,7 +82,9 @@ export const OutputHandler = ({
|
||||
</Text>
|
||||
|
||||
<NodeHandle
|
||||
handleId={generateHandleId(key)}
|
||||
handleId={
|
||||
uiType === BlockUIType.AGENT ? key : generateHandleId(key)
|
||||
}
|
||||
isConnected={isConnected}
|
||||
side="right"
|
||||
/>
|
||||
|
||||
@@ -7,6 +7,7 @@ import { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
|
||||
import { getV2GetSpecificAgent } from "@/app/api/__generated__/endpoints/store/store";
|
||||
import {
|
||||
getGetV2ListLibraryAgentsQueryKey,
|
||||
getV2GetLibraryAgent,
|
||||
usePostV2AddMarketplaceAgent,
|
||||
} from "@/app/api/__generated__/endpoints/library/library";
|
||||
import {
|
||||
@@ -151,7 +152,12 @@ export const useBlockMenuSearch = () => {
|
||||
});
|
||||
|
||||
const libraryAgent = response.data as LibraryAgent;
|
||||
addAgentToBuilder(libraryAgent);
|
||||
|
||||
const { data: libraryAgentDetails } = await getV2GetLibraryAgent(
|
||||
libraryAgent.id,
|
||||
);
|
||||
|
||||
addAgentToBuilder(libraryAgentDetails as LibraryAgent);
|
||||
|
||||
toast({
|
||||
title: "Agent Added",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { getGetV2GetBuilderItemCountsQueryKey } from "@/app/api/__generated__/endpoints/default/default";
|
||||
import {
|
||||
getGetV2ListLibraryAgentsQueryKey,
|
||||
getV2GetLibraryAgent,
|
||||
usePostV2AddMarketplaceAgent,
|
||||
} from "@/app/api/__generated__/endpoints/library/library";
|
||||
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;
|
||||
addAgentToBuilder(libraryAgent);
|
||||
|
||||
const { data: libraryAgentDetails } = await getV2GetLibraryAgent(
|
||||
libraryAgent.id,
|
||||
);
|
||||
|
||||
addAgentToBuilder(libraryAgentDetails as LibraryAgent);
|
||||
|
||||
toast({
|
||||
title: "Agent Added",
|
||||
|
||||
@@ -103,6 +103,7 @@ const FlowEditor: React.FC<{
|
||||
updateNode,
|
||||
getViewport,
|
||||
setViewport,
|
||||
fitView,
|
||||
screenToFlowPosition,
|
||||
} = useReactFlow<CustomNode, CustomEdge>();
|
||||
const [nodeId, setNodeId] = useState<number>(1);
|
||||
@@ -115,6 +116,7 @@ const FlowEditor: React.FC<{
|
||||
const [pinBlocksPopover, setPinBlocksPopover] = useState(false);
|
||||
// State to control if save popover should be pinned open
|
||||
const [pinSavePopover, setPinSavePopover] = useState(false);
|
||||
const [hasAutoFramed, setHasAutoFramed] = useState(false);
|
||||
|
||||
const {
|
||||
agentName,
|
||||
@@ -482,35 +484,26 @@ const FlowEditor: React.FC<{
|
||||
return uuidv4();
|
||||
}, []);
|
||||
|
||||
// Set the initial view port to center the canvas.
|
||||
useEffect(() => {
|
||||
const { x, y } = getViewport();
|
||||
if (nodes.length <= 0 || x !== 0 || y !== 0) {
|
||||
if (nodes.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const topLeft = { x: Infinity, y: Infinity };
|
||||
const bottomRight = { x: -Infinity, y: -Infinity };
|
||||
if (hasAutoFramed) {
|
||||
return;
|
||||
}
|
||||
|
||||
nodes.forEach((node) => {
|
||||
const { x, y } = node.position;
|
||||
topLeft.x = Math.min(topLeft.x, x);
|
||||
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 rafId = requestAnimationFrame(() => {
|
||||
fitView({ padding: 0.2, duration: 800, maxZoom: 1 });
|
||||
setHasAutoFramed(true);
|
||||
});
|
||||
|
||||
const centerX = (topLeft.x + bottomRight.x) / 2;
|
||||
const centerY = (topLeft.y + bottomRight.y) / 2;
|
||||
const zoom = 0.8;
|
||||
return () => cancelAnimationFrame(rafId);
|
||||
}, [fitView, hasAutoFramed, nodes.length]);
|
||||
|
||||
setViewport({
|
||||
x: window.innerWidth / 2 - centerX * zoom,
|
||||
y: window.innerHeight / 2 - centerY * zoom,
|
||||
zoom: zoom,
|
||||
});
|
||||
}, [nodes, getViewport, setViewport]);
|
||||
useEffect(() => {
|
||||
setHasAutoFramed(false);
|
||||
}, [flowID, flowVersion]);
|
||||
|
||||
const navigateToNode = useCallback(
|
||||
(nodeId: string) => {
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import type { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
|
||||
import type {
|
||||
BlockIOSubSchema,
|
||||
CredentialsMetaInput,
|
||||
} from "@/lib/autogpt-server-api/types";
|
||||
import { Text } from "@/components/atoms/Text/Text";
|
||||
import type { CredentialsMetaInput } from "@/lib/autogpt-server-api/types";
|
||||
import { CredentialsInput } from "../CredentialsInputs/CredentialsInputs";
|
||||
import {
|
||||
getAgentCredentialsFields,
|
||||
getAgentInputFields,
|
||||
renderValue,
|
||||
} from "./helpers";
|
||||
import { RunAgentInputs } from "../RunAgentInputs/RunAgentInputs";
|
||||
import { getAgentCredentialsFields, getAgentInputFields } from "./helpers";
|
||||
|
||||
type Props = {
|
||||
agent: LibraryAgent;
|
||||
@@ -28,19 +23,23 @@ export function AgentInputsReadOnly({
|
||||
getAgentCredentialsFields(agent),
|
||||
);
|
||||
|
||||
// Take actual input entries as leading; augment with schema from input fields.
|
||||
// TODO: ensure consistent ordering.
|
||||
const inputEntries =
|
||||
inputs &&
|
||||
Object.entries(inputs).map<[string, [BlockIOSubSchema | undefined, any]]>(
|
||||
([k, v]) => [k, [inputFields[k], v]],
|
||||
);
|
||||
Object.entries(inputs).map(([key, value]) => ({
|
||||
key,
|
||||
schema: inputFields[key],
|
||||
value,
|
||||
}));
|
||||
|
||||
const hasInputs = inputEntries && inputEntries.length > 0;
|
||||
const hasCredentials = credentialInputs && credentialFieldEntries.length > 0;
|
||||
|
||||
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 (
|
||||
@@ -48,16 +47,20 @@ export function AgentInputsReadOnly({
|
||||
{/* Regular inputs */}
|
||||
{hasInputs && (
|
||||
<div className="flex flex-col gap-4">
|
||||
{inputEntries.map(([key, [schema, value]]) => (
|
||||
<div key={key} className="flex flex-col gap-1.5">
|
||||
<label className="text-sm font-medium">
|
||||
{schema?.title || key}
|
||||
</label>
|
||||
<p className="whitespace-pre-wrap break-words text-sm text-neutral-700">
|
||||
{renderValue(value)}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
{inputEntries.map(({ key, schema, value }) => {
|
||||
if (!schema) return null;
|
||||
|
||||
return (
|
||||
<RunAgentInputs
|
||||
key={key}
|
||||
schema={schema}
|
||||
value={value}
|
||||
placeholder={schema.description}
|
||||
onChange={() => {}}
|
||||
readOnly={true}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -62,12 +62,15 @@ export function CredentialRow({
|
||||
</div>
|
||||
<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">
|
||||
<Text variant="body" className="tracking-tight">
|
||||
<Text
|
||||
variant="body"
|
||||
className="line-clamp-1 flex-[0_0_50%] text-ellipsis tracking-tight"
|
||||
>
|
||||
{getCredentialDisplayName(credential, displayName)}
|
||||
</Text>
|
||||
<Text
|
||||
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)}
|
||||
</Text>
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Button } from "@/components/atoms/Button/Button";
|
||||
import { FileInput } from "@/components/atoms/FileInput/FileInput";
|
||||
import { Switch } from "@/components/atoms/Switch/Switch";
|
||||
import { GoogleDrivePickerInput } from "@/components/contextual/GoogleDrivePicker/GoogleDrivePickerInput";
|
||||
import { InformationTooltip } from "@/components/molecules/InformationTooltip/InformationTooltip";
|
||||
import { TimePicker } from "@/components/molecules/TimePicker/TimePicker";
|
||||
import {
|
||||
BlockIOObjectSubSchema,
|
||||
@@ -32,6 +33,7 @@ interface Props {
|
||||
value?: any;
|
||||
placeholder?: string;
|
||||
onChange: (value: any) => void;
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -44,6 +46,7 @@ export function RunAgentInputs({
|
||||
value,
|
||||
placeholder,
|
||||
onChange,
|
||||
readOnly = false,
|
||||
...props
|
||||
}: Props & React.HTMLAttributes<HTMLElement>) {
|
||||
const { handleUploadFile, uploadProgress } = useRunAgentInputs();
|
||||
@@ -62,7 +65,6 @@ export function RunAgentInputs({
|
||||
id={`${baseId}-number`}
|
||||
label={schema.title ?? placeholder ?? "Number"}
|
||||
hideLabel
|
||||
size="small"
|
||||
type="number"
|
||||
value={value ?? ""}
|
||||
placeholder={placeholder || "Enter number"}
|
||||
@@ -80,7 +82,6 @@ export function RunAgentInputs({
|
||||
id={`${baseId}-textarea`}
|
||||
label={schema.title ?? placeholder ?? "Text"}
|
||||
hideLabel
|
||||
size="small"
|
||||
type="textarea"
|
||||
rows={3}
|
||||
value={value ?? ""}
|
||||
@@ -102,7 +103,7 @@ export function RunAgentInputs({
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
className="w-full"
|
||||
showRemoveButton={false}
|
||||
showRemoveButton={!readOnly}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
@@ -130,7 +131,6 @@ export function RunAgentInputs({
|
||||
id={`${baseId}-date`}
|
||||
label={schema.title ?? placeholder ?? "Date"}
|
||||
hideLabel
|
||||
size="small"
|
||||
type="date"
|
||||
value={value ? format(value as Date, "yyyy-MM-dd") : ""}
|
||||
onChange={(e) => {
|
||||
@@ -159,7 +159,6 @@ export function RunAgentInputs({
|
||||
id={`${baseId}-datetime`}
|
||||
label={schema.title ?? placeholder ?? "Date time"}
|
||||
hideLabel
|
||||
size="small"
|
||||
type="datetime-local"
|
||||
value={value ?? ""}
|
||||
onChange={(e) => onChange((e.target as HTMLInputElement).value)}
|
||||
@@ -194,7 +193,6 @@ export function RunAgentInputs({
|
||||
label={schema.title ?? placeholder ?? "Select"}
|
||||
hideLabel
|
||||
value={value ?? ""}
|
||||
size="small"
|
||||
onValueChange={(val: string) => onChange(val)}
|
||||
placeholder={placeholder || "Select an option"}
|
||||
options={schema.enum
|
||||
@@ -217,7 +215,6 @@ export function RunAgentInputs({
|
||||
items={allKeys.map((key) => ({
|
||||
value: key,
|
||||
label: _schema.properties[key]?.title ?? key,
|
||||
size: "small",
|
||||
}))}
|
||||
selectedValues={selectedValues}
|
||||
onChange={(values: string[]) =>
|
||||
@@ -336,7 +333,6 @@ export function RunAgentInputs({
|
||||
id={`${baseId}-text`}
|
||||
label={schema.title ?? placeholder ?? "Text"}
|
||||
hideLabel
|
||||
size="small"
|
||||
type="text"
|
||||
value={value ?? ""}
|
||||
onChange={(e) => onChange((e.target as HTMLInputElement).value)}
|
||||
@@ -347,6 +343,17 @@ export function RunAgentInputs({
|
||||
}
|
||||
|
||||
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"
|
||||
subtitle="Enter the information you want to provide to the agent for this task"
|
||||
>
|
||||
{/* Regular inputs */}
|
||||
{inputFields.map(([key, inputSubSchema]) => (
|
||||
<div key={key} className="flex w-full flex-col gap-0 space-y-2">
|
||||
<label className="flex items-center gap-1 text-sm font-medium">
|
||||
{inputSubSchema.title || key}
|
||||
<InformationTooltip description={inputSubSchema.description} />
|
||||
</label>
|
||||
|
||||
<RunAgentInputs
|
||||
schema={inputSubSchema}
|
||||
value={inputValues[key] ?? inputSubSchema.default}
|
||||
placeholder={inputSubSchema.description}
|
||||
onChange={(value) => setInputValue(key, value)}
|
||||
data-testid={`agent-input-${key}`}
|
||||
/>
|
||||
</div>
|
||||
<RunAgentInputs
|
||||
key={key}
|
||||
schema={inputSubSchema}
|
||||
value={inputValues[key] ?? inputSubSchema.default}
|
||||
placeholder={inputSubSchema.description}
|
||||
onChange={(value) => setInputValue(key, value)}
|
||||
data-testid={`agent-input-${key}`}
|
||||
/>
|
||||
))}
|
||||
</ModalSection>
|
||||
) : null}
|
||||
|
||||
@@ -4,20 +4,19 @@ import { AgentExecutionStatus } from "@/app/api/__generated__/models/agentExecut
|
||||
import type { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
|
||||
import { LoadingSpinner } from "@/components/atoms/LoadingSpinner/LoadingSpinner";
|
||||
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 { InformationTooltip } from "@/components/molecules/InformationTooltip/InformationTooltip";
|
||||
import {
|
||||
ScrollableTabs,
|
||||
ScrollableTabsContent,
|
||||
ScrollableTabsList,
|
||||
ScrollableTabsTrigger,
|
||||
} from "@/components/molecules/ScrollableTabs/ScrollableTabs";
|
||||
import { PendingReviewsList } from "@/components/organisms/PendingReviewsList/PendingReviewsList";
|
||||
import { usePendingReviewsForExecution } from "@/hooks/usePendingReviews";
|
||||
import { isLargeScreen, useBreakpoint } from "@/lib/hooks/useBreakpoint";
|
||||
import { InfoIcon } from "@phosphor-icons/react";
|
||||
import { useEffect } from "react";
|
||||
import { AgentInputsReadOnly } from "../../modals/AgentInputsReadOnly/AgentInputsReadOnly";
|
||||
import { AnchorLinksWrap } from "../AnchorLinksWrap";
|
||||
import { LoadingSelectedContent } from "../LoadingSelectedContent";
|
||||
import { RunDetailCard } from "../RunDetailCard/RunDetailCard";
|
||||
import { RunDetailHeader } from "../RunDetailHeader/RunDetailHeader";
|
||||
@@ -28,9 +27,6 @@ import { SelectedRunActions } from "./components/SelectedRunActions/SelectedRunA
|
||||
import { WebhookTriggerSection } from "./components/WebhookTriggerSection";
|
||||
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 {
|
||||
agent: LibraryAgent;
|
||||
runId: string;
|
||||
@@ -65,13 +61,6 @@ export function SelectedRunView({
|
||||
const withSummary = run?.stats?.activity_status;
|
||||
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) {
|
||||
return (
|
||||
<ErrorCard
|
||||
@@ -112,118 +101,116 @@ export function SelectedRunView({
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Navigation Links */}
|
||||
<AnchorLinksWrap>
|
||||
{withSummary && (
|
||||
<button
|
||||
onClick={() => scrollToSection("summary")}
|
||||
className={anchorStyles}
|
||||
>
|
||||
Summary
|
||||
</button>
|
||||
)}
|
||||
<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>
|
||||
<ScrollableTabs
|
||||
defaultValue="output"
|
||||
className="-mt-2 flex flex-col"
|
||||
>
|
||||
<ScrollableTabsList className="px-4">
|
||||
{withSummary && (
|
||||
<ScrollableTabsTrigger value="summary">
|
||||
Summary
|
||||
</ScrollableTabsTrigger>
|
||||
)}
|
||||
</RunDetailCard>
|
||||
</div>
|
||||
|
||||
{/* Input Section */}
|
||||
<div id="input" className="scroll-mt-4">
|
||||
<RunDetailCard title="Your input">
|
||||
<AgentInputsReadOnly
|
||||
agent={agent}
|
||||
inputs={run?.inputs}
|
||||
credentialInputs={run?.credential_inputs}
|
||||
/>
|
||||
</RunDetailCard>
|
||||
</div>
|
||||
|
||||
{/* Reviews Section */}
|
||||
{withReviews && (
|
||||
<div id="reviews" className="scroll-mt-4">
|
||||
<RunDetailCard>
|
||||
{reviewsLoading ? (
|
||||
<div className="text-neutral-500">Loading reviews…</div>
|
||||
) : pendingReviews.length > 0 ? (
|
||||
<PendingReviewsList
|
||||
reviews={pendingReviews}
|
||||
onReviewComplete={refetchReviews}
|
||||
emptyMessage="No pending reviews for this execution"
|
||||
/>
|
||||
) : (
|
||||
<div className="text-neutral-600">
|
||||
No pending reviews for this execution
|
||||
<ScrollableTabsTrigger value="output">
|
||||
Output
|
||||
</ScrollableTabsTrigger>
|
||||
<ScrollableTabsTrigger value="input">
|
||||
Your input
|
||||
</ScrollableTabsTrigger>
|
||||
{withReviews && (
|
||||
<ScrollableTabsTrigger value="reviews">
|
||||
Reviews ({pendingReviews.length})
|
||||
</ScrollableTabsTrigger>
|
||||
)}
|
||||
</ScrollableTabsList>
|
||||
<div className="my-6 flex flex-col gap-6">
|
||||
{/* Summary Section */}
|
||||
{withSummary && (
|
||||
<ScrollableTabsContent value="summary">
|
||||
<div className="scroll-mt-4">
|
||||
<RunDetailCard
|
||||
title={
|
||||
<div className="flex items-center gap-1">
|
||||
<Text variant="lead-semibold">Summary</Text>
|
||||
<InformationTooltip
|
||||
iconSize={20}
|
||||
description="This AI-generated summary describes how the agent handled your task. It's an experimental feature and may occasionally be inaccurate."
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<RunSummary run={run} />
|
||||
</RunDetailCard>
|
||||
</div>
|
||||
)}
|
||||
</RunDetailCard>
|
||||
</ScrollableTabsContent>
|
||||
)}
|
||||
|
||||
{/* 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>
|
||||
)}
|
||||
</ScrollableTabs>
|
||||
</div>
|
||||
</SelectedViewLayout>
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,6 @@ import { humanizeCronExpression } from "@/lib/cron-expression-utils";
|
||||
import { isLargeScreen, useBreakpoint } from "@/lib/hooks/useBreakpoint";
|
||||
import { formatInTimezone, getTimezoneDisplayName } from "@/lib/timezone-utils";
|
||||
import { AgentInputsReadOnly } from "../../modals/AgentInputsReadOnly/AgentInputsReadOnly";
|
||||
import { AnchorLinksWrap } from "../AnchorLinksWrap";
|
||||
import { LoadingSelectedContent } from "../LoadingSelectedContent";
|
||||
import { RunDetailCard } from "../RunDetailCard/RunDetailCard";
|
||||
import { RunDetailHeader } from "../RunDetailHeader/RunDetailHeader";
|
||||
@@ -17,9 +16,6 @@ import { SelectedViewLayout } from "../SelectedViewLayout";
|
||||
import { SelectedScheduleActions } from "./components/SelectedScheduleActions";
|
||||
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 {
|
||||
agent: LibraryAgent;
|
||||
scheduleId: string;
|
||||
@@ -45,13 +41,6 @@ export function SelectedScheduleView({
|
||||
const breakpoint = useBreakpoint();
|
||||
const isLgScreenUp = isLargeScreen(breakpoint);
|
||||
|
||||
function scrollToSection(id: string) {
|
||||
const element = document.getElementById(id);
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
}
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<ErrorCard
|
||||
@@ -108,22 +97,6 @@ export function SelectedScheduleView({
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{/* Navigation Links */}
|
||||
<AnchorLinksWrap>
|
||||
<button
|
||||
onClick={() => scrollToSection("schedule")}
|
||||
className={anchorStyles}
|
||||
>
|
||||
Schedule
|
||||
</button>
|
||||
<button
|
||||
onClick={() => scrollToSection("input")}
|
||||
className={anchorStyles}
|
||||
>
|
||||
Your input
|
||||
</button>
|
||||
</AnchorLinksWrap>
|
||||
|
||||
{/* Schedule Section */}
|
||||
<div id="schedule" className="scroll-mt-4">
|
||||
<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
|
||||
variant="icon"
|
||||
size="icon"
|
||||
aria-label="Open in builder"
|
||||
as="NextLink"
|
||||
href={openInBuilderHref}
|
||||
target="_blank"
|
||||
aria-label="View scheduled task details"
|
||||
>
|
||||
<EyeIcon weight="bold" size={18} className="text-zinc-700" />
|
||||
</Button>
|
||||
|
||||
@@ -4,7 +4,6 @@ import type { GraphExecutionMeta } from "@/app/api/__generated__/models/graphExe
|
||||
import type { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
|
||||
import { Input } from "@/components/atoms/Input/Input";
|
||||
import { ErrorCard } from "@/components/molecules/ErrorCard/ErrorCard";
|
||||
import { InformationTooltip } from "@/components/molecules/InformationTooltip/InformationTooltip";
|
||||
import {
|
||||
getAgentCredentialsFields,
|
||||
getAgentInputFields,
|
||||
@@ -138,25 +137,13 @@ export function SelectedTemplateView({
|
||||
<RunDetailCard title="Your Input">
|
||||
<div className="flex flex-col gap-4">
|
||||
{inputFields.map(([key, inputSubSchema]) => (
|
||||
<div
|
||||
<RunAgentInputs
|
||||
key={key}
|
||||
className="flex w-full flex-col gap-0 space-y-2"
|
||||
>
|
||||
<label className="flex items-center gap-1 text-sm font-medium">
|
||||
{inputSubSchema.title || key}
|
||||
{inputSubSchema.description && (
|
||||
<InformationTooltip
|
||||
description={inputSubSchema.description}
|
||||
/>
|
||||
)}
|
||||
</label>
|
||||
<RunAgentInputs
|
||||
schema={inputSubSchema}
|
||||
value={inputs[key] ?? inputSubSchema.default}
|
||||
placeholder={inputSubSchema.description}
|
||||
onChange={(value) => setInputValue(key, value)}
|
||||
/>
|
||||
</div>
|
||||
schema={inputSubSchema}
|
||||
value={inputs[key] ?? inputSubSchema.default}
|
||||
placeholder={inputSubSchema.description}
|
||||
onChange={(value) => setInputValue(key, value)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</RunDetailCard>
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import type { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
|
||||
import { Input } from "@/components/atoms/Input/Input";
|
||||
import { ErrorCard } from "@/components/molecules/ErrorCard/ErrorCard";
|
||||
import { InformationTooltip } from "@/components/molecules/InformationTooltip/InformationTooltip";
|
||||
import {
|
||||
getAgentCredentialsFields,
|
||||
getAgentInputFields,
|
||||
@@ -131,25 +130,13 @@ export function SelectedTriggerView({
|
||||
<RunDetailCard title="Your Input">
|
||||
<div className="flex flex-col gap-4">
|
||||
{inputFields.map(([key, inputSubSchema]) => (
|
||||
<div
|
||||
<RunAgentInputs
|
||||
key={key}
|
||||
className="flex w-full flex-col gap-0 space-y-2"
|
||||
>
|
||||
<label className="flex items-center gap-1 text-sm font-medium">
|
||||
{inputSubSchema.title || key}
|
||||
{inputSubSchema.description && (
|
||||
<InformationTooltip
|
||||
description={inputSubSchema.description}
|
||||
/>
|
||||
)}
|
||||
</label>
|
||||
<RunAgentInputs
|
||||
schema={inputSubSchema}
|
||||
value={inputs[key] ?? inputSubSchema.default}
|
||||
placeholder={inputSubSchema.description}
|
||||
onChange={(value) => setInputValue(key, value)}
|
||||
/>
|
||||
</div>
|
||||
schema={inputSubSchema}
|
||||
value={inputs[key] ?? inputSubSchema.default}
|
||||
placeholder={inputSubSchema.description}
|
||||
onChange={(value) => setInputValue(key, value)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</RunDetailCard>
|
||||
|
||||
@@ -680,28 +680,20 @@ export function AgentRunDraftView({
|
||||
|
||||
{/* Regular inputs */}
|
||||
{Object.entries(agentInputFields).map(([key, inputSubSchema]) => (
|
||||
<div key={key} className="flex flex-col space-y-2">
|
||||
<label className="flex items-center gap-1 text-sm font-medium">
|
||||
{inputSubSchema.title || key}
|
||||
<InformationTooltip
|
||||
description={inputSubSchema.description}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<RunAgentInputs
|
||||
schema={inputSubSchema}
|
||||
value={inputValues[key] ?? inputSubSchema.default}
|
||||
placeholder={inputSubSchema.description}
|
||||
onChange={(value) => {
|
||||
setInputValues((obj) => ({
|
||||
...obj,
|
||||
[key]: value,
|
||||
}));
|
||||
setChangedPresetAttributes((prev) => prev.add("inputs"));
|
||||
}}
|
||||
data-testid={`agent-input-${key}`}
|
||||
/>
|
||||
</div>
|
||||
<RunAgentInputs
|
||||
key={key}
|
||||
schema={inputSubSchema}
|
||||
value={inputValues[key] ?? inputSubSchema.default}
|
||||
placeholder={inputSubSchema.description}
|
||||
onChange={(value) => {
|
||||
setInputValues((obj) => ({
|
||||
...obj,
|
||||
[key]: value,
|
||||
}));
|
||||
setChangedPresetAttributes((prev) => prev.add("inputs"));
|
||||
}}
|
||||
data-testid={`agent-input-${key}`}
|
||||
/>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -6,6 +6,10 @@ import {
|
||||
import { environment } from "@/services/environment";
|
||||
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 {
|
||||
const backendPath = path.join("/");
|
||||
return `${environment.getAGPTServerBaseUrl()}/${backendPath}${queryString}`;
|
||||
|
||||
@@ -1,36 +1,33 @@
|
||||
"use client";
|
||||
|
||||
import { LaunchDarklyProvider } from "@/services/feature-flags/feature-flag-provider";
|
||||
import OnboardingProvider from "@/providers/onboarding/onboarding-provider";
|
||||
import { TooltipProvider } from "@/components/atoms/Tooltip/BaseTooltip";
|
||||
import { SentryUserTracker } from "@/components/monitor/SentryUserTracker";
|
||||
import { BackendAPIProvider } from "@/lib/autogpt-server-api/context";
|
||||
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 { 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) {
|
||||
const queryClient = getQueryClient();
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<NuqsAdapter>
|
||||
<NextThemesProvider {...props}>
|
||||
<BackendAPIProvider>
|
||||
<SentryUserTracker />
|
||||
<CredentialsProvider>
|
||||
<LaunchDarklyProvider>
|
||||
<OnboardingProvider>
|
||||
<BackendAPIProvider>
|
||||
<SentryUserTracker />
|
||||
<CredentialsProvider>
|
||||
<LaunchDarklyProvider>
|
||||
<OnboardingProvider>
|
||||
<ThemeProvider forcedTheme="light" {...props}>
|
||||
<TooltipProvider>{children}</TooltipProvider>
|
||||
</OnboardingProvider>
|
||||
</LaunchDarklyProvider>
|
||||
</CredentialsProvider>
|
||||
</BackendAPIProvider>
|
||||
</NextThemesProvider>
|
||||
</ThemeProvider>
|
||||
</OnboardingProvider>
|
||||
</LaunchDarklyProvider>
|
||||
</CredentialsProvider>
|
||||
</BackendAPIProvider>
|
||||
</NuqsAdapter>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
||||
@@ -20,12 +20,16 @@ export function Button(props: ButtonProps) {
|
||||
rightIcon,
|
||||
children,
|
||||
as = "button",
|
||||
unmask = true,
|
||||
...restProps
|
||||
} = props;
|
||||
|
||||
const disabled = "disabled" in props ? props.disabled : false;
|
||||
const isDisabled = disabled;
|
||||
|
||||
const applyUnmask = (...classes: Array<string | false | null | undefined>) =>
|
||||
cn(...classes, unmask && "sentry-unmask");
|
||||
|
||||
// Extract aria-label for tooltip on icon variant
|
||||
const ariaLabel =
|
||||
"aria-label" in restProps ? restProps["aria-label"] : undefined;
|
||||
@@ -84,11 +88,11 @@ export function Button(props: ButtonProps) {
|
||||
if (loading) {
|
||||
const loadingClassName =
|
||||
variant === "ghost"
|
||||
? cn(
|
||||
? applyUnmask(
|
||||
extendedButtonVariants({ variant, size, className }),
|
||||
"pointer-events-none",
|
||||
)
|
||||
: cn(
|
||||
: applyUnmask(
|
||||
extendedButtonVariants({ variant: "primary", size, className }),
|
||||
"pointer-events-none border-zinc-500 bg-zinc-500 text-white",
|
||||
);
|
||||
@@ -120,7 +124,7 @@ export function Button(props: ButtonProps) {
|
||||
const nextLinkButton = (
|
||||
<NextLink
|
||||
{...(restProps as LinkProps)}
|
||||
className={cn(
|
||||
className={applyUnmask(
|
||||
extendedButtonVariants({ variant, size, className }),
|
||||
loading && "pointer-events-none",
|
||||
isDisabled && "pointer-events-none opacity-50",
|
||||
@@ -136,7 +140,7 @@ export function Button(props: ButtonProps) {
|
||||
|
||||
const regularButton = (
|
||||
<button
|
||||
className={cn(
|
||||
className={applyUnmask(
|
||||
extendedButtonVariants({ variant, size, className }),
|
||||
loading && "pointer-events-none",
|
||||
)}
|
||||
|
||||
@@ -50,6 +50,11 @@ type BaseButtonProps = {
|
||||
rightIcon?: React.ReactNode;
|
||||
asChild?: boolean;
|
||||
withTooltip?: boolean;
|
||||
/**
|
||||
* Adds the sentry-unmask class for static button labels.
|
||||
* Disable for user-provided or dynamic strings.
|
||||
*/
|
||||
unmask?: boolean;
|
||||
} & VariantProps<typeof extendedButtonVariants>;
|
||||
|
||||
type ButtonAsButton = BaseButtonProps &
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -6,6 +6,11 @@ type CustomProps = {
|
||||
as?: As;
|
||||
size?: Variant;
|
||||
className?: string;
|
||||
/**
|
||||
* Adds the sentry-unmask class for static text visibility in replays.
|
||||
* Disable when rendering user-provided or dynamic content.
|
||||
*/
|
||||
unmask?: boolean;
|
||||
};
|
||||
|
||||
export type TextProps = React.PropsWithChildren<
|
||||
@@ -18,11 +23,14 @@ export function Text({
|
||||
as: outerAs,
|
||||
size,
|
||||
className = "",
|
||||
unmask = true,
|
||||
...rest
|
||||
}: TextProps) {
|
||||
const variantClasses = variants[size || variant] || variants.body;
|
||||
const Element = outerAs || variantElementMap[variant];
|
||||
const combinedClassName = `${variantClasses} ${className}`.trim();
|
||||
const combinedClassName = `${variantClasses} ${
|
||||
unmask ? "sentry-unmask" : ""
|
||||
} ${className}`.trim();
|
||||
|
||||
return React.createElement(
|
||||
Element,
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Cross2Icon } from "@radix-ui/react-icons";
|
||||
import React, { useCallback } from "react";
|
||||
import { GoogleDrivePicker } from "./GoogleDrivePicker";
|
||||
|
||||
export interface GoogleDrivePickerInputProps {
|
||||
export interface Props {
|
||||
config: GoogleDrivePickerConfig;
|
||||
value: any;
|
||||
onChange: (value: any) => void;
|
||||
@@ -21,7 +21,7 @@ export function GoogleDrivePickerInput({
|
||||
error,
|
||||
className,
|
||||
showRemoveButton = true,
|
||||
}: GoogleDrivePickerInputProps) {
|
||||
}: Props) {
|
||||
const [pickerError, setPickerError] = React.useState<string | null>(null);
|
||||
const isMultiSelect = config.multiselect || false;
|
||||
const hasAutoCredentials = !!config.auto_credentials;
|
||||
|
||||
@@ -19,7 +19,7 @@ export function MobileNavbarMenuItem({
|
||||
onClick,
|
||||
}: Props) {
|
||||
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)}
|
||||
<div className="relative">
|
||||
<div
|
||||
|
||||
@@ -3,8 +3,7 @@ const commonStyles = {
|
||||
title: "font-poppins text-md md:text-lg leading-none",
|
||||
overlay:
|
||||
"fixed inset-0 z-50 bg-stone-500/20 dark:bg-black/50 backdrop-blur-md animate-fade-in",
|
||||
content:
|
||||
"overflow-y-hidden bg-white p-6 fixed rounded-2xlarge flex flex-col z-50 w-full",
|
||||
content: "bg-white p-6 fixed rounded-2xlarge flex flex-col z-50 w-full",
|
||||
};
|
||||
|
||||
// Modal specific styles
|
||||
|
||||
@@ -9,16 +9,20 @@ import ReactMarkdown from "react-markdown";
|
||||
|
||||
type Props = {
|
||||
description?: string;
|
||||
iconSize?: number;
|
||||
};
|
||||
|
||||
export function InformationTooltip({ description }: Props) {
|
||||
export function InformationTooltip({ description, iconSize = 24 }: Props) {
|
||||
if (!description) return null;
|
||||
|
||||
return (
|
||||
<TooltipProvider delayDuration={400}>
|
||||
<Tooltip>
|
||||
<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>
|
||||
<TooltipContent>
|
||||
<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,
|
||||
} from "@/components/atoms/Tooltip/BaseTooltip";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { BlockUIType } from "@/app/(platform)/build/components/types";
|
||||
|
||||
type TypeOption = {
|
||||
type: string;
|
||||
@@ -47,7 +48,14 @@ export const AnyOfField = ({
|
||||
onBlur,
|
||||
onFocus,
|
||||
}: 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 { nodeId, showHandles = true } = updatedFormContexrt;
|
||||
|
||||
@@ -58,7 +58,15 @@ const FieldTemplate: React.FC<FieldTemplateProps> = ({
|
||||
|
||||
let handleId = null;
|
||||
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 {
|
||||
handleId = arrayFieldHandleId;
|
||||
}
|
||||
|
||||
@@ -910,7 +910,37 @@ export default class BackendAPI {
|
||||
reject(new Error("Invalid JSON response"));
|
||||
}
|
||||
} 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> {
|
||||
// Handle 413 Payload Too Large with user-friendly message
|
||||
if (response.status === 413) {
|
||||
return "File is too large — max size is 256MB";
|
||||
}
|
||||
|
||||
try {
|
||||
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
|
||||
}
|
||||
|
||||
// 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;
|
||||
} catch {
|
||||
return response.statusText;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import scrollbar from "tailwind-scrollbar";
|
||||
import type { Config } from "tailwindcss";
|
||||
import tailwindcssAnimate from "tailwindcss-animate";
|
||||
import scrollbar from "tailwind-scrollbar";
|
||||
import { colors } from "./src/components/styles/colors";
|
||||
|
||||
const config = {
|
||||
darkMode: ["class"],
|
||||
darkMode: ["class", ".dark-mode"], // ignore dark: prefix classes for now until we fully support dark mode
|
||||
content: ["./src/**/*.{ts,tsx}"],
|
||||
prefix: "",
|
||||
theme: {
|
||||
|
||||
Reference in New Issue
Block a user