forked from Significant-Gravitas/AutoGPT
Compare commits
12 Commits
master
...
dependabot
Author | SHA1 | Date |
---|---|---|
|
211d68ac0e | |
|
800625c952 | |
|
56612f16cf | |
|
0d2bb46786 | |
|
c61317e448 | |
|
3c30783b14 | |
|
56b33327ab | |
|
c36c239dd5 | |
|
e53f1eaf80 | |
|
04915f2db0 | |
|
9d79bfadea | |
|
5f50c4863d |
|
@ -15,6 +15,9 @@ REDIS_PORT=6379
|
|||
REDIS_PASSWORD=password
|
||||
|
||||
ENABLE_CREDIT=false
|
||||
STRIPE_API_KEY=
|
||||
STRIPE_WEBHOOK_SECRET=
|
||||
|
||||
# What environment things should be logged under: local dev or prod
|
||||
APP_ENV=local
|
||||
# What environment to behave as: "local" or "cloud"
|
||||
|
@ -36,7 +39,7 @@ SUPABASE_JWT_SECRET=your-super-secret-jwt-token-with-at-least-32-characters-long
|
|||
## to use the platform's webhook-related functionality.
|
||||
## If you are developing locally, you can use something like ngrok to get a publc URL
|
||||
## and tunnel it to your locally running backend.
|
||||
PLATFORM_BASE_URL=https://your-public-url-here
|
||||
PLATFORM_BASE_URL=http://localhost:3000
|
||||
|
||||
## == INTEGRATION CREDENTIALS == ##
|
||||
# Each set of server side credentials is required for the corresponding 3rd party
|
||||
|
@ -72,6 +75,12 @@ GOOGLE_CLIENT_SECRET=
|
|||
TWITTER_CLIENT_ID=
|
||||
TWITTER_CLIENT_SECRET=
|
||||
|
||||
# Linear App
|
||||
# Make a new workspace for your OAuth APP -- trust me
|
||||
# https://linear.app/settings/api/applications/new
|
||||
# Callback URL: http://localhost:3000/auth/integrations/oauth_callback
|
||||
LINEAR_CLIENT_ID=
|
||||
LINEAR_CLIENT_SECRET=
|
||||
|
||||
## ===== OPTIONAL API KEYS ===== ##
|
||||
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import enum
|
||||
from typing import Any, List
|
||||
|
||||
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema, BlockType
|
||||
from backend.data.model import SchemaField
|
||||
from backend.util.mock import MockObject
|
||||
from backend.util.text import TextFormatter
|
||||
from backend.util.type import convert
|
||||
|
||||
formatter = TextFormatter()
|
||||
|
||||
|
@ -590,3 +592,47 @@ class CreateListBlock(Block):
|
|||
yield "list", input_data.values
|
||||
except Exception as e:
|
||||
yield "error", f"Failed to create list: {str(e)}"
|
||||
|
||||
|
||||
class TypeOptions(enum.Enum):
|
||||
STRING = "string"
|
||||
NUMBER = "number"
|
||||
BOOLEAN = "boolean"
|
||||
LIST = "list"
|
||||
DICTIONARY = "dictionary"
|
||||
|
||||
|
||||
class UniversalTypeConverterBlock(Block):
|
||||
class Input(BlockSchema):
|
||||
value: Any = SchemaField(
|
||||
description="The value to convert to a universal type."
|
||||
)
|
||||
type: TypeOptions = SchemaField(description="The type to convert the value to.")
|
||||
|
||||
class Output(BlockSchema):
|
||||
value: Any = SchemaField(description="The converted value.")
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
id="95d1b990-ce13-4d88-9737-ba5c2070c97b",
|
||||
description="This block is used to convert a value to a universal type.",
|
||||
categories={BlockCategory.BASIC},
|
||||
input_schema=UniversalTypeConverterBlock.Input,
|
||||
output_schema=UniversalTypeConverterBlock.Output,
|
||||
)
|
||||
|
||||
def run(self, input_data: Input, **kwargs) -> BlockOutput:
|
||||
try:
|
||||
converted_value = convert(
|
||||
input_data.value,
|
||||
{
|
||||
TypeOptions.STRING: str,
|
||||
TypeOptions.NUMBER: float,
|
||||
TypeOptions.BOOLEAN: bool,
|
||||
TypeOptions.LIST: list,
|
||||
TypeOptions.DICTIONARY: dict,
|
||||
}[input_data.type],
|
||||
)
|
||||
yield "value", converted_value
|
||||
except Exception as e:
|
||||
yield "error", f"Failed to convert value: {str(e)}"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from typing import List, Optional
|
||||
from typing import List
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
|
|
@ -0,0 +1,272 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from backend.blocks.linear._auth import LinearCredentials
|
||||
from backend.blocks.linear.models import (
|
||||
CreateCommentResponse,
|
||||
CreateIssueResponse,
|
||||
Issue,
|
||||
Project,
|
||||
)
|
||||
from backend.util.request import Requests
|
||||
|
||||
|
||||
class LinearAPIException(Exception):
|
||||
def __init__(self, message: str, status_code: int):
|
||||
super().__init__(message)
|
||||
self.status_code = status_code
|
||||
|
||||
|
||||
class LinearClient:
|
||||
"""Client for the Linear API
|
||||
|
||||
If you're looking for the schema: https://studio.apollographql.com/public/Linear-API/variant/current/schema
|
||||
"""
|
||||
|
||||
API_URL = "https://api.linear.app/graphql"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
credentials: LinearCredentials | None = None,
|
||||
custom_requests: Optional[Requests] = None,
|
||||
):
|
||||
if custom_requests:
|
||||
self._requests = custom_requests
|
||||
else:
|
||||
|
||||
headers: Dict[str, str] = {
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
if credentials:
|
||||
headers["Authorization"] = credentials.bearer()
|
||||
|
||||
self._requests = Requests(
|
||||
extra_headers=headers,
|
||||
trusted_origins=["https://api.linear.app"],
|
||||
raise_for_status=False,
|
||||
)
|
||||
|
||||
def _execute_graphql_request(
|
||||
self, query: str, variables: dict | None = None
|
||||
) -> Any:
|
||||
"""
|
||||
Executes a GraphQL request against the Linear API and returns the response data.
|
||||
|
||||
Args:
|
||||
query: The GraphQL query string.
|
||||
variables (optional): Any GraphQL query variables
|
||||
|
||||
Returns:
|
||||
The parsed JSON response data, or raises a LinearAPIException on error.
|
||||
"""
|
||||
payload: Dict[str, Any] = {"query": query}
|
||||
if variables:
|
||||
payload["variables"] = variables
|
||||
|
||||
response = self._requests.post(self.API_URL, json=payload)
|
||||
|
||||
if not response.ok:
|
||||
|
||||
try:
|
||||
error_data = response.json()
|
||||
error_message = error_data.get("errors", [{}])[0].get("message", "")
|
||||
except json.JSONDecodeError:
|
||||
error_message = response.text
|
||||
|
||||
raise LinearAPIException(
|
||||
f"Linear API request failed ({response.status_code}): {error_message}",
|
||||
response.status_code,
|
||||
)
|
||||
|
||||
response_data = response.json()
|
||||
if "errors" in response_data:
|
||||
|
||||
error_messages = [
|
||||
error.get("message", "") for error in response_data["errors"]
|
||||
]
|
||||
raise LinearAPIException(
|
||||
f"Linear API returned errors: {', '.join(error_messages)}",
|
||||
response.status_code,
|
||||
)
|
||||
|
||||
return response_data["data"]
|
||||
|
||||
def query(self, query: str, variables: Optional[dict] = None) -> dict:
|
||||
"""Executes a GraphQL query.
|
||||
|
||||
Args:
|
||||
query: The GraphQL query string.
|
||||
variables: Query variables, if any.
|
||||
|
||||
Returns:
|
||||
The response data.
|
||||
"""
|
||||
return self._execute_graphql_request(query, variables)
|
||||
|
||||
def mutate(self, mutation: str, variables: Optional[dict] = None) -> dict:
|
||||
"""Executes a GraphQL mutation.
|
||||
|
||||
Args:
|
||||
mutation: The GraphQL mutation string.
|
||||
variables: Query variables, if any.
|
||||
|
||||
Returns:
|
||||
The response data.
|
||||
"""
|
||||
return self._execute_graphql_request(mutation, variables)
|
||||
|
||||
def try_create_comment(self, issue_id: str, comment: str) -> CreateCommentResponse:
|
||||
try:
|
||||
mutation = """
|
||||
mutation CommentCreate($input: CommentCreateInput!) {
|
||||
commentCreate(input: $input) {
|
||||
success
|
||||
comment {
|
||||
id
|
||||
body
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
variables = {
|
||||
"input": {
|
||||
"body": comment,
|
||||
"issueId": issue_id,
|
||||
}
|
||||
}
|
||||
|
||||
added_comment = self.mutate(mutation, variables)
|
||||
# Select the commentCreate field from the mutation response
|
||||
return CreateCommentResponse(**added_comment["commentCreate"])
|
||||
except LinearAPIException as e:
|
||||
raise e
|
||||
|
||||
def try_get_team_by_name(self, team_name: str) -> str:
|
||||
try:
|
||||
query = """
|
||||
query GetTeamId($searchTerm: String!) {
|
||||
teams(filter: {
|
||||
or: [
|
||||
{ name: { eqIgnoreCase: $searchTerm } },
|
||||
{ key: { eqIgnoreCase: $searchTerm } }
|
||||
]
|
||||
}) {
|
||||
nodes {
|
||||
id
|
||||
name
|
||||
key
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
variables: dict[str, Any] = {
|
||||
"searchTerm": team_name,
|
||||
}
|
||||
|
||||
team_id = self.query(query, variables)
|
||||
return team_id["teams"]["nodes"][0]["id"]
|
||||
except LinearAPIException as e:
|
||||
raise e
|
||||
|
||||
def try_create_issue(
|
||||
self,
|
||||
team_id: str,
|
||||
title: str,
|
||||
description: str | None = None,
|
||||
priority: int | None = None,
|
||||
project_id: str | None = None,
|
||||
) -> CreateIssueResponse:
|
||||
try:
|
||||
mutation = """
|
||||
mutation IssueCreate($input: IssueCreateInput!) {
|
||||
issueCreate(input: $input) {
|
||||
issue {
|
||||
title
|
||||
description
|
||||
id
|
||||
identifier
|
||||
priority
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
variables: dict[str, Any] = {
|
||||
"input": {
|
||||
"teamId": team_id,
|
||||
"title": title,
|
||||
}
|
||||
}
|
||||
|
||||
if project_id:
|
||||
variables["input"]["projectId"] = project_id
|
||||
|
||||
if description:
|
||||
variables["input"]["description"] = description
|
||||
|
||||
if priority:
|
||||
variables["input"]["priority"] = priority
|
||||
|
||||
added_issue = self.mutate(mutation, variables)
|
||||
return CreateIssueResponse(**added_issue["issueCreate"])
|
||||
except LinearAPIException as e:
|
||||
raise e
|
||||
|
||||
def try_search_projects(self, term: str) -> list[Project]:
|
||||
try:
|
||||
query = """
|
||||
query SearchProjects($term: String!, $includeComments: Boolean!) {
|
||||
searchProjects(term: $term, includeComments: $includeComments) {
|
||||
nodes {
|
||||
id
|
||||
name
|
||||
description
|
||||
priority
|
||||
progress
|
||||
content
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
variables: dict[str, Any] = {
|
||||
"term": term,
|
||||
"includeComments": True,
|
||||
}
|
||||
|
||||
projects = self.query(query, variables)
|
||||
return [
|
||||
Project(**project) for project in projects["searchProjects"]["nodes"]
|
||||
]
|
||||
except LinearAPIException as e:
|
||||
raise e
|
||||
|
||||
def try_search_issues(self, term: str) -> list[Issue]:
|
||||
try:
|
||||
query = """
|
||||
query SearchIssues($term: String!, $includeComments: Boolean!) {
|
||||
searchIssues(term: $term, includeComments: $includeComments) {
|
||||
nodes {
|
||||
id
|
||||
identifier
|
||||
title
|
||||
description
|
||||
priority
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
variables: dict[str, Any] = {
|
||||
"term": term,
|
||||
"includeComments": True,
|
||||
}
|
||||
|
||||
issues = self.query(query, variables)
|
||||
return [Issue(**issue) for issue in issues["searchIssues"]["nodes"]]
|
||||
except LinearAPIException as e:
|
||||
raise e
|
|
@ -0,0 +1,101 @@
|
|||
from enum import Enum
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import SecretStr
|
||||
|
||||
from backend.data.model import (
|
||||
APIKeyCredentials,
|
||||
CredentialsField,
|
||||
CredentialsMetaInput,
|
||||
OAuth2Credentials,
|
||||
)
|
||||
from backend.integrations.providers import ProviderName
|
||||
from backend.util.settings import Secrets
|
||||
|
||||
secrets = Secrets()
|
||||
LINEAR_OAUTH_IS_CONFIGURED = bool(
|
||||
secrets.linear_client_id and secrets.linear_client_secret
|
||||
)
|
||||
|
||||
LinearCredentials = OAuth2Credentials | APIKeyCredentials
|
||||
# LinearCredentialsInput = CredentialsMetaInput[
|
||||
# Literal[ProviderName.LINEAR],
|
||||
# Literal["oauth2", "api_key"] if LINEAR_OAUTH_IS_CONFIGURED else Literal["oauth2"],
|
||||
# ]
|
||||
LinearCredentialsInput = CredentialsMetaInput[
|
||||
Literal[ProviderName.LINEAR], Literal["oauth2"]
|
||||
]
|
||||
|
||||
|
||||
# (required) Comma separated list of scopes:
|
||||
|
||||
# read - (Default) Read access for the user's account. This scope will always be present.
|
||||
|
||||
# write - Write access for the user's account. If your application only needs to create comments, use a more targeted scope
|
||||
|
||||
# issues:create - Allows creating new issues and their attachments
|
||||
|
||||
# comments:create - Allows creating new issue comments
|
||||
|
||||
# timeSchedule:write - Allows creating and modifying time schedules
|
||||
|
||||
|
||||
# admin - Full access to admin level endpoints. You should never ask for this permission unless it's absolutely needed
|
||||
class LinearScope(str, Enum):
|
||||
READ = "read"
|
||||
WRITE = "write"
|
||||
ISSUES_CREATE = "issues:create"
|
||||
COMMENTS_CREATE = "comments:create"
|
||||
TIME_SCHEDULE_WRITE = "timeSchedule:write"
|
||||
ADMIN = "admin"
|
||||
|
||||
|
||||
def LinearCredentialsField(scopes: list[LinearScope]) -> LinearCredentialsInput:
|
||||
"""
|
||||
Creates a Linear credentials input on a block.
|
||||
|
||||
Params:
|
||||
scope: The authorization scope needed for the block to work. ([list of available scopes](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/scopes-for-oauth-apps#available-scopes))
|
||||
""" # noqa
|
||||
return CredentialsField(
|
||||
required_scopes=set([LinearScope.READ.value]).union(
|
||||
set([scope.value for scope in scopes])
|
||||
),
|
||||
description="The Linear integration can be used with OAuth, "
|
||||
"or any API key with sufficient permissions for the blocks it is used on.",
|
||||
)
|
||||
|
||||
|
||||
TEST_CREDENTIALS_OAUTH = OAuth2Credentials(
|
||||
id="01234567-89ab-cdef-0123-456789abcdef",
|
||||
provider="linear",
|
||||
title="Mock Linear API key",
|
||||
username="mock-linear-username",
|
||||
access_token=SecretStr("mock-linear-access-token"),
|
||||
access_token_expires_at=None,
|
||||
refresh_token=SecretStr("mock-linear-refresh-token"),
|
||||
refresh_token_expires_at=None,
|
||||
scopes=["mock-linear-scopes"],
|
||||
)
|
||||
|
||||
TEST_CREDENTIALS_API_KEY = APIKeyCredentials(
|
||||
id="01234567-89ab-cdef-0123-456789abcdef",
|
||||
provider="linear",
|
||||
title="Mock Linear API key",
|
||||
api_key=SecretStr("mock-linear-api-key"),
|
||||
expires_at=None,
|
||||
)
|
||||
|
||||
TEST_CREDENTIALS_INPUT_OAUTH = {
|
||||
"provider": TEST_CREDENTIALS_OAUTH.provider,
|
||||
"id": TEST_CREDENTIALS_OAUTH.id,
|
||||
"type": TEST_CREDENTIALS_OAUTH.type,
|
||||
"title": TEST_CREDENTIALS_OAUTH.type,
|
||||
}
|
||||
|
||||
TEST_CREDENTIALS_INPUT_API_KEY = {
|
||||
"provider": TEST_CREDENTIALS_API_KEY.provider,
|
||||
"id": TEST_CREDENTIALS_API_KEY.id,
|
||||
"type": TEST_CREDENTIALS_API_KEY.type,
|
||||
"title": TEST_CREDENTIALS_API_KEY.type,
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
from backend.blocks.linear._api import LinearAPIException, LinearClient
|
||||
from backend.blocks.linear._auth import (
|
||||
TEST_CREDENTIALS_INPUT_OAUTH,
|
||||
TEST_CREDENTIALS_OAUTH,
|
||||
LinearCredentials,
|
||||
LinearCredentialsField,
|
||||
LinearCredentialsInput,
|
||||
LinearScope,
|
||||
)
|
||||
from backend.blocks.linear.models import CreateCommentResponse
|
||||
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
|
||||
from backend.data.model import SchemaField
|
||||
|
||||
|
||||
class LinearCreateCommentBlock(Block):
|
||||
"""Block for creating comments on Linear issues"""
|
||||
|
||||
class Input(BlockSchema):
|
||||
credentials: LinearCredentialsInput = LinearCredentialsField(
|
||||
scopes=[LinearScope.COMMENTS_CREATE],
|
||||
)
|
||||
issue_id: str = SchemaField(description="ID of the issue to comment on")
|
||||
comment: str = SchemaField(description="Comment text to add to the issue")
|
||||
|
||||
class Output(BlockSchema):
|
||||
comment_id: str = SchemaField(description="ID of the created comment")
|
||||
comment_body: str = SchemaField(
|
||||
description="Text content of the created comment"
|
||||
)
|
||||
error: str = SchemaField(description="Error message if comment creation failed")
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
id="8f7d3a2e-9b5c-4c6a-8f1d-7c8b3e4a5d6c",
|
||||
description="Creates a new comment on a Linear issue",
|
||||
input_schema=self.Input,
|
||||
output_schema=self.Output,
|
||||
categories={BlockCategory.PRODUCTIVITY, BlockCategory.ISSUE_TRACKING},
|
||||
test_input={
|
||||
"issue_id": "TEST-123",
|
||||
"comment": "Test comment",
|
||||
"credentials": TEST_CREDENTIALS_INPUT_OAUTH,
|
||||
},
|
||||
test_credentials=TEST_CREDENTIALS_OAUTH,
|
||||
test_output=[("comment_id", "abc123"), ("comment_body", "Test comment")],
|
||||
test_mock={
|
||||
"create_comment": lambda *args, **kwargs: (
|
||||
"abc123",
|
||||
"Test comment",
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def create_comment(
|
||||
credentials: LinearCredentials, issue_id: str, comment: str
|
||||
) -> tuple[str, str]:
|
||||
client = LinearClient(credentials=credentials)
|
||||
response: CreateCommentResponse = client.try_create_comment(
|
||||
issue_id=issue_id, comment=comment
|
||||
)
|
||||
return response.comment.id, response.comment.body
|
||||
|
||||
def run(
|
||||
self, input_data: Input, *, credentials: LinearCredentials, **kwargs
|
||||
) -> BlockOutput:
|
||||
"""Execute the comment creation"""
|
||||
try:
|
||||
comment_id, comment_body = self.create_comment(
|
||||
credentials=credentials,
|
||||
issue_id=input_data.issue_id,
|
||||
comment=input_data.comment,
|
||||
)
|
||||
|
||||
yield "comment_id", comment_id
|
||||
yield "comment_body", comment_body
|
||||
|
||||
except LinearAPIException as e:
|
||||
yield "error", str(e)
|
||||
except Exception as e:
|
||||
yield "error", f"Unexpected error: {str(e)}"
|
|
@ -0,0 +1,186 @@
|
|||
from backend.blocks.linear._api import LinearAPIException, LinearClient
|
||||
from backend.blocks.linear._auth import (
|
||||
TEST_CREDENTIALS_INPUT_OAUTH,
|
||||
TEST_CREDENTIALS_OAUTH,
|
||||
LinearCredentials,
|
||||
LinearCredentialsField,
|
||||
LinearCredentialsInput,
|
||||
LinearScope,
|
||||
)
|
||||
from backend.blocks.linear.models import CreateIssueResponse, Issue
|
||||
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
|
||||
from backend.data.model import SchemaField
|
||||
|
||||
|
||||
class LinearCreateIssueBlock(Block):
|
||||
"""Block for creating issues on Linear"""
|
||||
|
||||
class Input(BlockSchema):
|
||||
credentials: LinearCredentialsInput = LinearCredentialsField(
|
||||
scopes=[LinearScope.ISSUES_CREATE],
|
||||
)
|
||||
title: str = SchemaField(description="Title of the issue")
|
||||
description: str | None = SchemaField(description="Description of the issue")
|
||||
team_name: str = SchemaField(
|
||||
description="Name of the team to create the issue on"
|
||||
)
|
||||
priority: int | None = SchemaField(
|
||||
description="Priority of the issue",
|
||||
default=None,
|
||||
minimum=0,
|
||||
maximum=4,
|
||||
)
|
||||
project_name: str | None = SchemaField(
|
||||
description="Name of the project to create the issue on",
|
||||
default=None,
|
||||
)
|
||||
|
||||
class Output(BlockSchema):
|
||||
issue_id: str = SchemaField(description="ID of the created issue")
|
||||
issue_title: str = SchemaField(description="Title of the created issue")
|
||||
error: str = SchemaField(description="Error message if issue creation failed")
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
id="f9c68f55-dcca-40a8-8771-abf9601680aa",
|
||||
description="Creates a new issue on Linear",
|
||||
input_schema=self.Input,
|
||||
output_schema=self.Output,
|
||||
categories={BlockCategory.PRODUCTIVITY, BlockCategory.ISSUE_TRACKING},
|
||||
test_input={
|
||||
"title": "Test issue",
|
||||
"description": "Test description",
|
||||
"team_name": "Test team",
|
||||
"project_name": "Test project",
|
||||
"credentials": TEST_CREDENTIALS_INPUT_OAUTH,
|
||||
},
|
||||
test_credentials=TEST_CREDENTIALS_OAUTH,
|
||||
test_output=[("issue_id", "abc123"), ("issue_title", "Test issue")],
|
||||
test_mock={
|
||||
"create_issue": lambda *args, **kwargs: (
|
||||
"abc123",
|
||||
"Test issue",
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def create_issue(
|
||||
credentials: LinearCredentials,
|
||||
team_name: str,
|
||||
title: str,
|
||||
description: str | None = None,
|
||||
priority: int | None = None,
|
||||
project_name: str | None = None,
|
||||
) -> tuple[str, str]:
|
||||
client = LinearClient(credentials=credentials)
|
||||
team_id = client.try_get_team_by_name(team_name=team_name)
|
||||
project_id: str | None = None
|
||||
if project_name:
|
||||
projects = client.try_search_projects(term=project_name)
|
||||
if projects:
|
||||
project_id = projects[0].id
|
||||
else:
|
||||
raise LinearAPIException("Project not found", status_code=404)
|
||||
response: CreateIssueResponse = client.try_create_issue(
|
||||
team_id=team_id,
|
||||
title=title,
|
||||
description=description,
|
||||
priority=priority,
|
||||
project_id=project_id,
|
||||
)
|
||||
return response.issue.identifier, response.issue.title
|
||||
|
||||
def run(
|
||||
self, input_data: Input, *, credentials: LinearCredentials, **kwargs
|
||||
) -> BlockOutput:
|
||||
"""Execute the issue creation"""
|
||||
try:
|
||||
issue_id, issue_title = self.create_issue(
|
||||
credentials=credentials,
|
||||
team_name=input_data.team_name,
|
||||
title=input_data.title,
|
||||
description=input_data.description,
|
||||
priority=input_data.priority,
|
||||
project_name=input_data.project_name,
|
||||
)
|
||||
|
||||
yield "issue_id", issue_id
|
||||
yield "issue_title", issue_title
|
||||
|
||||
except LinearAPIException as e:
|
||||
yield "error", str(e)
|
||||
except Exception as e:
|
||||
yield "error", f"Unexpected error: {str(e)}"
|
||||
|
||||
|
||||
class LinearSearchIssuesBlock(Block):
|
||||
"""Block for searching issues on Linear"""
|
||||
|
||||
class Input(BlockSchema):
|
||||
term: str = SchemaField(description="Term to search for issues")
|
||||
credentials: LinearCredentialsInput = LinearCredentialsField(
|
||||
scopes=[LinearScope.READ],
|
||||
)
|
||||
|
||||
class Output(BlockSchema):
|
||||
issues: list[Issue] = SchemaField(description="List of issues")
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
id="b5a2a0e6-26b4-4c5b-8a42-bc79e9cb65c2",
|
||||
description="Searches for issues on Linear",
|
||||
input_schema=self.Input,
|
||||
output_schema=self.Output,
|
||||
test_input={
|
||||
"term": "Test issue",
|
||||
"credentials": TEST_CREDENTIALS_INPUT_OAUTH,
|
||||
},
|
||||
test_credentials=TEST_CREDENTIALS_OAUTH,
|
||||
test_output=[
|
||||
(
|
||||
"issues",
|
||||
[
|
||||
Issue(
|
||||
id="abc123",
|
||||
identifier="abc123",
|
||||
title="Test issue",
|
||||
description="Test description",
|
||||
priority=1,
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
test_mock={
|
||||
"search_issues": lambda *args, **kwargs: [
|
||||
Issue(
|
||||
id="abc123",
|
||||
identifier="abc123",
|
||||
title="Test issue",
|
||||
description="Test description",
|
||||
priority=1,
|
||||
)
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def search_issues(
|
||||
credentials: LinearCredentials,
|
||||
term: str,
|
||||
) -> list[Issue]:
|
||||
client = LinearClient(credentials=credentials)
|
||||
response: list[Issue] = client.try_search_issues(term=term)
|
||||
return response
|
||||
|
||||
def run(
|
||||
self, input_data: Input, *, credentials: LinearCredentials, **kwargs
|
||||
) -> BlockOutput:
|
||||
"""Execute the issue search"""
|
||||
try:
|
||||
issues = self.search_issues(credentials=credentials, term=input_data.term)
|
||||
yield "issues", issues
|
||||
except LinearAPIException as e:
|
||||
yield "error", str(e)
|
||||
except Exception as e:
|
||||
yield "error", f"Unexpected error: {str(e)}"
|
|
@ -0,0 +1,41 @@
|
|||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Comment(BaseModel):
|
||||
id: str
|
||||
body: str
|
||||
|
||||
|
||||
class CreateCommentInput(BaseModel):
|
||||
body: str
|
||||
issueId: str
|
||||
|
||||
|
||||
class CreateCommentResponse(BaseModel):
|
||||
success: bool
|
||||
comment: Comment
|
||||
|
||||
|
||||
class CreateCommentResponseWrapper(BaseModel):
|
||||
commentCreate: CreateCommentResponse
|
||||
|
||||
|
||||
class Issue(BaseModel):
|
||||
id: str
|
||||
identifier: str
|
||||
title: str
|
||||
description: str | None
|
||||
priority: int
|
||||
|
||||
|
||||
class CreateIssueResponse(BaseModel):
|
||||
issue: Issue
|
||||
|
||||
|
||||
class Project(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
description: str
|
||||
priority: int
|
||||
progress: int
|
||||
content: str
|
|
@ -0,0 +1,93 @@
|
|||
from backend.blocks.linear._api import LinearAPIException, LinearClient
|
||||
from backend.blocks.linear._auth import (
|
||||
TEST_CREDENTIALS_INPUT_OAUTH,
|
||||
TEST_CREDENTIALS_OAUTH,
|
||||
LinearCredentials,
|
||||
LinearCredentialsField,
|
||||
LinearCredentialsInput,
|
||||
LinearScope,
|
||||
)
|
||||
from backend.blocks.linear.models import Project
|
||||
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
|
||||
from backend.data.model import SchemaField
|
||||
|
||||
|
||||
class LinearSearchProjectsBlock(Block):
|
||||
"""Block for searching projects on Linear"""
|
||||
|
||||
class Input(BlockSchema):
|
||||
credentials: LinearCredentialsInput = LinearCredentialsField(
|
||||
scopes=[LinearScope.READ],
|
||||
)
|
||||
term: str = SchemaField(description="Term to search for projects")
|
||||
|
||||
class Output(BlockSchema):
|
||||
projects: list[Project] = SchemaField(description="List of projects")
|
||||
error: str = SchemaField(description="Error message if issue creation failed")
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
id="446a1d35-9d8f-4ac5-83ea-7684ec50e6af",
|
||||
description="Searches for projects on Linear",
|
||||
input_schema=self.Input,
|
||||
output_schema=self.Output,
|
||||
categories={BlockCategory.PRODUCTIVITY, BlockCategory.ISSUE_TRACKING},
|
||||
test_input={
|
||||
"term": "Test project",
|
||||
"credentials": TEST_CREDENTIALS_INPUT_OAUTH,
|
||||
},
|
||||
test_credentials=TEST_CREDENTIALS_OAUTH,
|
||||
test_output=[
|
||||
(
|
||||
"projects",
|
||||
[
|
||||
Project(
|
||||
id="abc123",
|
||||
name="Test project",
|
||||
description="Test description",
|
||||
priority=1,
|
||||
progress=1,
|
||||
content="Test content",
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
test_mock={
|
||||
"search_projects": lambda *args, **kwargs: [
|
||||
Project(
|
||||
id="abc123",
|
||||
name="Test project",
|
||||
description="Test description",
|
||||
priority=1,
|
||||
progress=1,
|
||||
content="Test content",
|
||||
)
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def search_projects(
|
||||
credentials: LinearCredentials,
|
||||
term: str,
|
||||
) -> list[Project]:
|
||||
client = LinearClient(credentials=credentials)
|
||||
response: list[Project] = client.try_search_projects(term=term)
|
||||
return response
|
||||
|
||||
def run(
|
||||
self, input_data: Input, *, credentials: LinearCredentials, **kwargs
|
||||
) -> BlockOutput:
|
||||
"""Execute the project search"""
|
||||
try:
|
||||
projects = self.search_projects(
|
||||
credentials=credentials,
|
||||
term=input_data.term,
|
||||
)
|
||||
|
||||
yield "projects", projects
|
||||
|
||||
except LinearAPIException as e:
|
||||
yield "error", str(e)
|
||||
except Exception as e:
|
||||
yield "error", f"Unexpected error: {str(e)}"
|
|
@ -64,6 +64,8 @@ class BlockCategory(Enum):
|
|||
SAFETY = (
|
||||
"Block that provides AI safety mechanisms such as detecting harmful content"
|
||||
)
|
||||
PRODUCTIVITY = "Block that helps with productivity"
|
||||
ISSUE_TRACKING = "Block that helps with issue tracking"
|
||||
|
||||
def dict(self) -> dict[str, str]:
|
||||
return {"category": self.name, "description": self.value}
|
||||
|
|
|
@ -1,40 +1,40 @@
|
|||
from abc import ABC, abstractmethod
|
||||
from datetime import datetime, timezone
|
||||
|
||||
import stripe
|
||||
from prisma import Json
|
||||
from prisma.enums import CreditTransactionType
|
||||
from prisma.errors import UniqueViolationError
|
||||
from prisma.models import CreditTransaction
|
||||
from prisma.models import CreditTransaction, User
|
||||
from prisma.types import CreditTransactionCreateInput, CreditTransactionWhereInput
|
||||
|
||||
from backend.data import db
|
||||
from backend.data.block import Block, BlockInput, get_block
|
||||
from backend.data.block_cost_config import BLOCK_COSTS
|
||||
from backend.data.cost import BlockCost, BlockCostType
|
||||
from backend.util.settings import Config
|
||||
from backend.data.execution import NodeExecutionEntry
|
||||
from backend.data.user import get_user_by_id
|
||||
from backend.util.settings import Settings
|
||||
|
||||
config = Config()
|
||||
settings = Settings()
|
||||
stripe.api_key = settings.secrets.stripe_api_key
|
||||
|
||||
|
||||
class UserCreditBase(ABC):
|
||||
def __init__(self, num_user_credits_refill: int):
|
||||
self.num_user_credits_refill = num_user_credits_refill
|
||||
|
||||
@abstractmethod
|
||||
async def get_or_refill_credit(self, user_id: str) -> int:
|
||||
async def get_credits(self, user_id: str) -> int:
|
||||
"""
|
||||
Get the current credit for the user and refill if no transaction has been made in the current cycle.
|
||||
Get the current credits for the user.
|
||||
|
||||
Returns:
|
||||
int: The current credit for the user.
|
||||
int: The current credits for the user.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def spend_credits(
|
||||
self,
|
||||
user_id: str,
|
||||
user_credit: int,
|
||||
block_id: str,
|
||||
input_data: BlockInput,
|
||||
entry: NodeExecutionEntry,
|
||||
data_size: float,
|
||||
run_time: float,
|
||||
) -> int:
|
||||
|
@ -42,10 +42,7 @@ class UserCreditBase(ABC):
|
|||
Spend the credits for the user based on the block usage.
|
||||
|
||||
Args:
|
||||
user_id (str): The user ID.
|
||||
user_credit (int): The current credit for the user.
|
||||
block_id (str): The block ID.
|
||||
input_data (BlockInput): The input data for the block.
|
||||
entry (NodeExecutionEntry): The node execution identifiers & data.
|
||||
data_size (float): The size of the data being processed.
|
||||
run_time (float): The time taken to run the block.
|
||||
|
||||
|
@ -57,7 +54,7 @@ class UserCreditBase(ABC):
|
|||
@abstractmethod
|
||||
async def top_up_credits(self, user_id: str, amount: int):
|
||||
"""
|
||||
Top up the credits for the user.
|
||||
Top up the credits for the user immediately.
|
||||
|
||||
Args:
|
||||
user_id (str): The user ID.
|
||||
|
@ -65,51 +62,137 @@ class UserCreditBase(ABC):
|
|||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def top_up_intent(self, user_id: str, amount: int) -> str:
|
||||
"""
|
||||
Create a payment intent to top up the credits for the user.
|
||||
|
||||
class UserCredit(UserCreditBase):
|
||||
async def get_or_refill_credit(self, user_id: str) -> int:
|
||||
cur_time = self.time_now()
|
||||
cur_month = cur_time.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
|
||||
nxt_month = (
|
||||
cur_month.replace(month=cur_month.month + 1)
|
||||
if cur_month.month < 12
|
||||
else cur_month.replace(year=cur_month.year + 1, month=1)
|
||||
Args:
|
||||
user_id (str): The user ID.
|
||||
amount (int): The amount of credits to top up.
|
||||
|
||||
Returns:
|
||||
str: The redirect url to the payment page.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def fulfill_checkout(
|
||||
self, *, session_id: str | None = None, user_id: str | None = None
|
||||
):
|
||||
"""
|
||||
Fulfill the Stripe checkout session.
|
||||
|
||||
Args:
|
||||
session_id (str | None): The checkout session ID. Will try to fulfill most recent if None.
|
||||
user_id (str | None): The user ID must be provided if session_id is None.
|
||||
"""
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def time_now() -> datetime:
|
||||
return datetime.now(timezone.utc)
|
||||
|
||||
# ====== Transaction Helper Methods ====== #
|
||||
# Any modifications to the transaction table should only be done through these methods #
|
||||
|
||||
async def _get_credits(self, user_id: str) -> tuple[int, datetime]:
|
||||
"""
|
||||
Returns the current balance of the user & the latest balance snapshot time.
|
||||
"""
|
||||
top_time = self.time_now()
|
||||
snapshot = await CreditTransaction.prisma().find_first(
|
||||
where={
|
||||
"userId": user_id,
|
||||
"createdAt": {"lte": top_time},
|
||||
"isActive": True,
|
||||
"runningBalance": {"not": None}, # type: ignore
|
||||
},
|
||||
order={"createdAt": "desc"},
|
||||
)
|
||||
if snapshot:
|
||||
return snapshot.runningBalance or 0, snapshot.createdAt
|
||||
|
||||
user_credit = await CreditTransaction.prisma().group_by(
|
||||
# No snapshot: Manually calculate balance using current month's transactions.
|
||||
low_time = top_time.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
|
||||
transactions = await CreditTransaction.prisma().group_by(
|
||||
by=["userId"],
|
||||
sum={"amount": True},
|
||||
where={
|
||||
"userId": user_id,
|
||||
"createdAt": {"gte": cur_month, "lt": nxt_month},
|
||||
"createdAt": {"gte": low_time, "lte": top_time},
|
||||
"isActive": True,
|
||||
},
|
||||
)
|
||||
transaction_balance = (
|
||||
transactions[0].get("_sum", {}).get("amount", 0) if transactions else 0
|
||||
)
|
||||
return transaction_balance, datetime.min
|
||||
|
||||
if user_credit:
|
||||
credit_sum = user_credit[0].get("_sum") or {}
|
||||
return credit_sum.get("amount", 0)
|
||||
async def _enable_transaction(
|
||||
self, transaction_key: str, user_id: str, metadata: Json
|
||||
):
|
||||
|
||||
key = f"MONTHLY-CREDIT-TOP-UP-{cur_month}"
|
||||
transaction = await CreditTransaction.prisma().find_first_or_raise(
|
||||
where={"transactionKey": transaction_key, "userId": user_id}
|
||||
)
|
||||
|
||||
try:
|
||||
await CreditTransaction.prisma().create(
|
||||
if transaction.isActive:
|
||||
return
|
||||
|
||||
async with db.locked_transaction(f"usr_trx_{user_id}"):
|
||||
user_balance, _ = await self._get_credits(user_id)
|
||||
|
||||
await CreditTransaction.prisma().update(
|
||||
where={
|
||||
"creditTransactionIdentifier": {
|
||||
"transactionKey": transaction_key,
|
||||
"userId": user_id,
|
||||
}
|
||||
},
|
||||
data={
|
||||
"amount": self.num_user_credits_refill,
|
||||
"type": CreditTransactionType.TOP_UP,
|
||||
"userId": user_id,
|
||||
"transactionKey": key,
|
||||
"isActive": True,
|
||||
"runningBalance": user_balance + transaction.amount,
|
||||
"createdAt": self.time_now(),
|
||||
}
|
||||
"metadata": metadata,
|
||||
},
|
||||
)
|
||||
except UniqueViolationError:
|
||||
pass # Already refilled this month
|
||||
|
||||
return self.num_user_credits_refill
|
||||
async def _add_transaction(
|
||||
self,
|
||||
user_id: str,
|
||||
amount: int,
|
||||
transaction_type: CreditTransactionType,
|
||||
is_active: bool = True,
|
||||
transaction_key: str | None = None,
|
||||
metadata: Json = Json({}),
|
||||
):
|
||||
async with db.locked_transaction(f"usr_trx_{user_id}"):
|
||||
# Get latest balance snapshot
|
||||
user_balance, _ = await self._get_credits(user_id)
|
||||
if amount < 0 and user_balance < abs(amount):
|
||||
raise ValueError(
|
||||
f"Insufficient balance for user {user_id}, balance: {user_balance}, amount: {amount}"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def time_now():
|
||||
return datetime.now(timezone.utc)
|
||||
# Create the transaction
|
||||
transaction_data: CreditTransactionCreateInput = {
|
||||
"userId": user_id,
|
||||
"amount": amount,
|
||||
"runningBalance": user_balance + amount,
|
||||
"type": transaction_type,
|
||||
"metadata": metadata,
|
||||
"isActive": is_active,
|
||||
"createdAt": self.time_now(),
|
||||
}
|
||||
if transaction_key:
|
||||
transaction_data["transactionKey"] = transaction_key
|
||||
await CreditTransaction.prisma().create(data=transaction_data)
|
||||
|
||||
return user_balance + amount
|
||||
|
||||
|
||||
class UserCredit(UserCreditBase):
|
||||
|
||||
def _block_usage_cost(
|
||||
self,
|
||||
|
@ -148,8 +231,8 @@ class UserCredit(UserCreditBase):
|
|||
) -> bool:
|
||||
"""
|
||||
Filter rules:
|
||||
- If costFilter is an object, then check if costFilter is the subset of inputValues
|
||||
- Otherwise, check if costFilter is equal to inputValues.
|
||||
- If cost_filter is an object, then check if cost_filter is the subset of input_data
|
||||
- Otherwise, check if cost_filter is equal to input_data.
|
||||
- Undefined, null, and empty string are considered as equal.
|
||||
"""
|
||||
if not isinstance(cost_filter, dict) or not isinstance(input_data, dict):
|
||||
|
@ -163,57 +246,169 @@ class UserCredit(UserCreditBase):
|
|||
|
||||
async def spend_credits(
|
||||
self,
|
||||
user_id: str,
|
||||
user_credit: int,
|
||||
block_id: str,
|
||||
input_data: BlockInput,
|
||||
entry: NodeExecutionEntry,
|
||||
data_size: float,
|
||||
run_time: float,
|
||||
validate_balance: bool = True,
|
||||
) -> int:
|
||||
block = get_block(block_id)
|
||||
block = get_block(entry.block_id)
|
||||
if not block:
|
||||
raise ValueError(f"Block not found: {block_id}")
|
||||
raise ValueError(f"Block not found: {entry.block_id}")
|
||||
|
||||
cost, matching_filter = self._block_usage_cost(
|
||||
block=block, input_data=input_data, data_size=data_size, run_time=run_time
|
||||
block=block, input_data=entry.data, data_size=data_size, run_time=run_time
|
||||
)
|
||||
if cost <= 0:
|
||||
if cost == 0:
|
||||
return 0
|
||||
|
||||
if validate_balance and user_credit < cost:
|
||||
raise ValueError(f"Insufficient credit: {user_credit} < {cost}")
|
||||
|
||||
await CreditTransaction.prisma().create(
|
||||
data={
|
||||
"userId": user_id,
|
||||
"amount": -cost,
|
||||
"type": CreditTransactionType.USAGE,
|
||||
"blockId": block.id,
|
||||
"metadata": Json(
|
||||
{
|
||||
"block": block.name,
|
||||
"input": matching_filter,
|
||||
}
|
||||
),
|
||||
"createdAt": self.time_now(),
|
||||
}
|
||||
await self._add_transaction(
|
||||
user_id=entry.user_id,
|
||||
amount=-cost,
|
||||
transaction_type=CreditTransactionType.USAGE,
|
||||
metadata=Json(
|
||||
{
|
||||
"graph_exec_id": entry.graph_exec_id,
|
||||
"graph_id": entry.graph_id,
|
||||
"node_id": entry.node_id,
|
||||
"node_exec_id": entry.node_exec_id,
|
||||
"block_id": entry.block_id,
|
||||
"block": block.name,
|
||||
"input": matching_filter,
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
return cost
|
||||
|
||||
async def top_up_credits(self, user_id: str, amount: int):
|
||||
await CreditTransaction.prisma().create(
|
||||
data={
|
||||
"userId": user_id,
|
||||
"amount": amount,
|
||||
"type": CreditTransactionType.TOP_UP,
|
||||
"createdAt": self.time_now(),
|
||||
}
|
||||
if amount < 0:
|
||||
raise ValueError(f"Top up amount must not be negative: {amount}")
|
||||
|
||||
await self._add_transaction(
|
||||
user_id=user_id,
|
||||
amount=amount,
|
||||
transaction_type=CreditTransactionType.TOP_UP,
|
||||
)
|
||||
|
||||
async def top_up_intent(self, user_id: str, amount: int) -> str:
|
||||
# Create checkout session
|
||||
# https://docs.stripe.com/checkout/quickstart?client=react
|
||||
# unit_amount param is always in the smallest currency unit (so cents for usd)
|
||||
# which is equal to amount of credits
|
||||
checkout_session = stripe.checkout.Session.create(
|
||||
customer=await get_stripe_customer_id(user_id),
|
||||
line_items=[
|
||||
{
|
||||
"price_data": {
|
||||
"currency": "usd",
|
||||
"product_data": {
|
||||
"name": "AutoGPT Platform Credits",
|
||||
},
|
||||
"unit_amount": amount,
|
||||
},
|
||||
"quantity": 1,
|
||||
}
|
||||
],
|
||||
mode="payment",
|
||||
success_url=settings.config.platform_base_url
|
||||
+ "/marketplace/credits?topup=success",
|
||||
cancel_url=settings.config.platform_base_url
|
||||
+ "/marketplace/credits?topup=cancel",
|
||||
)
|
||||
|
||||
# Create pending transaction
|
||||
await self._add_transaction(
|
||||
user_id=user_id,
|
||||
amount=amount,
|
||||
transaction_type=CreditTransactionType.TOP_UP,
|
||||
transaction_key=checkout_session.id,
|
||||
is_active=False,
|
||||
metadata=Json({"checkout_session": checkout_session}),
|
||||
)
|
||||
|
||||
return checkout_session.url or ""
|
||||
|
||||
# https://docs.stripe.com/checkout/fulfillment
|
||||
async def fulfill_checkout(
|
||||
self, *, session_id: str | None = None, user_id: str | None = None
|
||||
):
|
||||
if (not session_id and not user_id) or (session_id and user_id):
|
||||
raise ValueError("Either session_id or user_id must be provided")
|
||||
|
||||
# Retrieve CreditTransaction
|
||||
find_filter: CreditTransactionWhereInput = {
|
||||
"type": CreditTransactionType.TOP_UP,
|
||||
"isActive": False,
|
||||
}
|
||||
if session_id:
|
||||
find_filter["transactionKey"] = session_id
|
||||
if user_id:
|
||||
find_filter["userId"] = user_id
|
||||
|
||||
# Find the most recent inactive top-up transaction
|
||||
credit_transaction = await CreditTransaction.prisma().find_first_or_raise(
|
||||
where=find_filter,
|
||||
order={"createdAt": "desc"},
|
||||
)
|
||||
|
||||
# This can be called multiple times for one id, so ignore if already fulfilled
|
||||
if not credit_transaction:
|
||||
return
|
||||
|
||||
# Retrieve the Checkout Session from the API
|
||||
checkout_session = stripe.checkout.Session.retrieve(
|
||||
credit_transaction.transactionKey
|
||||
)
|
||||
|
||||
# Check the Checkout Session's payment_status property
|
||||
# to determine if fulfillment should be performed
|
||||
if checkout_session.payment_status in ["paid", "no_payment_required"]:
|
||||
await self._enable_transaction(
|
||||
transaction_key=credit_transaction.transactionKey,
|
||||
user_id=credit_transaction.userId,
|
||||
metadata=Json({"checkout_session": checkout_session}),
|
||||
)
|
||||
|
||||
async def get_credits(self, user_id: str) -> int:
|
||||
balance, _ = await self._get_credits(user_id)
|
||||
return balance
|
||||
|
||||
|
||||
class BetaUserCredit(UserCredit):
|
||||
"""
|
||||
This is a temporary class to handle the test user utilizing monthly credit refill.
|
||||
TODO: Remove this class & its feature toggle.
|
||||
"""
|
||||
|
||||
def __init__(self, num_user_credits_refill: int):
|
||||
self.num_user_credits_refill = num_user_credits_refill
|
||||
|
||||
async def get_credits(self, user_id: str) -> int:
|
||||
cur_time = self.time_now().date()
|
||||
balance, snapshot_time = await self._get_credits(user_id)
|
||||
if (snapshot_time.year, snapshot_time.month) == (cur_time.year, cur_time.month):
|
||||
return balance
|
||||
|
||||
try:
|
||||
await CreditTransaction.prisma().create(
|
||||
data={
|
||||
"transactionKey": f"MONTHLY-CREDIT-TOP-UP-{cur_time}",
|
||||
"userId": user_id,
|
||||
"amount": self.num_user_credits_refill,
|
||||
"runningBalance": self.num_user_credits_refill,
|
||||
"type": CreditTransactionType.TOP_UP,
|
||||
"metadata": Json({}),
|
||||
"isActive": True,
|
||||
"createdAt": self.time_now(),
|
||||
}
|
||||
)
|
||||
except UniqueViolationError:
|
||||
pass # Already refilled this month
|
||||
|
||||
return self.num_user_credits_refill
|
||||
|
||||
|
||||
class DisabledUserCredit(UserCreditBase):
|
||||
async def get_or_refill_credit(self, *args, **kwargs) -> int:
|
||||
async def get_credits(self, *args, **kwargs) -> int:
|
||||
return 0
|
||||
|
||||
async def spend_credits(self, *args, **kwargs) -> int:
|
||||
|
@ -222,13 +417,37 @@ class DisabledUserCredit(UserCreditBase):
|
|||
async def top_up_credits(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
async def top_up_intent(self, *args, **kwargs) -> str:
|
||||
return ""
|
||||
|
||||
async def fulfill_checkout(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
def get_user_credit_model() -> UserCreditBase:
|
||||
if config.enable_credit.lower() == "true":
|
||||
return UserCredit(config.num_user_credits_refill)
|
||||
else:
|
||||
return DisabledUserCredit(0)
|
||||
if not settings.config.enable_credit:
|
||||
return DisabledUserCredit()
|
||||
|
||||
if settings.config.enable_beta_monthly_credit:
|
||||
return BetaUserCredit(settings.config.num_user_credits_refill)
|
||||
|
||||
return UserCredit()
|
||||
|
||||
|
||||
def get_block_costs() -> dict[str, list[BlockCost]]:
|
||||
return {block().id: costs for block, costs in BLOCK_COSTS.items()}
|
||||
|
||||
|
||||
async def get_stripe_customer_id(user_id: str) -> str:
|
||||
user = await get_user_by_id(user_id)
|
||||
if not user:
|
||||
raise ValueError(f"User not found: {user_id}")
|
||||
|
||||
if user.stripeCustomerId:
|
||||
return user.stripeCustomerId
|
||||
|
||||
customer = stripe.Customer.create(name=user.name or "", email=user.email)
|
||||
await User.prisma().update(
|
||||
where={"id": user_id}, data={"stripeCustomerId": customer.id}
|
||||
)
|
||||
return customer.id
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import logging
|
||||
import os
|
||||
import zlib
|
||||
from contextlib import asynccontextmanager
|
||||
from uuid import uuid4
|
||||
|
||||
|
@ -54,6 +55,14 @@ async def transaction():
|
|||
yield tx
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def locked_transaction(key: str):
|
||||
lock_key = zlib.crc32(key.encode("utf-8"))
|
||||
async with transaction() as tx:
|
||||
await tx.execute_raw(f"SELECT pg_advisory_xact_lock({lock_key})")
|
||||
yield tx
|
||||
|
||||
|
||||
class BaseDbModel(BaseModel):
|
||||
id: str = Field(default_factory=lambda: str(uuid4()))
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
from collections import defaultdict
|
||||
from datetime import datetime, timezone
|
||||
from multiprocessing import Manager
|
||||
from typing import Any, AsyncGenerator, Generator, Generic, TypeVar
|
||||
from typing import Any, AsyncGenerator, Generator, Generic, Optional, TypeVar
|
||||
|
||||
from prisma.enums import AgentExecutionStatus
|
||||
from prisma.errors import PrismaError
|
||||
from prisma.models import (
|
||||
AgentGraphExecution,
|
||||
AgentNodeExecution,
|
||||
|
@ -31,6 +32,7 @@ class NodeExecutionEntry(BaseModel):
|
|||
graph_id: str
|
||||
node_exec_id: str
|
||||
node_id: str
|
||||
block_id: str
|
||||
data: BlockInput
|
||||
|
||||
|
||||
|
@ -324,6 +326,30 @@ async def update_execution_status(
|
|||
return ExecutionResult.from_db(res)
|
||||
|
||||
|
||||
async def get_execution(
|
||||
execution_id: str, user_id: str
|
||||
) -> Optional[AgentNodeExecution]:
|
||||
"""
|
||||
Get an execution by ID. Returns None if not found.
|
||||
|
||||
Args:
|
||||
execution_id: The ID of the execution to retrieve
|
||||
|
||||
Returns:
|
||||
The execution if found, None otherwise
|
||||
"""
|
||||
try:
|
||||
execution = await AgentNodeExecution.prisma().find_unique(
|
||||
where={
|
||||
"id": execution_id,
|
||||
"userId": user_id,
|
||||
}
|
||||
)
|
||||
return execution
|
||||
except PrismaError:
|
||||
return None
|
||||
|
||||
|
||||
async def get_execution_results(graph_exec_id: str) -> list[ExecutionResult]:
|
||||
executions = await AgentNodeExecution.prisma().find_many(
|
||||
where={"agentGraphExecutionId": graph_exec_id},
|
||||
|
|
|
@ -4,6 +4,7 @@ from typing import Any, Callable, Concatenate, Coroutine, ParamSpec, TypeVar, ca
|
|||
from backend.data.credit import get_user_credit_model
|
||||
from backend.data.execution import (
|
||||
ExecutionResult,
|
||||
NodeExecutionEntry,
|
||||
RedisExecutionEventBus,
|
||||
create_graph_execution,
|
||||
get_execution_results,
|
||||
|
@ -78,12 +79,8 @@ class DatabaseManager(AppService):
|
|||
|
||||
# Credits
|
||||
user_credit_model = get_user_credit_model()
|
||||
get_or_refill_credit = cast(
|
||||
Callable[[Any, str], int],
|
||||
exposed_run_and_wait(user_credit_model.get_or_refill_credit),
|
||||
)
|
||||
spend_credits = cast(
|
||||
Callable[[Any, str, int, str, dict[str, str], float, float], int],
|
||||
Callable[[Any, NodeExecutionEntry, float, float], int],
|
||||
exposed_run_and_wait(user_credit_model.spend_credits),
|
||||
)
|
||||
|
||||
|
|
|
@ -183,9 +183,6 @@ def execute_node(
|
|||
|
||||
output_size = 0
|
||||
end_status = ExecutionStatus.COMPLETED
|
||||
credit = db_client.get_or_refill_credit(user_id)
|
||||
if credit < 0:
|
||||
raise ValueError(f"Insufficient credit: {credit}")
|
||||
|
||||
try:
|
||||
for output_name, output_data in node_block.execute(
|
||||
|
@ -241,7 +238,8 @@ def execute_node(
|
|||
if res.end_time and res.start_time
|
||||
else 0
|
||||
)
|
||||
db_client.spend_credits(user_id, credit, node_block.id, input_data, s, t)
|
||||
data.data = input_data
|
||||
db_client.spend_credits(data, s, t)
|
||||
|
||||
# Update execution stats
|
||||
if execution_stats is not None:
|
||||
|
@ -260,7 +258,7 @@ def _enqueue_next_nodes(
|
|||
log_metadata: LogMetadata,
|
||||
) -> list[NodeExecutionEntry]:
|
||||
def add_enqueued_execution(
|
||||
node_exec_id: str, node_id: str, data: BlockInput
|
||||
node_exec_id: str, node_id: str, block_id: str, data: BlockInput
|
||||
) -> NodeExecutionEntry:
|
||||
exec_update = db_client.update_execution_status(
|
||||
node_exec_id, ExecutionStatus.QUEUED, data
|
||||
|
@ -272,6 +270,7 @@ def _enqueue_next_nodes(
|
|||
graph_id=graph_id,
|
||||
node_exec_id=node_exec_id,
|
||||
node_id=node_id,
|
||||
block_id=block_id,
|
||||
data=data,
|
||||
)
|
||||
|
||||
|
@ -325,7 +324,12 @@ def _enqueue_next_nodes(
|
|||
# Input is complete, enqueue the execution.
|
||||
log_metadata.info(f"Enqueued {suffix}")
|
||||
enqueued_executions.append(
|
||||
add_enqueued_execution(next_node_exec_id, next_node_id, next_node_input)
|
||||
add_enqueued_execution(
|
||||
node_exec_id=next_node_exec_id,
|
||||
node_id=next_node_id,
|
||||
block_id=next_node.block_id,
|
||||
data=next_node_input,
|
||||
)
|
||||
)
|
||||
|
||||
# Next execution stops here if the link is not static.
|
||||
|
@ -355,7 +359,12 @@ def _enqueue_next_nodes(
|
|||
continue
|
||||
log_metadata.info(f"Enqueueing static-link execution {suffix}")
|
||||
enqueued_executions.append(
|
||||
add_enqueued_execution(iexec.node_exec_id, next_node_id, idata)
|
||||
add_enqueued_execution(
|
||||
node_exec_id=iexec.node_exec_id,
|
||||
node_id=next_node_id,
|
||||
block_id=next_node.block_id,
|
||||
data=idata,
|
||||
)
|
||||
)
|
||||
return enqueued_executions
|
||||
|
||||
|
@ -803,8 +812,8 @@ class ExecutionManager(AppService):
|
|||
# Extract request input data, and assign it to the input pin.
|
||||
if block.block_type == BlockType.INPUT:
|
||||
name = node.input_default.get("name")
|
||||
if name and name in data:
|
||||
input_data = {"value": data[name]}
|
||||
if name in data.get("node_input", {}):
|
||||
input_data = {"value": data["node_input"][name]}
|
||||
|
||||
# Extract webhook payload, and assign it to the input pin
|
||||
webhook_payload_key = f"webhook_{node.webhook_id}_payload"
|
||||
|
@ -840,6 +849,7 @@ class ExecutionManager(AppService):
|
|||
graph_id=node_exec.graph_id,
|
||||
node_exec_id=node_exec.node_exec_id,
|
||||
node_id=node_exec.node_id,
|
||||
block_id=node_exec.block_id,
|
||||
data=node_exec.input_data,
|
||||
)
|
||||
)
|
||||
|
|
|
@ -23,6 +23,15 @@ from backend.util.settings import Settings
|
|||
|
||||
settings = Settings()
|
||||
|
||||
# This is an overrride since ollama doesn't actually require an API key, but the creddential system enforces one be attached
|
||||
ollama_credentials = APIKeyCredentials(
|
||||
id="744fdc56-071a-4761-b5a5-0af0ce10a2b5",
|
||||
provider="ollama",
|
||||
api_key=SecretStr("FAKE_API_KEY"),
|
||||
title="Use Credits for Ollama",
|
||||
expires_at=None,
|
||||
)
|
||||
|
||||
revid_credentials = APIKeyCredentials(
|
||||
id="fdb7f412-f519-48d1-9b5f-d2f73d0e01fe",
|
||||
provider="revid",
|
||||
|
@ -124,6 +133,7 @@ nvidia_credentials = APIKeyCredentials(
|
|||
|
||||
|
||||
DEFAULT_CREDENTIALS = [
|
||||
ollama_credentials,
|
||||
revid_credentials,
|
||||
ideogram_credentials,
|
||||
replicate_credentials,
|
||||
|
@ -169,6 +179,10 @@ class IntegrationCredentialsStore:
|
|||
def get_all_creds(self, user_id: str) -> list[Credentials]:
|
||||
users_credentials = self._get_user_integrations(user_id).credentials
|
||||
all_credentials = users_credentials
|
||||
# These will always be added
|
||||
all_credentials.append(ollama_credentials)
|
||||
|
||||
# These will only be added if the API key is set
|
||||
if settings.secrets.revid_api_key:
|
||||
all_credentials.append(revid_credentials)
|
||||
if settings.secrets.ideogram_api_key:
|
||||
|
|
|
@ -2,6 +2,7 @@ from typing import TYPE_CHECKING
|
|||
|
||||
from .github import GitHubOAuthHandler
|
||||
from .google import GoogleOAuthHandler
|
||||
from .linear import LinearOAuthHandler
|
||||
from .notion import NotionOAuthHandler
|
||||
from .twitter import TwitterOAuthHandler
|
||||
|
||||
|
@ -17,6 +18,7 @@ HANDLERS_BY_NAME: dict["ProviderName", type["BaseOAuthHandler"]] = {
|
|||
GoogleOAuthHandler,
|
||||
NotionOAuthHandler,
|
||||
TwitterOAuthHandler,
|
||||
LinearOAuthHandler,
|
||||
]
|
||||
}
|
||||
# --8<-- [end:HANDLERS_BY_NAMEExample]
|
||||
|
|
|
@ -0,0 +1,165 @@
|
|||
import json
|
||||
from typing import Optional
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from pydantic import SecretStr
|
||||
|
||||
from backend.blocks.linear._api import LinearAPIException
|
||||
from backend.data.model import APIKeyCredentials, OAuth2Credentials
|
||||
from backend.integrations.providers import ProviderName
|
||||
from backend.util.request import requests
|
||||
|
||||
from .base import BaseOAuthHandler
|
||||
|
||||
|
||||
class LinearOAuthHandler(BaseOAuthHandler):
|
||||
"""
|
||||
OAuth2 handler for Linear.
|
||||
"""
|
||||
|
||||
PROVIDER_NAME = ProviderName.LINEAR
|
||||
|
||||
def __init__(self, client_id: str, client_secret: str, redirect_uri: str):
|
||||
self.client_id = client_id
|
||||
self.client_secret = client_secret
|
||||
self.redirect_uri = redirect_uri
|
||||
self.auth_base_url = "https://linear.app/oauth/authorize"
|
||||
self.token_url = "https://api.linear.app/oauth/token" # Correct token URL
|
||||
self.revoke_url = "https://api.linear.app/oauth/revoke"
|
||||
|
||||
def get_login_url(
|
||||
self, scopes: list[str], state: str, code_challenge: Optional[str]
|
||||
) -> str:
|
||||
|
||||
params = {
|
||||
"client_id": self.client_id,
|
||||
"redirect_uri": self.redirect_uri,
|
||||
"response_type": "code", # Important: include "response_type"
|
||||
"scope": ",".join(scopes), # Comma-separated, not space-separated
|
||||
"state": state,
|
||||
}
|
||||
return f"{self.auth_base_url}?{urlencode(params)}"
|
||||
|
||||
def exchange_code_for_tokens(
|
||||
self, code: str, scopes: list[str], code_verifier: Optional[str]
|
||||
) -> OAuth2Credentials:
|
||||
return self._request_tokens({"code": code, "redirect_uri": self.redirect_uri})
|
||||
|
||||
def revoke_tokens(self, credentials: OAuth2Credentials) -> bool:
|
||||
if not credentials.access_token:
|
||||
raise ValueError("No access token to revoke")
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {credentials.access_token.get_secret_value()}"
|
||||
}
|
||||
|
||||
response = requests.post(self.revoke_url, headers=headers)
|
||||
if not response.ok:
|
||||
try:
|
||||
error_data = response.json()
|
||||
error_message = error_data.get("error", "Unknown error")
|
||||
except json.JSONDecodeError:
|
||||
error_message = response.text
|
||||
raise LinearAPIException(
|
||||
f"Failed to revoke Linear tokens ({response.status_code}): {error_message}",
|
||||
response.status_code,
|
||||
)
|
||||
|
||||
return True # Linear doesn't return JSON on successful revoke
|
||||
|
||||
def _refresh_tokens(self, credentials: OAuth2Credentials) -> OAuth2Credentials:
|
||||
if not credentials.refresh_token:
|
||||
raise ValueError(
|
||||
"No refresh token available."
|
||||
) # Linear uses non-expiring tokens
|
||||
|
||||
return self._request_tokens(
|
||||
{
|
||||
"refresh_token": credentials.refresh_token.get_secret_value(),
|
||||
"grant_type": "refresh_token",
|
||||
}
|
||||
)
|
||||
|
||||
def _request_tokens(
|
||||
self,
|
||||
params: dict[str, str],
|
||||
current_credentials: Optional[OAuth2Credentials] = None,
|
||||
) -> OAuth2Credentials:
|
||||
request_body = {
|
||||
"client_id": self.client_id,
|
||||
"client_secret": self.client_secret,
|
||||
"grant_type": "authorization_code", # Ensure grant_type is correct
|
||||
**params,
|
||||
}
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/x-www-form-urlencoded"
|
||||
} # Correct header for token request
|
||||
response = requests.post(self.token_url, data=request_body, headers=headers)
|
||||
|
||||
if not response.ok:
|
||||
try:
|
||||
error_data = response.json()
|
||||
error_message = error_data.get("error", "Unknown error")
|
||||
|
||||
except json.JSONDecodeError:
|
||||
error_message = response.text
|
||||
raise LinearAPIException(
|
||||
f"Failed to fetch Linear tokens ({response.status_code}): {error_message}",
|
||||
response.status_code,
|
||||
)
|
||||
|
||||
token_data = response.json()
|
||||
|
||||
# Note: Linear access tokens do not expire, so we set expires_at to None
|
||||
new_credentials = OAuth2Credentials(
|
||||
provider=self.PROVIDER_NAME,
|
||||
title=current_credentials.title if current_credentials else None,
|
||||
username=token_data.get("user", {}).get(
|
||||
"name", "Unknown User"
|
||||
), # extract name or set appropriate
|
||||
access_token=token_data["access_token"],
|
||||
scopes=token_data["scope"].split(
|
||||
","
|
||||
), # Linear returns comma-separated scopes
|
||||
refresh_token=token_data.get(
|
||||
"refresh_token"
|
||||
), # Linear uses non-expiring tokens so this might be null
|
||||
access_token_expires_at=None,
|
||||
refresh_token_expires_at=None,
|
||||
)
|
||||
if current_credentials:
|
||||
new_credentials.id = current_credentials.id
|
||||
return new_credentials
|
||||
|
||||
def _request_username(self, access_token: str) -> Optional[str]:
|
||||
|
||||
# Use the LinearClient to fetch user details using GraphQL
|
||||
from backend.blocks.linear._api import LinearClient
|
||||
|
||||
try:
|
||||
|
||||
linear_client = LinearClient(
|
||||
APIKeyCredentials(
|
||||
api_key=SecretStr(access_token),
|
||||
title="temp",
|
||||
provider=self.PROVIDER_NAME,
|
||||
expires_at=None,
|
||||
)
|
||||
) # Temporary credentials for this request
|
||||
|
||||
query = """
|
||||
query Viewer {
|
||||
viewer {
|
||||
name
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
response = linear_client.query(query)
|
||||
return response["viewer"]["name"]
|
||||
|
||||
except Exception as e: # Handle any errors
|
||||
|
||||
print(f"Error fetching username: {e}")
|
||||
return None
|
|
@ -17,6 +17,7 @@ class ProviderName(str, Enum):
|
|||
HUBSPOT = "hubspot"
|
||||
IDEOGRAM = "ideogram"
|
||||
JINA = "jina"
|
||||
LINEAR = "linear"
|
||||
MEDIUM = "medium"
|
||||
NOTION = "notion"
|
||||
NVIDIA = "nvidia"
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
from fastapi import FastAPI
|
||||
|
||||
from .routes.v1 import v1_router
|
||||
|
||||
external_app = FastAPI(
|
||||
title="AutoGPT External API",
|
||||
description="External API for AutoGPT integrations",
|
||||
docs_url="/docs",
|
||||
version="1.0",
|
||||
)
|
||||
external_app.include_router(v1_router, prefix="/v1")
|
|
@ -0,0 +1,37 @@
|
|||
from fastapi import Depends, HTTPException, Request
|
||||
from fastapi.security import APIKeyHeader
|
||||
from prisma.enums import APIKeyPermission
|
||||
|
||||
from backend.data.api_key import has_permission, validate_api_key
|
||||
|
||||
api_key_header = APIKeyHeader(name="X-API-Key")
|
||||
|
||||
|
||||
async def require_api_key(request: Request):
|
||||
"""Base middleware for API key authentication"""
|
||||
api_key = await api_key_header(request)
|
||||
|
||||
if api_key is None:
|
||||
raise HTTPException(status_code=401, detail="Missing API key")
|
||||
|
||||
api_key_obj = await validate_api_key(api_key)
|
||||
|
||||
if not api_key_obj:
|
||||
raise HTTPException(status_code=401, detail="Invalid API key")
|
||||
|
||||
request.state.api_key = api_key_obj
|
||||
return api_key_obj
|
||||
|
||||
|
||||
def require_permission(permission: APIKeyPermission):
|
||||
"""Dependency function for checking specific permissions"""
|
||||
|
||||
async def check_permission(api_key=Depends(require_api_key)):
|
||||
if not has_permission(api_key, permission):
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail=f"API key missing required permission: {permission}",
|
||||
)
|
||||
return api_key
|
||||
|
||||
return check_permission
|
|
@ -0,0 +1,111 @@
|
|||
import logging
|
||||
from collections import defaultdict
|
||||
from typing import Any, Sequence
|
||||
|
||||
from autogpt_libs.utils.cache import thread_cached
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from prisma.enums import APIKeyPermission
|
||||
|
||||
import backend.data.block
|
||||
from backend.data import execution as execution_db
|
||||
from backend.data import graph as graph_db
|
||||
from backend.data.api_key import APIKey
|
||||
from backend.data.block import BlockInput, CompletedBlockOutput
|
||||
from backend.executor import ExecutionManager
|
||||
from backend.server.external.middleware import require_permission
|
||||
from backend.util.service import get_service_client
|
||||
from backend.util.settings import Settings
|
||||
|
||||
|
||||
@thread_cached
|
||||
def execution_manager_client() -> ExecutionManager:
|
||||
return get_service_client(ExecutionManager)
|
||||
|
||||
|
||||
settings = Settings()
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
v1_router = APIRouter()
|
||||
|
||||
|
||||
@v1_router.get(
|
||||
path="/blocks",
|
||||
tags=["blocks"],
|
||||
dependencies=[Depends(require_permission(APIKeyPermission.READ_BLOCK))],
|
||||
)
|
||||
def get_graph_blocks() -> Sequence[dict[Any, Any]]:
|
||||
blocks = [block() for block in backend.data.block.get_blocks().values()]
|
||||
return [b.to_dict() for b in blocks]
|
||||
|
||||
|
||||
@v1_router.post(
|
||||
path="/blocks/{block_id}/execute",
|
||||
tags=["blocks"],
|
||||
dependencies=[Depends(require_permission(APIKeyPermission.EXECUTE_BLOCK))],
|
||||
)
|
||||
def execute_graph_block(
|
||||
block_id: str,
|
||||
data: BlockInput,
|
||||
api_key: APIKey = Depends(require_permission(APIKeyPermission.EXECUTE_BLOCK)),
|
||||
) -> CompletedBlockOutput:
|
||||
obj = backend.data.block.get_block(block_id)
|
||||
if not obj:
|
||||
raise HTTPException(status_code=404, detail=f"Block #{block_id} not found.")
|
||||
|
||||
output = defaultdict(list)
|
||||
for name, data in obj.execute(data):
|
||||
output[name].append(data)
|
||||
return output
|
||||
|
||||
|
||||
@v1_router.post(
|
||||
path="/graphs/{graph_id}/execute",
|
||||
tags=["graphs"],
|
||||
)
|
||||
def execute_graph(
|
||||
graph_id: str,
|
||||
node_input: dict[Any, Any],
|
||||
api_key: APIKey = Depends(require_permission(APIKeyPermission.EXECUTE_GRAPH)),
|
||||
) -> dict[str, Any]:
|
||||
try:
|
||||
graph_exec = execution_manager_client().add_execution(
|
||||
graph_id, node_input, user_id=api_key.user_id
|
||||
)
|
||||
return {"id": graph_exec.graph_exec_id}
|
||||
except Exception as e:
|
||||
msg = e.__str__().encode().decode("unicode_escape")
|
||||
raise HTTPException(status_code=400, detail=msg)
|
||||
|
||||
|
||||
@v1_router.get(
|
||||
path="/graphs/{graph_id}/executions/{graph_exec_id}/results",
|
||||
tags=["graphs"],
|
||||
)
|
||||
async def get_graph_execution_results(
|
||||
graph_id: str,
|
||||
graph_exec_id: str,
|
||||
api_key: APIKey = Depends(require_permission(APIKeyPermission.READ_GRAPH)),
|
||||
) -> dict:
|
||||
graph = await graph_db.get_graph(graph_id, user_id=api_key.user_id)
|
||||
if not graph:
|
||||
raise HTTPException(status_code=404, detail=f"Graph #{graph_id} not found.")
|
||||
|
||||
results = await execution_db.get_execution_results(graph_exec_id)
|
||||
|
||||
return {
|
||||
"execution_id": graph_exec_id,
|
||||
"nodes": [
|
||||
{
|
||||
"node_id": result.node_id,
|
||||
"input": (
|
||||
result.input_data.get("value")
|
||||
if "value" in result.input_data
|
||||
else result.input_data
|
||||
),
|
||||
"output": result.output_data.get(
|
||||
"response", result.output_data.get("result", [])
|
||||
),
|
||||
}
|
||||
for result in results
|
||||
],
|
||||
}
|
|
@ -110,6 +110,11 @@ def callback(
|
|||
|
||||
logger.debug(f"Received credentials with final scopes: {credentials.scopes}")
|
||||
|
||||
# Linear returns scopes as a single string with spaces, so we need to split them
|
||||
# TODO: make a bypass of this part of the OAuth handler
|
||||
if len(credentials.scopes) == 1 and " " in credentials.scopes[0]:
|
||||
credentials.scopes = credentials.scopes[0].split(" ")
|
||||
|
||||
# Check if the granted scopes are sufficient for the requested scopes
|
||||
if not set(scopes).issubset(set(credentials.scopes)):
|
||||
# For now, we'll just log the warning and continue
|
||||
|
|
|
@ -56,3 +56,8 @@ class SetGraphActiveVersion(pydantic.BaseModel):
|
|||
|
||||
class UpdatePermissionsRequest(pydantic.BaseModel):
|
||||
permissions: List[APIKeyPermission]
|
||||
|
||||
|
||||
class RequestTopUp(pydantic.BaseModel):
|
||||
amount: int
|
||||
"""Amount of credits to top up."""
|
||||
|
|
|
@ -20,6 +20,7 @@ import backend.server.v2.library.routes
|
|||
import backend.server.v2.store.routes
|
||||
import backend.util.service
|
||||
import backend.util.settings
|
||||
from backend.server.external.api import external_app
|
||||
|
||||
settings = backend.util.settings.Settings()
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -94,6 +95,8 @@ app.include_router(
|
|||
backend.server.v2.library.routes.router, tags=["v2"], prefix="/api/library"
|
||||
)
|
||||
|
||||
app.mount("/external-api", external_app)
|
||||
|
||||
|
||||
@app.get(path="/health", tags=["health"], dependencies=[])
|
||||
async def health():
|
||||
|
|
|
@ -4,10 +4,11 @@ from collections import defaultdict
|
|||
from typing import TYPE_CHECKING, Annotated, Any, Sequence
|
||||
|
||||
import pydantic
|
||||
import stripe
|
||||
from autogpt_libs.auth.middleware import auth_middleware
|
||||
from autogpt_libs.feature_flag.client import feature_flag
|
||||
from autogpt_libs.utils.cache import thread_cached
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, Response
|
||||
from typing_extensions import Optional, TypedDict
|
||||
|
||||
import backend.data.block
|
||||
|
@ -28,7 +29,11 @@ from backend.data.api_key import (
|
|||
update_api_key_permissions,
|
||||
)
|
||||
from backend.data.block import BlockInput, CompletedBlockOutput
|
||||
from backend.data.credit import get_block_costs, get_user_credit_model
|
||||
from backend.data.credit import (
|
||||
get_block_costs,
|
||||
get_stripe_customer_id,
|
||||
get_user_credit_model,
|
||||
)
|
||||
from backend.data.user import get_or_create_user
|
||||
from backend.executor import ExecutionManager, ExecutionScheduler, scheduler
|
||||
from backend.integrations.creds_manager import IntegrationCredentialsManager
|
||||
|
@ -40,6 +45,7 @@ from backend.server.model import (
|
|||
CreateAPIKeyRequest,
|
||||
CreateAPIKeyResponse,
|
||||
CreateGraph,
|
||||
RequestTopUp,
|
||||
SetGraphActiveVersion,
|
||||
UpdatePermissionsRequest,
|
||||
)
|
||||
|
@ -134,7 +140,69 @@ async def get_user_credits(
|
|||
user_id: Annotated[str, Depends(get_user_id)],
|
||||
) -> dict[str, int]:
|
||||
# Credits can go negative, so ensure it's at least 0 for user to see.
|
||||
return {"credits": max(await _user_credit_model.get_or_refill_credit(user_id), 0)}
|
||||
return {"credits": max(await _user_credit_model.get_credits(user_id), 0)}
|
||||
|
||||
|
||||
@v1_router.post(
|
||||
path="/credits", tags=["credits"], dependencies=[Depends(auth_middleware)]
|
||||
)
|
||||
async def request_top_up(
|
||||
request: RequestTopUp, user_id: Annotated[str, Depends(get_user_id)]
|
||||
):
|
||||
checkout_url = await _user_credit_model.top_up_intent(user_id, request.amount)
|
||||
return {"checkout_url": checkout_url}
|
||||
|
||||
|
||||
@v1_router.patch(
|
||||
path="/credits", tags=["credits"], dependencies=[Depends(auth_middleware)]
|
||||
)
|
||||
async def fulfill_checkout(user_id: Annotated[str, Depends(get_user_id)]):
|
||||
await _user_credit_model.fulfill_checkout(user_id=user_id)
|
||||
return Response(status_code=200)
|
||||
|
||||
|
||||
@v1_router.post(path="/credits/stripe_webhook", tags=["credits"])
|
||||
async def stripe_webhook(request: Request):
|
||||
# Get the raw request body
|
||||
payload = await request.body()
|
||||
# Get the signature header
|
||||
sig_header = request.headers.get("stripe-signature")
|
||||
|
||||
try:
|
||||
event = stripe.Webhook.construct_event(
|
||||
payload, sig_header, settings.secrets.stripe_webhook_secret
|
||||
)
|
||||
except ValueError:
|
||||
# Invalid payload
|
||||
raise HTTPException(status_code=400)
|
||||
except stripe.SignatureVerificationError:
|
||||
# Invalid signature
|
||||
raise HTTPException(status_code=400)
|
||||
|
||||
if (
|
||||
event["type"] == "checkout.session.completed"
|
||||
or event["type"] == "checkout.session.async_payment_succeeded"
|
||||
):
|
||||
await _user_credit_model.fulfill_checkout(
|
||||
session_id=event["data"]["object"]["id"]
|
||||
)
|
||||
|
||||
return Response(status_code=200)
|
||||
|
||||
|
||||
@v1_router.get(path="/credits/manage", dependencies=[Depends(auth_middleware)])
|
||||
async def manage_payment_method(
|
||||
user_id: Annotated[str, Depends(get_user_id)],
|
||||
) -> dict[str, str]:
|
||||
session = stripe.billing_portal.Session.create(
|
||||
customer=await get_stripe_customer_id(user_id),
|
||||
return_url=settings.config.platform_base_url + "/marketplace/credits",
|
||||
)
|
||||
if not session:
|
||||
raise HTTPException(
|
||||
status_code=400, detail="Failed to create billing portal session"
|
||||
)
|
||||
return {"url": session.url}
|
||||
|
||||
|
||||
########################################################
|
||||
|
@ -545,7 +613,6 @@ def get_execution_schedules(
|
|||
tags=["api-keys"],
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
)
|
||||
@feature_flag("api-keys-enabled")
|
||||
async def create_api_key(
|
||||
request: CreateAPIKeyRequest, user_id: Annotated[str, Depends(get_user_id)]
|
||||
) -> CreateAPIKeyResponse:
|
||||
|
@ -569,7 +636,6 @@ async def create_api_key(
|
|||
tags=["api-keys"],
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
)
|
||||
@feature_flag("api-keys-enabled")
|
||||
async def get_api_keys(
|
||||
user_id: Annotated[str, Depends(get_user_id)]
|
||||
) -> list[APIKeyWithoutHash]:
|
||||
|
@ -587,7 +653,6 @@ async def get_api_keys(
|
|||
tags=["api-keys"],
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
)
|
||||
@feature_flag("api-keys-enabled")
|
||||
async def get_api_key(
|
||||
key_id: str, user_id: Annotated[str, Depends(get_user_id)]
|
||||
) -> APIKeyWithoutHash:
|
||||
|
|
|
@ -81,10 +81,14 @@ class Config(UpdateTrackingModel["Config"], BaseSettings):
|
|||
default=True,
|
||||
description="If authentication is enabled or not",
|
||||
)
|
||||
enable_credit: str = Field(
|
||||
default="false",
|
||||
enable_credit: bool = Field(
|
||||
default=False,
|
||||
description="If user credit system is enabled or not",
|
||||
)
|
||||
enable_beta_monthly_credit: bool = Field(
|
||||
default=True,
|
||||
description="If beta monthly credits accounting is enabled or not",
|
||||
)
|
||||
num_user_credits_refill: int = Field(
|
||||
default=1500,
|
||||
description="Number of credits to refill for each user",
|
||||
|
@ -309,6 +313,12 @@ class Secrets(UpdateTrackingModel["Secrets"], BaseSettings):
|
|||
e2b_api_key: str = Field(default="", description="E2B API key")
|
||||
nvidia_api_key: str = Field(default="", description="Nvidia API key")
|
||||
|
||||
linear_client_id: str = Field(default="", description="Linear client ID")
|
||||
linear_client_secret: str = Field(default="", description="Linear client secret")
|
||||
|
||||
stripe_api_key: str = Field(default="", description="Stripe API Key")
|
||||
stripe_webhook_secret: str = Field(default="", description="Stripe Webhook Secret")
|
||||
|
||||
# Add more secret fields as needed
|
||||
|
||||
model_config = SettingsConfigDict(
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE "CreditTransaction" ADD COLUMN "runningBalance" INTEGER;
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `blockId` on the `CreditTransaction` table. All the data in the column will be moved to metadata->block_id.
|
||||
|
||||
*/
|
||||
BEGIN;
|
||||
|
||||
-- DropForeignKey blockId
|
||||
ALTER TABLE "CreditTransaction" DROP CONSTRAINT "CreditTransaction_blockId_fkey";
|
||||
|
||||
-- Update migrate blockId into metadata->"block_id"
|
||||
UPDATE "CreditTransaction"
|
||||
SET "metadata" = jsonb_set(
|
||||
COALESCE("metadata"::jsonb, '{}'),
|
||||
'{block_id}',
|
||||
to_jsonb("blockId")
|
||||
)
|
||||
WHERE "blockId" IS NOT NULL;
|
||||
|
||||
-- AlterTable drop blockId
|
||||
ALTER TABLE "CreditTransaction" DROP COLUMN "blockId";
|
||||
|
||||
COMMIT;
|
||||
|
||||
/*
|
||||
These indices dropped below were part of the cleanup during the schema change applied above.
|
||||
These indexes were not useful and will not impact anything upon their removal.
|
||||
*/
|
||||
|
||||
-- DropIndex
|
||||
DROP INDEX "StoreListingReview_storeListingVersionId_idx";
|
||||
|
||||
-- DropIndex
|
||||
DROP INDEX "StoreListingSubmission_Status_idx";
|
|
@ -3688,6 +3688,22 @@ docs = ["myst-parser[linkify]", "sphinx", "sphinx-rtd-theme"]
|
|||
release = ["twine"]
|
||||
test = ["pylint", "pytest", "pytest-black", "pytest-cov", "pytest-pylint"]
|
||||
|
||||
[[package]]
|
||||
name = "stripe"
|
||||
version = "11.4.1"
|
||||
description = "Python bindings for the Stripe API"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "stripe-11.4.1-py2.py3-none-any.whl", hash = "sha256:8aa47a241de0355c383c916c4ef7273ab666f096a44ee7081e357db4a36f0cce"},
|
||||
{file = "stripe-11.4.1.tar.gz", hash = "sha256:7ddd251b622d490fe57d78487855dc9f4d95b1bb113607e81fd377037a133d5a"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
requests = {version = ">=2.20", markers = "python_version >= \"3.0\""}
|
||||
typing-extensions = {version = ">=4.5.0", markers = "python_version >= \"3.7\""}
|
||||
|
||||
[[package]]
|
||||
name = "supabase"
|
||||
version = "2.11.0"
|
||||
|
@ -4432,4 +4448,4 @@ type = ["pytest-mypy"]
|
|||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = ">=3.10,<3.13"
|
||||
content-hash = "711669de9e6d5b81f19286bd41d52f57bc0177ba8ff5f2b477313a5b2d012ae5"
|
||||
content-hash = "341712d286b6a6fae89055bd21a55d8fa918973e446f6c0f0329a8493022cbae"
|
||||
|
|
|
@ -39,6 +39,7 @@ python-dotenv = "^1.0.1"
|
|||
redis = "^5.2.0"
|
||||
sentry-sdk = "2.19.2"
|
||||
strenum = "^0.4.9"
|
||||
stripe = "^11.3.0"
|
||||
supabase = "2.11.0"
|
||||
tenacity = "^9.0.0"
|
||||
tweepy = "^4.14.0"
|
||||
|
|
|
@ -32,12 +32,12 @@ model User {
|
|||
AgentPreset AgentPreset[]
|
||||
UserAgent UserAgent[]
|
||||
|
||||
Profile Profile[]
|
||||
StoreListing StoreListing[]
|
||||
StoreListingReview StoreListingReview[]
|
||||
StoreListingSubmission StoreListingSubmission[]
|
||||
APIKeys APIKey[]
|
||||
IntegrationWebhooks IntegrationWebhook[]
|
||||
Profile Profile[]
|
||||
StoreListing StoreListing[]
|
||||
StoreListingReview StoreListingReview[]
|
||||
StoreListingSubmission StoreListingSubmission[]
|
||||
APIKeys APIKey[]
|
||||
IntegrationWebhooks IntegrationWebhook[]
|
||||
|
||||
@@index([id])
|
||||
@@index([email])
|
||||
|
@ -64,23 +64,23 @@ model AgentGraph {
|
|||
AgentNodes AgentNode[]
|
||||
AgentGraphExecution AgentGraphExecution[]
|
||||
|
||||
AgentPreset AgentPreset[]
|
||||
UserAgent UserAgent[]
|
||||
StoreListing StoreListing[]
|
||||
StoreListingVersion StoreListingVersion?
|
||||
AgentPreset AgentPreset[]
|
||||
UserAgent UserAgent[]
|
||||
StoreListing StoreListing[]
|
||||
StoreListingVersion StoreListingVersion?
|
||||
|
||||
@@id(name: "graphVersionId", [id, version])
|
||||
@@index([userId, isActive])
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
//////////////// USER SPECIFIC DATA ////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
// An AgentPrest is an Agent + User Configuration of that agent.
|
||||
// For example, if someone has created a weather agent and they want to set it up to
|
||||
// For example, if someone has created a weather agent and they want to set it up to
|
||||
// Inform them of extreme weather warnings in Texas, the agent with the configuration to set it to
|
||||
// monitor texas, along with the cron setup or webhook tiggers, is an AgentPreset
|
||||
model AgentPreset {
|
||||
|
@ -102,9 +102,9 @@ model AgentPreset {
|
|||
agentVersion Int
|
||||
Agent AgentGraph @relation(fields: [agentId, agentVersion], references: [id, version], onDelete: Cascade)
|
||||
|
||||
InputPresets AgentNodeExecutionInputOutput[] @relation("AgentPresetsInputData")
|
||||
UserAgents UserAgent[]
|
||||
AgentExecution AgentGraphExecution[]
|
||||
InputPresets AgentNodeExecutionInputOutput[] @relation("AgentPresetsInputData")
|
||||
UserAgents UserAgent[]
|
||||
AgentExecution AgentGraphExecution[]
|
||||
|
||||
@@index([userId])
|
||||
}
|
||||
|
@ -134,11 +134,11 @@ model UserAgent {
|
|||
@@index([userId])
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
//////// AGENT DEFINITION AND EXECUTION TABLES ////////
|
||||
////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
// This model describes a single node in the Agent Graph/Flow (Multi Agent System).
|
||||
model AgentNode {
|
||||
|
@ -207,7 +207,6 @@ model AgentBlock {
|
|||
|
||||
// Prisma requires explicit back-references.
|
||||
ReferencedByAgentNode AgentNode[]
|
||||
CreditTransaction CreditTransaction[]
|
||||
}
|
||||
|
||||
// This model describes the status of an AgentGraphExecution or AgentNodeExecution.
|
||||
|
@ -345,11 +344,11 @@ model AnalyticsDetails {
|
|||
@@index([type])
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
////////////// METRICS TRACKING TABLES ////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
model AnalyticsMetrics {
|
||||
id String @id @default(uuid())
|
||||
createdAt DateTime @default(now())
|
||||
|
@ -375,11 +374,11 @@ enum CreditTransactionType {
|
|||
USAGE
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
//////// ACCOUNTING AND CREDIT SYSTEM TABLES //////////
|
||||
////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
model CreditTransaction {
|
||||
transactionKey String @default(uuid())
|
||||
createdAt DateTime @default(now())
|
||||
|
@ -387,12 +386,11 @@ model CreditTransaction {
|
|||
userId String
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
blockId String?
|
||||
block AgentBlock? @relation(fields: [blockId], references: [id])
|
||||
|
||||
amount Int
|
||||
type CreditTransactionType
|
||||
|
||||
runningBalance Int?
|
||||
|
||||
isActive Boolean @default(true)
|
||||
metadata Json?
|
||||
|
||||
|
@ -400,11 +398,11 @@ model CreditTransaction {
|
|||
@@index([userId, createdAt])
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
////////////// Store TABLES ///////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
model Profile {
|
||||
id String @id @default(uuid())
|
||||
|
@ -412,7 +410,7 @@ model Profile {
|
|||
updatedAt DateTime @default(now()) @updatedAt
|
||||
|
||||
// Only 1 of user or group can be set.
|
||||
// The user this profile belongs to, if any.
|
||||
// The user this profile belongs to, if any.
|
||||
userId String?
|
||||
User User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
|
@ -526,7 +524,7 @@ model StoreListingVersion {
|
|||
agentVersion Int
|
||||
Agent AgentGraph @relation(fields: [agentId, agentVersion], references: [id, version])
|
||||
|
||||
// The detials for this version of the agent, this allows the author to update the details of the agent,
|
||||
// The details for this version of the agent, this allows the author to update the details of the agent,
|
||||
// But still allow using old versions of the agent with there original details.
|
||||
// TODO: Create a database view that shows only the latest version of each store listing.
|
||||
slug String
|
||||
|
@ -571,7 +569,6 @@ model StoreListingReview {
|
|||
comments String?
|
||||
|
||||
@@unique([storeListingVersionId, reviewByUserId])
|
||||
@@index([storeListingVersionId])
|
||||
}
|
||||
|
||||
enum SubmissionStatus {
|
||||
|
@ -599,7 +596,6 @@ model StoreListingSubmission {
|
|||
reviewComments String?
|
||||
|
||||
@@index([storeListingId])
|
||||
@@index([Status])
|
||||
}
|
||||
|
||||
enum APIKeyPermission {
|
||||
|
|
|
@ -4,95 +4,101 @@ import pytest
|
|||
from prisma.models import CreditTransaction
|
||||
|
||||
from backend.blocks.llm import AITextGeneratorBlock
|
||||
from backend.data.credit import UserCredit
|
||||
from backend.data.credit import BetaUserCredit
|
||||
from backend.data.execution import NodeExecutionEntry
|
||||
from backend.data.user import DEFAULT_USER_ID
|
||||
from backend.integrations.credentials_store import openai_credentials
|
||||
from backend.util.test import SpinTestServer
|
||||
|
||||
REFILL_VALUE = 1000
|
||||
user_credit = UserCredit(REFILL_VALUE)
|
||||
user_credit = BetaUserCredit(REFILL_VALUE)
|
||||
|
||||
|
||||
async def disable_test_user_transactions():
|
||||
await CreditTransaction.prisma().delete_many(where={"userId": DEFAULT_USER_ID})
|
||||
|
||||
|
||||
@pytest.mark.asyncio(scope="session")
|
||||
async def test_block_credit_usage(server: SpinTestServer):
|
||||
current_credit = await user_credit.get_or_refill_credit(DEFAULT_USER_ID)
|
||||
await disable_test_user_transactions()
|
||||
await user_credit.top_up_credits(DEFAULT_USER_ID, 100)
|
||||
current_credit = await user_credit.get_credits(DEFAULT_USER_ID)
|
||||
|
||||
spending_amount_1 = await user_credit.spend_credits(
|
||||
DEFAULT_USER_ID,
|
||||
current_credit,
|
||||
AITextGeneratorBlock().id,
|
||||
{
|
||||
"model": "gpt-4-turbo",
|
||||
"credentials": {
|
||||
"id": openai_credentials.id,
|
||||
"provider": openai_credentials.provider,
|
||||
"type": openai_credentials.type,
|
||||
NodeExecutionEntry(
|
||||
user_id=DEFAULT_USER_ID,
|
||||
graph_id="test_graph",
|
||||
node_id="test_node",
|
||||
graph_exec_id="test_graph_exec",
|
||||
node_exec_id="test_node_exec",
|
||||
block_id=AITextGeneratorBlock().id,
|
||||
data={
|
||||
"model": "gpt-4-turbo",
|
||||
"credentials": {
|
||||
"id": openai_credentials.id,
|
||||
"provider": openai_credentials.provider,
|
||||
"type": openai_credentials.type,
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
0.0,
|
||||
0.0,
|
||||
validate_balance=False,
|
||||
)
|
||||
assert spending_amount_1 > 0
|
||||
|
||||
spending_amount_2 = await user_credit.spend_credits(
|
||||
DEFAULT_USER_ID,
|
||||
current_credit,
|
||||
AITextGeneratorBlock().id,
|
||||
{"model": "gpt-4-turbo", "api_key": "owned_api_key"},
|
||||
NodeExecutionEntry(
|
||||
user_id=DEFAULT_USER_ID,
|
||||
graph_id="test_graph",
|
||||
node_id="test_node",
|
||||
graph_exec_id="test_graph_exec",
|
||||
node_exec_id="test_node_exec",
|
||||
block_id=AITextGeneratorBlock().id,
|
||||
data={"model": "gpt-4-turbo", "api_key": "owned_api_key"},
|
||||
),
|
||||
0.0,
|
||||
0.0,
|
||||
validate_balance=False,
|
||||
)
|
||||
assert spending_amount_2 == 0
|
||||
|
||||
new_credit = await user_credit.get_or_refill_credit(DEFAULT_USER_ID)
|
||||
new_credit = await user_credit.get_credits(DEFAULT_USER_ID)
|
||||
assert new_credit == current_credit - spending_amount_1 - spending_amount_2
|
||||
|
||||
|
||||
@pytest.mark.asyncio(scope="session")
|
||||
async def test_block_credit_top_up(server: SpinTestServer):
|
||||
current_credit = await user_credit.get_or_refill_credit(DEFAULT_USER_ID)
|
||||
await disable_test_user_transactions()
|
||||
current_credit = await user_credit.get_credits(DEFAULT_USER_ID)
|
||||
|
||||
await user_credit.top_up_credits(DEFAULT_USER_ID, 100)
|
||||
|
||||
new_credit = await user_credit.get_or_refill_credit(DEFAULT_USER_ID)
|
||||
new_credit = await user_credit.get_credits(DEFAULT_USER_ID)
|
||||
assert new_credit == current_credit + 100
|
||||
|
||||
|
||||
@pytest.mark.asyncio(scope="session")
|
||||
async def test_block_credit_reset(server: SpinTestServer):
|
||||
month1 = datetime(2022, 1, 15)
|
||||
month2 = datetime(2022, 2, 15)
|
||||
await disable_test_user_transactions()
|
||||
month1 = 1
|
||||
month2 = 2
|
||||
|
||||
user_credit.time_now = lambda: month2
|
||||
month2credit = await user_credit.get_or_refill_credit(DEFAULT_USER_ID)
|
||||
# set the calendar to month 2 but use current time from now
|
||||
user_credit.time_now = lambda: datetime.now().replace(month=month2)
|
||||
month2credit = await user_credit.get_credits(DEFAULT_USER_ID)
|
||||
|
||||
# Month 1 result should only affect month 1
|
||||
user_credit.time_now = lambda: month1
|
||||
month1credit = await user_credit.get_or_refill_credit(DEFAULT_USER_ID)
|
||||
user_credit.time_now = lambda: datetime.now().replace(month=month1)
|
||||
month1credit = await user_credit.get_credits(DEFAULT_USER_ID)
|
||||
await user_credit.top_up_credits(DEFAULT_USER_ID, 100)
|
||||
assert await user_credit.get_or_refill_credit(DEFAULT_USER_ID) == month1credit + 100
|
||||
assert await user_credit.get_credits(DEFAULT_USER_ID) == month1credit + 100
|
||||
|
||||
# Month 2 balance is unaffected
|
||||
user_credit.time_now = lambda: month2
|
||||
assert await user_credit.get_or_refill_credit(DEFAULT_USER_ID) == month2credit
|
||||
user_credit.time_now = lambda: datetime.now().replace(month=month2)
|
||||
assert await user_credit.get_credits(DEFAULT_USER_ID) == month2credit
|
||||
|
||||
|
||||
@pytest.mark.asyncio(scope="session")
|
||||
async def test_credit_refill(server: SpinTestServer):
|
||||
# Clear all transactions within the month
|
||||
await CreditTransaction.prisma().update_many(
|
||||
where={
|
||||
"userId": DEFAULT_USER_ID,
|
||||
"createdAt": {
|
||||
"gte": datetime(2022, 2, 1),
|
||||
"lt": datetime(2022, 3, 1),
|
||||
},
|
||||
},
|
||||
data={"isActive": False},
|
||||
)
|
||||
user_credit.time_now = lambda: datetime(2022, 2, 15)
|
||||
|
||||
balance = await user_credit.get_or_refill_credit(DEFAULT_USER_ID)
|
||||
await disable_test_user_transactions()
|
||||
balance = await user_credit.get_credits(DEFAULT_USER_ID)
|
||||
assert balance == REFILL_VALUE
|
||||
|
|
|
@ -125,7 +125,7 @@ async def test_agent_execution(server: SpinTestServer):
|
|||
logger.info("Starting test_agent_execution")
|
||||
test_user = await create_test_user()
|
||||
test_graph = await create_graph(server, create_test_graph(), test_user)
|
||||
data = {"input_1": "Hello", "input_2": "World"}
|
||||
data = {"node_input": {"input_1": "Hello", "input_2": "World"}}
|
||||
graph_exec_id = await execute_graph(
|
||||
server.agent_server,
|
||||
test_graph,
|
||||
|
|
|
@ -298,7 +298,6 @@ async def main():
|
|||
data={
|
||||
"transactionKey": str(faker.uuid4()),
|
||||
"userId": user.id,
|
||||
"blockId": block.id,
|
||||
"amount": random.randint(1, 100),
|
||||
"type": (
|
||||
prisma.enums.CreditTransactionType.TOP_UP
|
||||
|
|
|
@ -5,6 +5,7 @@ NEXT_PUBLIC_AGPT_MARKETPLACE_URL=http://localhost:8015/api/v1/market
|
|||
NEXT_PUBLIC_LAUNCHDARKLY_ENABLED=false
|
||||
NEXT_PUBLIC_LAUNCHDARKLY_CLIENT_ID=
|
||||
NEXT_PUBLIC_APP_ENV=dev
|
||||
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=
|
||||
|
||||
## Locale settings
|
||||
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
"@radix-ui/react-toast": "^1.2.4",
|
||||
"@radix-ui/react-tooltip": "^1.1.6",
|
||||
"@sentry/nextjs": "^8",
|
||||
"@stripe/stripe-js": "^5.3.0",
|
||||
"@supabase/ssr": "^0.5.2",
|
||||
"@supabase/supabase-js": "^2.47.8",
|
||||
"@tanstack/react-table": "^8.20.6",
|
||||
|
@ -64,7 +65,7 @@
|
|||
"launchdarkly-react-client-sdk": "^3.6.0",
|
||||
"lucide-react": "^0.469.0",
|
||||
"moment": "^2.30.1",
|
||||
"next": "^14.2.13",
|
||||
"next": "^14.2.21",
|
||||
"next-themes": "^0.4.4",
|
||||
"react": "^18",
|
||||
"react-day-picker": "^9.5.0",
|
||||
|
@ -81,35 +82,35 @@
|
|||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@chromatic-com/storybook": "^3.2.3",
|
||||
"@chromatic-com/storybook": "^3.2.4",
|
||||
"@playwright/test": "^1.48.2",
|
||||
"@storybook/addon-a11y": "^8.3.5",
|
||||
"@storybook/addon-essentials": "^8.4.2",
|
||||
"@storybook/addon-interactions": "^8.4.2",
|
||||
"@storybook/addon-links": "^8.4.2",
|
||||
"@storybook/addon-onboarding": "^8.4.2",
|
||||
"@storybook/blocks": "^8.4.2",
|
||||
"@storybook/nextjs": "^8.4.2",
|
||||
"@storybook/addon-a11y": "^8.5.0",
|
||||
"@storybook/addon-essentials": "^8.5.0",
|
||||
"@storybook/addon-interactions": "^8.5.0",
|
||||
"@storybook/addon-links": "^8.5.0",
|
||||
"@storybook/addon-onboarding": "^8.5.0",
|
||||
"@storybook/blocks": "^8.5.0",
|
||||
"@storybook/nextjs": "^8.5.0",
|
||||
"@storybook/react": "^8.3.5",
|
||||
"@storybook/test": "^8.3.5",
|
||||
"@storybook/test-runner": "^0.21.0",
|
||||
"@types/negotiator": "^0.6.3",
|
||||
"@types/node": "^22.10.5",
|
||||
"@types/node": "^22.10.7",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"@types/react-modal": "^3.16.3",
|
||||
"axe-playwright": "^2.0.3",
|
||||
"chromatic": "^11.22.0",
|
||||
"chromatic": "^11.25.0",
|
||||
"concurrently": "^9.1.2",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "15.1.3",
|
||||
"eslint-config-next": "15.1.5",
|
||||
"eslint-plugin-storybook": "^0.11.2",
|
||||
"msw": "^2.7.0",
|
||||
"msw-storybook-addon": "^2.0.3",
|
||||
"postcss": "^8",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier-plugin-tailwindcss": "^0.6.9",
|
||||
"storybook": "^8.4.5",
|
||||
"prettier-plugin-tailwindcss": "^0.6.10",
|
||||
"storybook": "^8.5.0",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "^5"
|
||||
},
|
||||
|
|
|
@ -49,7 +49,7 @@ export default async function RootLayout({
|
|||
links={[
|
||||
{
|
||||
name: "Marketplace",
|
||||
href: "/store",
|
||||
href: "/marketplace",
|
||||
},
|
||||
{
|
||||
name: "Library",
|
||||
|
@ -66,7 +66,7 @@ export default async function RootLayout({
|
|||
{
|
||||
icon: IconType.Edit,
|
||||
text: "Edit profile",
|
||||
href: "/store/profile",
|
||||
href: "/marketplace/profile",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -75,7 +75,7 @@ export default async function RootLayout({
|
|||
{
|
||||
icon: IconType.LayoutDashboard,
|
||||
text: "Creator Dashboard",
|
||||
href: "/store/dashboard",
|
||||
href: "/marketplace/dashboard",
|
||||
},
|
||||
{
|
||||
icon: IconType.UploadCloud,
|
||||
|
@ -88,7 +88,7 @@ export default async function RootLayout({
|
|||
{
|
||||
icon: IconType.Settings,
|
||||
text: "Settings",
|
||||
href: "/store/settings",
|
||||
href: "/marketplace/settings",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
"use client";
|
||||
import { Button } from "@/components/agptui/Button";
|
||||
import useCredits from "@/hooks/useCredits";
|
||||
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
||||
import { useSearchParams, useRouter } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export default function CreditsPage() {
|
||||
const { requestTopUp } = useCredits();
|
||||
const [amount, setAmount] = useState(5);
|
||||
const [patched, setPatched] = useState(false);
|
||||
const searchParams = useSearchParams();
|
||||
const router = useRouter();
|
||||
const topupStatus = searchParams.get("topup");
|
||||
const api = useBackendAPI();
|
||||
|
||||
useEffect(() => {
|
||||
if (!patched && topupStatus === "success") {
|
||||
api.fulfillCheckout();
|
||||
setPatched(true);
|
||||
}
|
||||
}, [api, patched, topupStatus]);
|
||||
|
||||
const openBillingPortal = async () => {
|
||||
const portal = await api.getUserPaymentPortalLink();
|
||||
router.push(portal.url);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full min-w-[800px] px-4 sm:px-8">
|
||||
<h1 className="font-circular mb-6 text-[28px] font-normal text-neutral-900 dark:text-neutral-100 sm:mb-8 sm:text-[35px]">
|
||||
Credits
|
||||
</h1>
|
||||
|
||||
<div className="grid grid-cols-1 gap-8 lg:grid-cols-2">
|
||||
{/* Left Column */}
|
||||
<div>
|
||||
<h2 className="text-lg">Top-up Credits</h2>
|
||||
|
||||
<p className="mb-6 text-neutral-600 dark:text-neutral-400">
|
||||
{topupStatus === "success" && (
|
||||
<span className="text-green-500">
|
||||
Your payment was successful. Your credits will be updated
|
||||
shortly.
|
||||
</span>
|
||||
)}
|
||||
{topupStatus === "cancel" && (
|
||||
<span className="text-red-500">
|
||||
Payment failed. Your payment method has not been charged.
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
|
||||
<div className="mb-4 w-full">
|
||||
<label className="text-neutral-700">
|
||||
1 USD = 100 credits, 5 USD is a minimum top-up
|
||||
</label>
|
||||
<div className="rounded-[55px] border border-slate-200 px-4 py-2.5 dark:border-slate-700 dark:bg-slate-800">
|
||||
<input
|
||||
type="number"
|
||||
name="displayName"
|
||||
value={amount}
|
||||
placeholder="Top-up amount in USD"
|
||||
min="5"
|
||||
step="1"
|
||||
className="w-full"
|
||||
onChange={(e) => setAmount(parseInt(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
variant="default"
|
||||
className="font-circular ml-auto"
|
||||
onClick={() => requestTopUp(amount)}
|
||||
>
|
||||
Top-up
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Right Column */}
|
||||
<div>
|
||||
<h2 className="text-lg">Manage Your Payment Methods</h2>
|
||||
<br />
|
||||
<p className="text-neutral-600">
|
||||
You can manage your cards and see your payment history in the
|
||||
billing portal.
|
||||
</p>
|
||||
<br />
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
variant="default"
|
||||
className="font-circular ml-auto"
|
||||
onClick={() => openBillingPortal()}
|
||||
>
|
||||
Open Portal
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -33,14 +33,14 @@ export default function Page({}: {}) {
|
|||
} catch (error) {
|
||||
console.error("Error fetching submissions:", error);
|
||||
}
|
||||
}, [api, supabase]);
|
||||
}, [api]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!supabase) {
|
||||
return;
|
||||
}
|
||||
fetchData();
|
||||
}, [supabase]);
|
||||
}, [supabase, fetchData]);
|
||||
|
||||
const onEditSubmission = useCallback((submission: StoreSubmissionRequest) => {
|
||||
setSubmissionData(submission);
|
||||
|
@ -56,7 +56,7 @@ export default function Page({}: {}) {
|
|||
api.deleteStoreSubmission(submission_id);
|
||||
fetchData();
|
||||
},
|
||||
[supabase],
|
||||
[api, supabase, fetchData],
|
||||
);
|
||||
|
||||
const onOpenPopout = useCallback(() => {
|
|
@ -98,6 +98,7 @@ export default function PrivatePage() {
|
|||
// This contains ids for built-in "Use Credits for X" credentials
|
||||
const hiddenCredentials = useMemo(
|
||||
() => [
|
||||
"744fdc56-071a-4761-b5a5-0af0ce10a2b5", // Ollama
|
||||
"fdb7f412-f519-48d1-9b5f-d2f73d0e01fe", // Revid
|
||||
"760f84fc-b270-42de-91f6-08efe1b512d0", // Ideogram
|
||||
"6b9fc200-4726-4973-86c9-cd526f5ce5db", // Replicate
|
|
@ -5,12 +5,13 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
|||
const sidebarLinkGroups = [
|
||||
{
|
||||
links: [
|
||||
{ text: "Creator Dashboard", href: "/store/dashboard" },
|
||||
{ text: "Agent dashboard", href: "/store/agent-dashboard" },
|
||||
{ text: "Integrations", href: "/store/integrations" },
|
||||
{ text: "API Keys", href: "/store/api_keys" },
|
||||
{ text: "Profile", href: "/store/profile" },
|
||||
{ text: "Settings", href: "/store/settings" },
|
||||
{ text: "Creator Dashboard", href: "/marketplace/dashboard" },
|
||||
{ text: "Agent dashboard", href: "/marketplace/agent-dashboard" },
|
||||
{ text: "Credits", href: "/marketplace/credits" },
|
||||
{ text: "Integrations", href: "/marketplace/integrations" },
|
||||
{ text: "API Keys", href: "/marketplace/api_keys" },
|
||||
{ text: "Profile", href: "/marketplace/profile" },
|
||||
{ text: "Settings", href: "/marketplace/settings" },
|
||||
],
|
||||
},
|
||||
];
|
|
@ -45,10 +45,10 @@ export default async function Page({
|
|||
});
|
||||
|
||||
const breadcrumbs = [
|
||||
{ name: "Store", link: "/store" },
|
||||
{ name: "Store", link: "/marketplace" },
|
||||
{
|
||||
name: agent.creator,
|
||||
link: `/store/creator/${encodeURIComponent(agent.creator)}`,
|
||||
link: `/marketplace/creator/${encodeURIComponent(agent.creator)}`,
|
||||
},
|
||||
{ name: agent.agent_name, link: "#" },
|
||||
];
|
|
@ -47,7 +47,7 @@ export default async function Page({
|
|||
<main className="mt-5 px-4">
|
||||
<BreadCrumbs
|
||||
items={[
|
||||
{ name: "Store", link: "/store" },
|
||||
{ name: "Store", link: "/marketplace" },
|
||||
{ name: creator.name, link: "#" },
|
||||
]}
|
||||
/>
|
|
@ -1,7 +1,179 @@
|
|||
"use client";
|
||||
import * as React from "react";
|
||||
import { HeroSection } from "@/components/agptui/composite/HeroSection";
|
||||
import {
|
||||
FeaturedSection,
|
||||
FeaturedAgent,
|
||||
} from "@/components/agptui/composite/FeaturedSection";
|
||||
import {
|
||||
AgentsSection,
|
||||
Agent,
|
||||
} from "@/components/agptui/composite/AgentsSection";
|
||||
import { BecomeACreator } from "@/components/agptui/BecomeACreator";
|
||||
import {
|
||||
FeaturedCreators,
|
||||
FeaturedCreator,
|
||||
} from "@/components/agptui/composite/FeaturedCreators";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Metadata } from "next";
|
||||
import {
|
||||
StoreAgentsResponse,
|
||||
CreatorsResponse,
|
||||
} from "@/lib/autogpt-server-api/types";
|
||||
import BackendAPI from "@/lib/autogpt-server-api";
|
||||
|
||||
import { redirect } from "next/navigation";
|
||||
async function getStoreData() {
|
||||
try {
|
||||
const api = new BackendAPI();
|
||||
|
||||
export default function Page() {
|
||||
redirect("/store");
|
||||
// Add error handling and default values
|
||||
let featuredAgents: StoreAgentsResponse = {
|
||||
agents: [],
|
||||
pagination: {
|
||||
total_items: 0,
|
||||
total_pages: 0,
|
||||
current_page: 0,
|
||||
page_size: 0,
|
||||
},
|
||||
};
|
||||
let topAgents: StoreAgentsResponse = {
|
||||
agents: [],
|
||||
pagination: {
|
||||
total_items: 0,
|
||||
total_pages: 0,
|
||||
current_page: 0,
|
||||
page_size: 0,
|
||||
},
|
||||
};
|
||||
let featuredCreators: CreatorsResponse = {
|
||||
creators: [],
|
||||
pagination: {
|
||||
total_items: 0,
|
||||
total_pages: 0,
|
||||
current_page: 0,
|
||||
page_size: 0,
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
[featuredAgents, topAgents, featuredCreators] = await Promise.all([
|
||||
api.getStoreAgents({ featured: true }),
|
||||
api.getStoreAgents({ sorted_by: "runs" }),
|
||||
api.getStoreCreators({ featured: true, sorted_by: "num_agents" }),
|
||||
]);
|
||||
} catch (error) {
|
||||
console.error("Error fetching store data:", error);
|
||||
}
|
||||
|
||||
return {
|
||||
featuredAgents,
|
||||
topAgents,
|
||||
featuredCreators,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error in getStoreData:", error);
|
||||
return {
|
||||
featuredAgents: {
|
||||
agents: [],
|
||||
pagination: {
|
||||
total_items: 0,
|
||||
total_pages: 0,
|
||||
current_page: 0,
|
||||
page_size: 0,
|
||||
},
|
||||
},
|
||||
topAgents: {
|
||||
agents: [],
|
||||
pagination: {
|
||||
total_items: 0,
|
||||
total_pages: 0,
|
||||
current_page: 0,
|
||||
page_size: 0,
|
||||
},
|
||||
},
|
||||
featuredCreators: {
|
||||
creators: [],
|
||||
pagination: {
|
||||
total_items: 0,
|
||||
total_pages: 0,
|
||||
current_page: 0,
|
||||
page_size: 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// FIX: Correct metadata
|
||||
export const metadata: Metadata = {
|
||||
title: "Marketplace - NextGen AutoGPT",
|
||||
description: "Find and use AI Agents created by our community",
|
||||
applicationName: "NextGen AutoGPT Store",
|
||||
authors: [{ name: "AutoGPT Team" }],
|
||||
keywords: [
|
||||
"AI agents",
|
||||
"automation",
|
||||
"artificial intelligence",
|
||||
"AutoGPT",
|
||||
"marketplace",
|
||||
],
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
},
|
||||
openGraph: {
|
||||
title: "Marketplace - NextGen AutoGPT",
|
||||
description: "Find and use AI Agents created by our community",
|
||||
type: "website",
|
||||
siteName: "NextGen AutoGPT Store",
|
||||
images: [
|
||||
{
|
||||
url: "/images/store-og.png",
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: "NextGen AutoGPT Store",
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: "Marketplace - NextGen AutoGPT",
|
||||
description: "Find and use AI Agents created by our community",
|
||||
images: ["/images/store-twitter.png"],
|
||||
},
|
||||
icons: {
|
||||
icon: "/favicon.ico",
|
||||
shortcut: "/favicon-16x16.png",
|
||||
apple: "/apple-touch-icon.png",
|
||||
},
|
||||
};
|
||||
|
||||
export default async function Page({}: {}) {
|
||||
// Get data server-side
|
||||
const { featuredAgents, topAgents, featuredCreators } = await getStoreData();
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-screen max-w-[1360px]">
|
||||
<main className="px-4">
|
||||
<HeroSection />
|
||||
<FeaturedSection
|
||||
featuredAgents={featuredAgents.agents as FeaturedAgent[]}
|
||||
/>
|
||||
<Separator />
|
||||
<AgentsSection
|
||||
sectionTitle="Top Agents"
|
||||
agents={topAgents.agents as Agent[]}
|
||||
/>
|
||||
<Separator />
|
||||
<FeaturedCreators
|
||||
featuredCreators={featuredCreators.creators as FeaturedCreator[]}
|
||||
/>
|
||||
<Separator />
|
||||
<BecomeACreator
|
||||
title="Become a Creator"
|
||||
description="Join our ever-growing community of hackers and tinkerers"
|
||||
buttonText="Become a Creator"
|
||||
/>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ function SearchResults({
|
|||
};
|
||||
|
||||
fetchData();
|
||||
}, [searchTerm, sort]);
|
||||
}, [api, searchTerm, sort]);
|
||||
|
||||
const agentsCount = agents.length;
|
||||
const creatorsCount = creators.length;
|
|
@ -3,5 +3,5 @@
|
|||
import { redirect } from "next/navigation";
|
||||
|
||||
export default function Page() {
|
||||
redirect("/store");
|
||||
redirect("/marketplace");
|
||||
}
|
||||
|
|
|
@ -98,6 +98,7 @@ export default function PrivatePage() {
|
|||
// This contains ids for built-in "Use Credits for X" credentials
|
||||
const hiddenCredentials = useMemo(
|
||||
() => [
|
||||
"744fdc56-071a-4761-b5a5-0af0ce10a2b5", // Ollama
|
||||
"fdb7f412-f519-48d1-9b5f-d2f73d0e01fe", // Revid
|
||||
"760f84fc-b270-42de-91f6-08efe1b512d0", // Ideogram
|
||||
"6b9fc200-4726-4973-86c9-cd526f5ce5db", // Replicate
|
||||
|
|
|
@ -38,7 +38,7 @@ export async function signup(values: z.infer<typeof signupFormSchema>) {
|
|||
}
|
||||
console.log("Signed up");
|
||||
revalidatePath("/", "layout");
|
||||
redirect("/store/profile");
|
||||
redirect("/marketplace/profile");
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,181 +0,0 @@
|
|||
import * as React from "react";
|
||||
import { HeroSection } from "@/components/agptui/composite/HeroSection";
|
||||
import {
|
||||
FeaturedSection,
|
||||
FeaturedAgent,
|
||||
} from "@/components/agptui/composite/FeaturedSection";
|
||||
import {
|
||||
AgentsSection,
|
||||
Agent,
|
||||
} from "@/components/agptui/composite/AgentsSection";
|
||||
import { BecomeACreator } from "@/components/agptui/BecomeACreator";
|
||||
import {
|
||||
FeaturedCreators,
|
||||
FeaturedCreator,
|
||||
} from "@/components/agptui/composite/FeaturedCreators";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Metadata } from "next";
|
||||
import {
|
||||
StoreAgentsResponse,
|
||||
CreatorsResponse,
|
||||
} from "@/lib/autogpt-server-api/types";
|
||||
import BackendAPI from "@/lib/autogpt-server-api";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
async function getStoreData() {
|
||||
try {
|
||||
const api = new BackendAPI();
|
||||
|
||||
// Add error handling and default values
|
||||
let featuredAgents: StoreAgentsResponse = {
|
||||
agents: [],
|
||||
pagination: {
|
||||
total_items: 0,
|
||||
total_pages: 0,
|
||||
current_page: 0,
|
||||
page_size: 0,
|
||||
},
|
||||
};
|
||||
let topAgents: StoreAgentsResponse = {
|
||||
agents: [],
|
||||
pagination: {
|
||||
total_items: 0,
|
||||
total_pages: 0,
|
||||
current_page: 0,
|
||||
page_size: 0,
|
||||
},
|
||||
};
|
||||
let featuredCreators: CreatorsResponse = {
|
||||
creators: [],
|
||||
pagination: {
|
||||
total_items: 0,
|
||||
total_pages: 0,
|
||||
current_page: 0,
|
||||
page_size: 0,
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
[featuredAgents, topAgents, featuredCreators] = await Promise.all([
|
||||
api.getStoreAgents({ featured: true }),
|
||||
api.getStoreAgents({ sorted_by: "runs" }),
|
||||
api.getStoreCreators({ featured: true, sorted_by: "num_agents" }),
|
||||
]);
|
||||
} catch (error) {
|
||||
console.error("Error fetching store data:", error);
|
||||
}
|
||||
|
||||
return {
|
||||
featuredAgents,
|
||||
topAgents,
|
||||
featuredCreators,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error in getStoreData:", error);
|
||||
return {
|
||||
featuredAgents: {
|
||||
agents: [],
|
||||
pagination: {
|
||||
total_items: 0,
|
||||
total_pages: 0,
|
||||
current_page: 0,
|
||||
page_size: 0,
|
||||
},
|
||||
},
|
||||
topAgents: {
|
||||
agents: [],
|
||||
pagination: {
|
||||
total_items: 0,
|
||||
total_pages: 0,
|
||||
current_page: 0,
|
||||
page_size: 0,
|
||||
},
|
||||
},
|
||||
featuredCreators: {
|
||||
creators: [],
|
||||
pagination: {
|
||||
total_items: 0,
|
||||
total_pages: 0,
|
||||
current_page: 0,
|
||||
page_size: 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// FIX: Correct metadata
|
||||
export const metadata: Metadata = {
|
||||
title: "Marketplace - NextGen AutoGPT",
|
||||
description: "Find and use AI Agents created by our community",
|
||||
applicationName: "NextGen AutoGPT Store",
|
||||
authors: [{ name: "AutoGPT Team" }],
|
||||
keywords: [
|
||||
"AI agents",
|
||||
"automation",
|
||||
"artificial intelligence",
|
||||
"AutoGPT",
|
||||
"marketplace",
|
||||
],
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
},
|
||||
openGraph: {
|
||||
title: "Marketplace - NextGen AutoGPT",
|
||||
description: "Find and use AI Agents created by our community",
|
||||
type: "website",
|
||||
siteName: "NextGen AutoGPT Store",
|
||||
images: [
|
||||
{
|
||||
url: "/images/store-og.png",
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: "NextGen AutoGPT Store",
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: "Marketplace - NextGen AutoGPT",
|
||||
description: "Find and use AI Agents created by our community",
|
||||
images: ["/images/store-twitter.png"],
|
||||
},
|
||||
icons: {
|
||||
icon: "/favicon.ico",
|
||||
shortcut: "/favicon-16x16.png",
|
||||
apple: "/apple-touch-icon.png",
|
||||
},
|
||||
};
|
||||
|
||||
export default async function Page({}: {}) {
|
||||
// Get data server-side
|
||||
const { featuredAgents, topAgents, featuredCreators } = await getStoreData();
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-screen max-w-[1360px]">
|
||||
<main className="px-4">
|
||||
<HeroSection />
|
||||
<FeaturedSection
|
||||
featuredAgents={featuredAgents.agents as FeaturedAgent[]}
|
||||
/>
|
||||
<Separator />
|
||||
<AgentsSection
|
||||
sectionTitle="Top Agents"
|
||||
agents={topAgents.agents as Agent[]}
|
||||
/>
|
||||
<Separator />
|
||||
<FeaturedCreators
|
||||
featuredCreators={featuredCreators.creators as FeaturedCreator[]}
|
||||
/>
|
||||
<Separator />
|
||||
<BecomeACreator
|
||||
title="Become a Creator"
|
||||
description="Join our ever-growing community of hackers and tinkerers"
|
||||
buttonText="Become a Creator"
|
||||
/>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -105,7 +105,7 @@ export const AgentInfo: React.FC<AgentInfoProps> = ({
|
|||
by
|
||||
</div>
|
||||
<Link
|
||||
href={`/store/creator/${encodeURIComponent(creator)}`}
|
||||
href={`/marketplace/creator/${encodeURIComponent(creator)}`}
|
||||
className="font-geist text-base font-medium text-neutral-800 hover:underline dark:text-neutral-200 sm:text-lg lg:text-xl"
|
||||
>
|
||||
{creator}
|
||||
|
|
|
@ -28,7 +28,7 @@ export const NavbarLink = ({ name, href }: NavbarLinkProps) => {
|
|||
: ""
|
||||
} flex items-center justify-start gap-3`}
|
||||
>
|
||||
{href === "/store" && (
|
||||
{href === "/marketplace" && (
|
||||
<IconShoppingCart
|
||||
className={`h-6 w-6 ${activeLink === href ? "text-white dark:text-black" : ""}`}
|
||||
/>
|
||||
|
|
|
@ -36,7 +36,7 @@ export const SearchBar: React.FC<SearchBarProps> = ({
|
|||
if (searchQuery.trim()) {
|
||||
// Encode the search term and navigate to the desired path
|
||||
const encodedTerm = encodeURIComponent(searchQuery);
|
||||
router.push(`/store/search?searchTerm=${encodedTerm}`);
|
||||
router.push(`/marketplace/search?searchTerm=${encodedTerm}`);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
IconIntegrations,
|
||||
IconProfile,
|
||||
IconSliders,
|
||||
IconCoin,
|
||||
} from "../ui/icons";
|
||||
|
||||
interface SidebarLinkGroup {
|
||||
|
@ -22,6 +23,10 @@ interface SidebarProps {
|
|||
}
|
||||
|
||||
export const Sidebar: React.FC<SidebarProps> = ({ linkGroups }) => {
|
||||
const stripeAvailable = Boolean(
|
||||
process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY,
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Sheet>
|
||||
|
@ -41,7 +46,7 @@ export const Sidebar: React.FC<SidebarProps> = ({ linkGroups }) => {
|
|||
<div className="h-full w-full rounded-2xl bg-zinc-200 dark:bg-zinc-800">
|
||||
<div className="inline-flex h-[264px] flex-col items-start justify-start gap-6 p-3">
|
||||
<Link
|
||||
href="/store/dashboard"
|
||||
href="/marketplace/dashboard"
|
||||
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
||||
>
|
||||
<IconDashboardLayout className="h-6 w-6" />
|
||||
|
@ -49,8 +54,19 @@ export const Sidebar: React.FC<SidebarProps> = ({ linkGroups }) => {
|
|||
Creator dashboard
|
||||
</div>
|
||||
</Link>
|
||||
{stripeAvailable && (
|
||||
<Link
|
||||
href="/marketplace/credits"
|
||||
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
||||
>
|
||||
<IconCoin className="h-6 w-6" />
|
||||
<div className="p-ui-medium text-base font-medium leading-normal">
|
||||
Credits
|
||||
</div>
|
||||
</Link>
|
||||
)}
|
||||
<Link
|
||||
href="/store/integrations"
|
||||
href="/marketplace/integrations"
|
||||
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
||||
>
|
||||
<IconIntegrations className="h-6 w-6" />
|
||||
|
@ -59,7 +75,7 @@ export const Sidebar: React.FC<SidebarProps> = ({ linkGroups }) => {
|
|||
</div>
|
||||
</Link>
|
||||
<Link
|
||||
href="/store/api_keys"
|
||||
href="/marketplace/api_keys"
|
||||
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
||||
>
|
||||
<KeyIcon className="h-6 w-6" />
|
||||
|
@ -68,7 +84,7 @@ export const Sidebar: React.FC<SidebarProps> = ({ linkGroups }) => {
|
|||
</div>
|
||||
</Link>
|
||||
<Link
|
||||
href="/store/profile"
|
||||
href="/marketplace/profile"
|
||||
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
||||
>
|
||||
<IconProfile className="h-6 w-6" />
|
||||
|
@ -77,7 +93,7 @@ export const Sidebar: React.FC<SidebarProps> = ({ linkGroups }) => {
|
|||
</div>
|
||||
</Link>
|
||||
<Link
|
||||
href="/store/settings"
|
||||
href="/marketplace/settings"
|
||||
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
||||
>
|
||||
<IconSliders className="h-6 w-6" />
|
||||
|
@ -94,7 +110,7 @@ export const Sidebar: React.FC<SidebarProps> = ({ linkGroups }) => {
|
|||
<div className="h-full w-full rounded-2xl bg-zinc-200 dark:bg-zinc-800">
|
||||
<div className="inline-flex h-[264px] flex-col items-start justify-start gap-6 p-3">
|
||||
<Link
|
||||
href="/store/dashboard"
|
||||
href="/marketplace/dashboard"
|
||||
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
||||
>
|
||||
<IconDashboardLayout className="h-6 w-6" />
|
||||
|
@ -102,8 +118,19 @@ export const Sidebar: React.FC<SidebarProps> = ({ linkGroups }) => {
|
|||
Agent dashboard
|
||||
</div>
|
||||
</Link>
|
||||
{stripeAvailable && (
|
||||
<Link
|
||||
href="/marketplace/credits"
|
||||
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
||||
>
|
||||
<IconCoin className="h-6 w-6" />
|
||||
<div className="p-ui-medium text-base font-medium leading-normal">
|
||||
Credits
|
||||
</div>
|
||||
</Link>
|
||||
)}
|
||||
<Link
|
||||
href="/store/integrations"
|
||||
href="/marketplace/integrations"
|
||||
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
||||
>
|
||||
<IconIntegrations className="h-6 w-6" />
|
||||
|
@ -112,7 +139,7 @@ export const Sidebar: React.FC<SidebarProps> = ({ linkGroups }) => {
|
|||
</div>
|
||||
</Link>
|
||||
<Link
|
||||
href="/store/api_keys"
|
||||
href="/marketplace/api_keys"
|
||||
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
||||
>
|
||||
<KeyIcon className="h-6 w-6" strokeWidth={1} />
|
||||
|
@ -121,7 +148,7 @@ export const Sidebar: React.FC<SidebarProps> = ({ linkGroups }) => {
|
|||
</div>
|
||||
</Link>
|
||||
<Link
|
||||
href="/store/profile"
|
||||
href="/marketplace/profile"
|
||||
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
||||
>
|
||||
<IconProfile className="h-6 w-6" />
|
||||
|
@ -130,7 +157,7 @@ export const Sidebar: React.FC<SidebarProps> = ({ linkGroups }) => {
|
|||
</div>
|
||||
</Link>
|
||||
<Link
|
||||
href="/store/settings"
|
||||
href="/marketplace/settings"
|
||||
className="inline-flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-neutral-800 hover:bg-neutral-800 hover:text-white dark:text-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-white"
|
||||
>
|
||||
<IconSliders className="h-6 w-6" />
|
||||
|
|
|
@ -39,7 +39,7 @@ export const AgentsSection: React.FC<AgentsSectionProps> = ({
|
|||
|
||||
const handleCardClick = (creator: string, slug: string) => {
|
||||
router.push(
|
||||
`/store/agent/${encodeURIComponent(creator)}/${encodeURIComponent(slug)}`,
|
||||
`/marketplace/agent/${encodeURIComponent(creator)}/${encodeURIComponent(slug)}`,
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ export const FeaturedCreators: React.FC<FeaturedCreatorsProps> = ({
|
|||
const router = useRouter();
|
||||
|
||||
const handleCardClick = (creator: string) => {
|
||||
router.push(`/store/creator/${encodeURIComponent(creator)}`);
|
||||
router.push(`/marketplace/creator/${encodeURIComponent(creator)}`);
|
||||
};
|
||||
|
||||
// Only show first 4 creators
|
||||
|
|
|
@ -43,7 +43,7 @@ export const FeaturedSection: React.FC<FeaturedSectionProps> = ({
|
|||
|
||||
const handleCardClick = (creator: string, slug: string) => {
|
||||
router.push(
|
||||
`/store/agent/${encodeURIComponent(creator)}/${encodeURIComponent(slug)}`,
|
||||
`/marketplace/agent/${encodeURIComponent(creator)}/${encodeURIComponent(slug)}`,
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ export const HeroSection: React.FC = () => {
|
|||
|
||||
function onFilterChange(selectedFilters: string[]) {
|
||||
const encodedTerm = encodeURIComponent(selectedFilters.join(", "));
|
||||
router.push(`/store/search?searchTerm=${encodedTerm}`);
|
||||
router.push(`/marketplace/search?searchTerm=${encodedTerm}`);
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -260,7 +260,7 @@ export const PublishAgentPopout: React.FC<PublishAgentPopoutProps> = ({
|
|||
onClose={handleClose}
|
||||
onDone={handleClose}
|
||||
onViewProgress={() => {
|
||||
router.push("/store/dashboard");
|
||||
router.push("/marketplace/dashboard");
|
||||
handleClose();
|
||||
}}
|
||||
/>
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
FaGoogle,
|
||||
FaMedium,
|
||||
FaKey,
|
||||
FaHubspot,
|
||||
} from "react-icons/fa";
|
||||
import { FC, useMemo, useState } from "react";
|
||||
import {
|
||||
|
@ -66,6 +67,7 @@ export const providerIcons: Record<
|
|||
google_maps: FaGoogle,
|
||||
jina: fallbackIcon,
|
||||
ideogram: fallbackIcon,
|
||||
linear: fallbackIcon,
|
||||
medium: FaMedium,
|
||||
ollama: fallbackIcon,
|
||||
openai: fallbackIcon,
|
||||
|
@ -79,7 +81,7 @@ export const providerIcons: Record<
|
|||
twitter: FaTwitter,
|
||||
unreal_speech: fallbackIcon,
|
||||
exa: fallbackIcon,
|
||||
hubspot: fallbackIcon,
|
||||
hubspot: FaHubspot,
|
||||
};
|
||||
// --8<-- [end:ProviderIconsEmbed]
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ const providerDisplayNames: Record<CredentialsProviderName, string> = {
|
|||
groq: "Groq",
|
||||
ideogram: "Ideogram",
|
||||
jina: "Jina",
|
||||
linear: "Linear",
|
||||
medium: "Medium",
|
||||
notion: "Notion",
|
||||
nvidia: "Nvidia",
|
||||
|
|
|
@ -1,37 +1,21 @@
|
|||
"use client";
|
||||
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { IconRefresh } from "@/components/ui/icons";
|
||||
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
||||
import useCredits from "@/hooks/useCredits";
|
||||
|
||||
export default function CreditButton() {
|
||||
const [credit, setCredit] = useState<number | null>(null);
|
||||
const api = useBackendAPI();
|
||||
|
||||
const fetchCredit = useCallback(async () => {
|
||||
try {
|
||||
const response = await api.getUserCredit();
|
||||
setCredit(response.credits);
|
||||
} catch (error) {
|
||||
console.error("Error fetching credit:", error);
|
||||
setCredit(null);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
fetchCredit();
|
||||
}, [fetchCredit]);
|
||||
const { credits, fetchCredits } = useCredits();
|
||||
|
||||
return (
|
||||
credit !== null && (
|
||||
credits !== null && (
|
||||
<Button
|
||||
onClick={fetchCredit}
|
||||
onClick={fetchCredits}
|
||||
variant="outline"
|
||||
className="flex items-center space-x-2 rounded-xl bg-gray-200"
|
||||
>
|
||||
<span className="mr-2 flex items-center text-foreground">
|
||||
{credit} <span className="ml-2 text-muted-foreground"> credits</span>
|
||||
{credits} <span className="ml-2 text-muted-foreground"> credits</span>
|
||||
</span>
|
||||
<IconRefresh />
|
||||
</Button>
|
||||
|
|
|
@ -24,7 +24,7 @@ export function NavBarButtons({ className }: { className?: string }) {
|
|||
icon: <BsBoxes />,
|
||||
},
|
||||
{
|
||||
href: "/store",
|
||||
href: "/marketplace",
|
||||
text: "Marketplace",
|
||||
icon: <IconMarketplace />,
|
||||
},
|
||||
|
|
|
@ -313,8 +313,6 @@ export const NodeGenericInputField: FC<{
|
|||
);
|
||||
}
|
||||
|
||||
console.log("propSchema", propSchema);
|
||||
|
||||
if ("properties" in propSchema) {
|
||||
// Render a multi-select for all-boolean sub-schemas with more than 3 properties
|
||||
if (
|
||||
|
|
|
@ -323,7 +323,7 @@ export const IconCoin = createIcon((props) => (
|
|||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeWidth="1.25"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
aria-label="Coin Icon"
|
||||
|
|
|
@ -862,6 +862,7 @@ export default function useAgentGraph(
|
|||
title: "Error saving agent",
|
||||
description: errorMessage,
|
||||
});
|
||||
setSaveRunRequest({ request: "save", state: "error" });
|
||||
}
|
||||
}, [_saveAgent, toast]);
|
||||
|
||||
|
@ -874,7 +875,7 @@ export default function useAgentGraph(
|
|||
request: "save",
|
||||
state: "saving",
|
||||
});
|
||||
}, [saveAgent]);
|
||||
}, [saveAgent, saveRunRequest.state]);
|
||||
|
||||
const requestSaveAndRun = useCallback(() => {
|
||||
saveAgent();
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
import AutoGPTServerAPI from "@/lib/autogpt-server-api";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { loadStripe } from "@stripe/stripe-js";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
const stripePromise = loadStripe(
|
||||
process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!,
|
||||
);
|
||||
|
||||
export default function useCredits(): {
|
||||
credits: number | null;
|
||||
fetchCredits: () => void;
|
||||
requestTopUp: (usd_amount: number) => Promise<void>;
|
||||
} {
|
||||
const [credits, setCredits] = useState<number | null>(null);
|
||||
const api = useMemo(() => new AutoGPTServerAPI(), []);
|
||||
const router = useRouter();
|
||||
|
||||
const fetchCredits = useCallback(async () => {
|
||||
const response = await api.getUserCredit();
|
||||
setCredits(response.credits);
|
||||
}, [api]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchCredits();
|
||||
}, [fetchCredits]);
|
||||
|
||||
const requestTopUp = useCallback(
|
||||
async (usd_amount: number) => {
|
||||
const stripe = await stripePromise;
|
||||
|
||||
if (!stripe) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert dollar amount to credit count
|
||||
const response = await api.requestTopUp(usd_amount * 100);
|
||||
router.push(response.checkout_url);
|
||||
},
|
||||
[api, router],
|
||||
);
|
||||
|
||||
return {
|
||||
credits,
|
||||
fetchCredits,
|
||||
requestTopUp,
|
||||
};
|
||||
}
|
|
@ -88,6 +88,18 @@ export default class BackendAPI {
|
|||
}
|
||||
}
|
||||
|
||||
requestTopUp(amount: number): Promise<{ checkout_url: string }> {
|
||||
return this._request("POST", "/credits", { amount });
|
||||
}
|
||||
|
||||
getUserPaymentPortalLink(): Promise<{ url: string }> {
|
||||
return this._get("/credits/manage");
|
||||
}
|
||||
|
||||
fulfillCheckout(): Promise<void> {
|
||||
return this._request("PATCH", "/credits");
|
||||
}
|
||||
|
||||
getBlocks(): Promise<Block[]> {
|
||||
return this._get("/blocks");
|
||||
}
|
||||
|
|
|
@ -111,6 +111,7 @@ export const PROVIDER_NAMES = {
|
|||
GROQ: "groq",
|
||||
IDEOGRAM: "ideogram",
|
||||
JINA: "jina",
|
||||
LINEAR: "linear",
|
||||
MEDIUM: "medium",
|
||||
NOTION: "notion",
|
||||
NVIDIA: "nvidia",
|
||||
|
|
|
@ -5,9 +5,9 @@ import { NextResponse, type NextRequest } from "next/server";
|
|||
const PROTECTED_PAGES = [
|
||||
"/monitor",
|
||||
"/build",
|
||||
"/store/profile",
|
||||
"/store/settings",
|
||||
"/store/dashboard",
|
||||
"/marketplace/profile",
|
||||
"/marketplace/settings",
|
||||
"/marketplace/dashboard",
|
||||
];
|
||||
const ADMIN_PAGES = ["/admin"];
|
||||
|
||||
|
@ -87,7 +87,7 @@ export async function updateSession(request: NextRequest) {
|
|||
ADMIN_PAGES.some((page) => request.nextUrl.pathname.startsWith(`${page}`))
|
||||
) {
|
||||
// no user, potentially respond by redirecting the user to the login page
|
||||
url.pathname = `/store`;
|
||||
url.pathname = `/marketplace`;
|
||||
return NextResponse.redirect(url);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ test.describe("Authentication", () => {
|
|||
test("user can login successfully", async ({ page, loginPage, testUser }) => {
|
||||
await page.goto("/login");
|
||||
await loginPage.login(testUser.email, testUser.password);
|
||||
await test.expect(page).toHaveURL("/store");
|
||||
await test.expect(page).toHaveURL("/marketplace");
|
||||
await test
|
||||
.expect(page.getByTestId("profile-popout-menu-trigger"))
|
||||
.toBeVisible();
|
||||
|
@ -19,7 +19,7 @@ test.describe("Authentication", () => {
|
|||
await page.goto("/login");
|
||||
await loginPage.login(testUser.email, testUser.password);
|
||||
|
||||
await test.expect(page).toHaveURL("/store");
|
||||
await test.expect(page).toHaveURL("/marketplace");
|
||||
|
||||
// Click on the profile menu trigger to open popout
|
||||
await page.getByTestId("profile-popout-menu-trigger").click();
|
||||
|
@ -43,7 +43,7 @@ test.describe("Authentication", () => {
|
|||
}) => {
|
||||
await page.goto("/login");
|
||||
await loginPage.login(testUser.email, testUser.password);
|
||||
await test.expect(page).toHaveURL("/store");
|
||||
await test.expect(page).toHaveURL("/marketplace");
|
||||
// Click on the profile menu trigger to open popout
|
||||
await page.getByTestId("profile-popout-menu-trigger").click();
|
||||
|
||||
|
@ -52,7 +52,7 @@ test.describe("Authentication", () => {
|
|||
|
||||
await test.expect(page).toHaveURL("/login");
|
||||
await loginPage.login(testUser.email, testUser.password);
|
||||
await test.expect(page).toHaveURL("/store");
|
||||
await test.expect(page).toHaveURL("/marketplace");
|
||||
await test
|
||||
.expect(page.getByTestId("profile-popout-menu-trigger"))
|
||||
.toBeVisible();
|
||||
|
|
|
@ -42,39 +42,75 @@ test.describe("Build", () => { //(1)!
|
|||
});
|
||||
// --8<-- [end:BuildPageExample]
|
||||
|
||||
test("user can add all blocks", async ({ page }, testInfo) => {
|
||||
test("user can add all blocks a-l", async ({ page }, testInfo) => {
|
||||
// this test is slow af so we 10x the timeout (sorry future me)
|
||||
await test.setTimeout(testInfo.timeout * 10);
|
||||
await test.setTimeout(testInfo.timeout * 100);
|
||||
await test.expect(buildPage.isLoaded()).resolves.toBeTruthy();
|
||||
await test.expect(page).toHaveURL(new RegExp("/.*build"));
|
||||
await buildPage.closeTutorial();
|
||||
await buildPage.openBlocksPanel();
|
||||
const blocks = await buildPage.getBlocks();
|
||||
|
||||
// add all the blocks in order
|
||||
const blocksToSkip = await buildPage.getBlocksToSkip();
|
||||
|
||||
// add all the blocks in order except for the agent executor block
|
||||
for (const block of blocks) {
|
||||
if (block.id !== "e189baac-8c20-45a1-94a7-55177ea42565") {
|
||||
if (block.name[0].toLowerCase() >= "m") {
|
||||
continue;
|
||||
}
|
||||
if (!blocksToSkip.some((b) => b === block.id)) {
|
||||
await buildPage.addBlock(block);
|
||||
}
|
||||
}
|
||||
await buildPage.closeBlocksPanel();
|
||||
// check that all the blocks are visible
|
||||
for (const block of blocks) {
|
||||
if (block.id !== "e189baac-8c20-45a1-94a7-55177ea42565") {
|
||||
if (block.name[0].toLowerCase() >= "m") {
|
||||
continue;
|
||||
}
|
||||
if (!blocksToSkip.some((b) => b === block.id)) {
|
||||
console.log("Checking block:", block.name);
|
||||
await test.expect(buildPage.hasBlock(block)).resolves.toBeTruthy();
|
||||
}
|
||||
}
|
||||
// fill in the input for the agent input block
|
||||
await buildPage.fillBlockInputByPlaceholder(
|
||||
blocks.find((b) => b.name === "Agent Input")?.id ?? "",
|
||||
"Enter Name",
|
||||
"Agent Input Field",
|
||||
);
|
||||
await buildPage.fillBlockInputByPlaceholder(
|
||||
blocks.find((b) => b.name === "Agent Output")?.id ?? "",
|
||||
"Enter Name",
|
||||
"Agent Output Field",
|
||||
);
|
||||
|
||||
// check that we can save the agent with all the blocks
|
||||
await buildPage.saveAgent("all blocks test", "all blocks test");
|
||||
// page should have a url like http://localhost:3000/build?flowID=f4f3a1da-cfb3-430f-a074-a455b047e340
|
||||
await test.expect(page).toHaveURL(new RegExp("/.*build\\?flowID=.+"));
|
||||
});
|
||||
|
||||
test("user can add all blocks m-z", async ({ page }, testInfo) => {
|
||||
// this test is slow af so we 10x the timeout (sorry future me)
|
||||
await test.setTimeout(testInfo.timeout * 100);
|
||||
await test.expect(buildPage.isLoaded()).resolves.toBeTruthy();
|
||||
await test.expect(page).toHaveURL(new RegExp("/.*build"));
|
||||
await buildPage.closeTutorial();
|
||||
await buildPage.openBlocksPanel();
|
||||
const blocks = await buildPage.getBlocks();
|
||||
|
||||
const blocksToSkip = await buildPage.getBlocksToSkip();
|
||||
|
||||
// add all the blocks in order except for the agent executor block
|
||||
for (const block of blocks) {
|
||||
if (block.name[0].toLowerCase() < "m") {
|
||||
continue;
|
||||
}
|
||||
if (!blocksToSkip.some((b) => b === block.id)) {
|
||||
await buildPage.addBlock(block);
|
||||
}
|
||||
}
|
||||
await buildPage.closeBlocksPanel();
|
||||
// check that all the blocks are visible
|
||||
for (const block of blocks) {
|
||||
if (block.name[0].toLowerCase() < "m") {
|
||||
continue;
|
||||
}
|
||||
if (!blocksToSkip.some((b) => b === block.id)) {
|
||||
await test.expect(buildPage.hasBlock(block)).resolves.toBeTruthy();
|
||||
}
|
||||
}
|
||||
|
||||
// check that we can save the agent with all the blocks
|
||||
await buildPage.saveAgent("all blocks test", "all blocks test");
|
||||
// page should have a url like http://localhost:3000/build?flowID=f4f3a1da-cfb3-430f-a074-a455b047e340
|
||||
|
|
|
@ -6,8 +6,7 @@ import { v4 as uuidv4 } from "uuid";
|
|||
import * as fs from "fs/promises";
|
||||
import path from "path";
|
||||
// --8<-- [start:AttachAgentId]
|
||||
|
||||
test.describe.skip("Monitor", () => {
|
||||
test.describe("Monitor", () => {
|
||||
let buildPage: BuildPage;
|
||||
let monitorPage: MonitorPage;
|
||||
|
||||
|
@ -54,21 +53,25 @@ test.describe.skip("Monitor", () => {
|
|||
await test.expect(agents.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test("user can export and import agents", async ({
|
||||
test.skip("user can export and import agents", async ({
|
||||
page,
|
||||
}, testInfo: TestInfo) => {
|
||||
// --8<-- [start:ReadAgentId]
|
||||
if (testInfo.attachments.length === 0 || !testInfo.attachments[0].body) {
|
||||
throw new Error("No agent id attached to the test");
|
||||
}
|
||||
const id = testInfo.attachments[0].body.toString();
|
||||
const testAttachName = testInfo.attachments[0].body.toString();
|
||||
// --8<-- [end:ReadAgentId]
|
||||
const agents = await monitorPage.listAgents();
|
||||
|
||||
const downloadPromise = page.waitForEvent("download");
|
||||
await monitorPage.exportToFile(
|
||||
agents.find((a: any) => a.id === id) || agents[0],
|
||||
const agent = agents.find(
|
||||
(a: any) => a.name === `test-agent-${testAttachName}`,
|
||||
);
|
||||
if (!agent) {
|
||||
throw new Error(`Agent ${testAttachName} not found`);
|
||||
}
|
||||
await monitorPage.exportToFile(agent);
|
||||
const download = await downloadPromise;
|
||||
|
||||
// Wait for the download process to complete and save the downloaded file somewhere.
|
||||
|
@ -78,9 +81,6 @@ test.describe.skip("Monitor", () => {
|
|||
console.log(`downloaded file to ${download.suggestedFilename()}`);
|
||||
await test.expect(download.suggestedFilename()).toBeDefined();
|
||||
// test-agent-uuid-v1.json
|
||||
if (id) {
|
||||
await test.expect(download.suggestedFilename()).toContain(id);
|
||||
}
|
||||
await test.expect(download.suggestedFilename()).toContain("test-agent-");
|
||||
await test.expect(download.suggestedFilename()).toContain("v1.json");
|
||||
|
||||
|
@ -89,9 +89,9 @@ test.describe.skip("Monitor", () => {
|
|||
const filesInFolder = await fs.readdir(
|
||||
`${monitorPage.downloadsFolder}/monitor`,
|
||||
);
|
||||
const importFile = filesInFolder.find((f) => f.includes(id));
|
||||
const importFile = filesInFolder.find((f) => f.includes(testAttachName));
|
||||
if (!importFile) {
|
||||
throw new Error(`No import file found for agent ${id}`);
|
||||
throw new Error(`No import file found for agent ${testAttachName}`);
|
||||
}
|
||||
const baseName = importFile.split(".")[0];
|
||||
await monitorPage.importFromFile(
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { ElementHandle, Locator, Page } from "@playwright/test";
|
||||
import { BasePage } from "./base.page";
|
||||
|
||||
interface Block {
|
||||
export interface Block {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
|
@ -378,6 +378,39 @@ export class BuildPage extends BasePage {
|
|||
};
|
||||
}
|
||||
|
||||
async getAgentExecutorBlockDetails(): Promise<Block> {
|
||||
return {
|
||||
id: "e189baac-8c20-45a1-94a7-55177ea42565",
|
||||
name: "Agent Executor",
|
||||
description: "Executes an existing agent inside your agent",
|
||||
};
|
||||
}
|
||||
|
||||
async getAgentOutputBlockDetails(): Promise<Block> {
|
||||
return {
|
||||
id: "363ae599-353e-4804-937e-b2ee3cef3da4",
|
||||
name: "Agent Output",
|
||||
description: "This block is used to output the result of an agent.",
|
||||
};
|
||||
}
|
||||
|
||||
async getAgentInputBlockDetails(): Promise<Block> {
|
||||
return {
|
||||
id: "c0a8e994-ebf1-4a9c-a4d8-89d09c86741b",
|
||||
name: "Agent Input",
|
||||
description: "This block is used to provide input to the graph.",
|
||||
};
|
||||
}
|
||||
|
||||
async getGithubTriggerBlockDetails(): Promise<Block> {
|
||||
return {
|
||||
id: "6c60ec01-8128-419e-988f-96a063ee2fea",
|
||||
name: "Github Trigger",
|
||||
description:
|
||||
"This block triggers on pull request events and outputs the event type and payload.",
|
||||
};
|
||||
}
|
||||
|
||||
async nextTutorialStep(): Promise<void> {
|
||||
console.log(`clicking next tutorial step`);
|
||||
await this.page.getByRole("button", { name: "Next" }).click();
|
||||
|
@ -448,6 +481,15 @@ export class BuildPage extends BasePage {
|
|||
);
|
||||
}
|
||||
|
||||
async getBlocksToSkip(): Promise<string[]> {
|
||||
return [
|
||||
(await this.getAgentExecutorBlockDetails()).id,
|
||||
(await this.getAgentInputBlockDetails()).id,
|
||||
(await this.getAgentOutputBlockDetails()).id,
|
||||
(await this.getGithubTriggerBlockDetails()).id,
|
||||
];
|
||||
}
|
||||
|
||||
async waitForRunTutorialButton(): Promise<void> {
|
||||
console.log(`waiting for run tutorial button`);
|
||||
await this.page.waitForSelector('[id="press-run-label"]');
|
||||
|
|
|
@ -43,9 +43,6 @@ export class MonitorPage extends BasePage {
|
|||
async isLoaded(): Promise<boolean> {
|
||||
console.log(`checking if monitor page is loaded`);
|
||||
try {
|
||||
// Wait for network to settle first
|
||||
await this.page.waitForLoadState("networkidle", { timeout: 10_000 });
|
||||
|
||||
// Wait for the monitor page
|
||||
await this.page.getByTestId("monitor-page").waitFor({
|
||||
state: "visible",
|
||||
|
@ -55,7 +52,7 @@ export class MonitorPage extends BasePage {
|
|||
// Wait for table headers to be visible (indicates table structure is ready)
|
||||
await this.page.locator("thead th").first().waitFor({
|
||||
state: "visible",
|
||||
timeout: 5_000,
|
||||
timeout: 15_000,
|
||||
});
|
||||
|
||||
// Wait for either a table row or an empty tbody to be present
|
||||
|
@ -63,14 +60,14 @@ export class MonitorPage extends BasePage {
|
|||
// Wait for at least one row
|
||||
this.page.locator("tbody tr[data-testid]").first().waitFor({
|
||||
state: "visible",
|
||||
timeout: 5_000,
|
||||
timeout: 15_000,
|
||||
}),
|
||||
// OR wait for an empty tbody (indicating no agents but table is loaded)
|
||||
this.page
|
||||
.locator("tbody[data-testid='agent-flow-list-body']:empty")
|
||||
.waitFor({
|
||||
state: "visible",
|
||||
timeout: 5_000,
|
||||
timeout: 15_000,
|
||||
}),
|
||||
]);
|
||||
|
||||
|
@ -114,6 +111,13 @@ export class MonitorPage extends BasePage {
|
|||
});
|
||||
}
|
||||
|
||||
agents.reduce((acc, agent) => {
|
||||
if (!agent.id.includes("flow-run")) {
|
||||
acc.push(agent);
|
||||
}
|
||||
return acc;
|
||||
}, [] as Agent[]);
|
||||
|
||||
return agents;
|
||||
}
|
||||
|
||||
|
@ -219,7 +223,7 @@ export class MonitorPage extends BasePage {
|
|||
async exportToFile(agent: Agent) {
|
||||
await this.clickAgent(agent.id);
|
||||
|
||||
console.log(`exporting agent ${agent.id} ${agent.name} to file`);
|
||||
console.log(`exporting agent id: ${agent.id} name: ${agent.name} to file`);
|
||||
await this.page.getByTestId("export-button").click();
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ test.describe("Profile", () => {
|
|||
// Start each test with login using worker auth
|
||||
await page.goto("/login");
|
||||
await loginPage.login(testUser.email, testUser.password);
|
||||
await test.expect(page).toHaveURL("/store");
|
||||
await test.expect(page).toHaveURL("/marketplace");
|
||||
});
|
||||
|
||||
test("user can view their profile information", async ({
|
||||
|
|
|
@ -1018,10 +1018,10 @@
|
|||
"@types/tough-cookie" "^4.0.5"
|
||||
tough-cookie "^4.1.4"
|
||||
|
||||
"@chromatic-com/storybook@^3.2.3":
|
||||
version "3.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@chromatic-com/storybook/-/storybook-3.2.3.tgz#0f4d167ac80fcb38293a92c230c43446049b6758"
|
||||
integrity sha512-3+hfANx79kIjP1qrOSLxpoAXOiYUA0S7A0WI0A24kASrv7USFNNW8etR5TjUilMb0LmqKUn3wDwUK2h6aceQ9g==
|
||||
"@chromatic-com/storybook@^3.2.4":
|
||||
version "3.2.4"
|
||||
resolved "https://registry.yarnpkg.com/@chromatic-com/storybook/-/storybook-3.2.4.tgz#8cb44d9475be18675498b17a6c6ad479872a35f1"
|
||||
integrity sha512-5/bOOYxfwZ2BktXeqcCpOVAoR6UCoeART5t9FVy22hoo8F291zOuX4y3SDgm10B1GVU/ZTtJWPT2X9wZFlxYLg==
|
||||
dependencies:
|
||||
chromatic "^11.15.0"
|
||||
filesize "^10.0.12"
|
||||
|
@ -1693,62 +1693,62 @@
|
|||
outvariant "^1.4.3"
|
||||
strict-event-emitter "^0.5.1"
|
||||
|
||||
"@next/env@14.2.20":
|
||||
version "14.2.20"
|
||||
resolved "https://registry.yarnpkg.com/@next/env/-/env-14.2.20.tgz#0be2cc955f4eb837516e7d7382284cd5bc1d5a02"
|
||||
integrity sha512-JfDpuOCB0UBKlEgEy/H6qcBSzHimn/YWjUHzKl1jMeUO+QVRdzmTTl8gFJaNO87c8DXmVKhFCtwxQ9acqB3+Pw==
|
||||
"@next/env@14.2.23":
|
||||
version "14.2.23"
|
||||
resolved "https://registry.yarnpkg.com/@next/env/-/env-14.2.23.tgz#3003b53693cbc476710b856f83e623c8231a6be9"
|
||||
integrity sha512-CysUC9IO+2Bh0omJ3qrb47S8DtsTKbFidGm6ow4gXIG6reZybqxbkH2nhdEm1tC8SmgzDdpq3BIML0PWsmyUYA==
|
||||
|
||||
"@next/eslint-plugin-next@15.1.3":
|
||||
version "15.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@next/eslint-plugin-next/-/eslint-plugin-next-15.1.3.tgz#32777736af151577df52d83f25c0c22bc9f3cb5e"
|
||||
integrity sha512-oeP1vnc5Cq9UoOb8SYHAEPbCXMzOgG70l+Zfd+Ie00R25FOm+CCVNrcIubJvB1tvBgakXE37MmqSycksXVPRqg==
|
||||
"@next/eslint-plugin-next@15.1.5":
|
||||
version "15.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@next/eslint-plugin-next/-/eslint-plugin-next-15.1.5.tgz#00c2c4313760049e5fde11ba7a3bd7a8a3cf97c1"
|
||||
integrity sha512-3cCrXBybsqe94UxD6DBQCYCCiP9YohBMgZ5IzzPYHmPzj8oqNlhBii5b6o1HDDaRHdz2pVnSsAROCtrczy8O0g==
|
||||
dependencies:
|
||||
fast-glob "3.3.1"
|
||||
|
||||
"@next/swc-darwin-arm64@14.2.20":
|
||||
version "14.2.20"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.20.tgz#3c99d318c08362aedde5d2778eec3a50b8085d99"
|
||||
integrity sha512-WDfq7bmROa5cIlk6ZNonNdVhKmbCv38XteVFYsxea1vDJt3SnYGgxLGMTXQNfs5OkFvAhmfKKrwe7Y0Hs+rWOg==
|
||||
"@next/swc-darwin-arm64@14.2.23":
|
||||
version "14.2.23"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.23.tgz#6d83f03e35e163e8bbeaf5aeaa6bf55eed23d7a1"
|
||||
integrity sha512-WhtEntt6NcbABA8ypEoFd3uzq5iAnrl9AnZt9dXdO+PZLACE32z3a3qA5OoV20JrbJfSJ6Sd6EqGZTrlRnGxQQ==
|
||||
|
||||
"@next/swc-darwin-x64@14.2.20":
|
||||
version "14.2.20"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.20.tgz#fd547fad1446a677f29c1160006fdd482bba4052"
|
||||
integrity sha512-XIQlC+NAmJPfa2hruLvr1H1QJJeqOTDV+v7tl/jIdoFvqhoihvSNykLU/G6NMgoeo+e/H7p/VeWSOvMUHKtTIg==
|
||||
"@next/swc-darwin-x64@14.2.23":
|
||||
version "14.2.23"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.23.tgz#e02abc35d5e36ce1550f674f8676999f293ba54f"
|
||||
integrity sha512-vwLw0HN2gVclT/ikO6EcE+LcIN+0mddJ53yG4eZd0rXkuEr/RnOaMH8wg/sYl5iz5AYYRo/l6XX7FIo6kwbw1Q==
|
||||
|
||||
"@next/swc-linux-arm64-gnu@14.2.20":
|
||||
version "14.2.20"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.20.tgz#1d6ba1929d3a11b74c0185cdeca1e38b824222ca"
|
||||
integrity sha512-pnzBrHTPXIMm5QX3QC8XeMkpVuoAYOmyfsO4VlPn+0NrHraNuWjdhe+3xLq01xR++iCvX+uoeZmJDKcOxI201Q==
|
||||
"@next/swc-linux-arm64-gnu@14.2.23":
|
||||
version "14.2.23"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.23.tgz#f13516ad2d665950951b59e7c239574bb8504d63"
|
||||
integrity sha512-uuAYwD3At2fu5CH1wD7FpP87mnjAv4+DNvLaR9kiIi8DLStWSW304kF09p1EQfhcbUI1Py2vZlBO2VaVqMRtpg==
|
||||
|
||||
"@next/swc-linux-arm64-musl@14.2.20":
|
||||
version "14.2.20"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.20.tgz#0fe0c67b5d916f99ca76b39416557af609768f17"
|
||||
integrity sha512-WhJJAFpi6yqmUx1momewSdcm/iRXFQS0HU2qlUGlGE/+98eu7JWLD5AAaP/tkK1mudS/rH2f9E3WCEF2iYDydQ==
|
||||
"@next/swc-linux-arm64-musl@14.2.23":
|
||||
version "14.2.23"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.23.tgz#10d05a1c161dc8426d54ccf6d9bbed6953a3252a"
|
||||
integrity sha512-Mm5KHd7nGgeJ4EETvVgFuqKOyDh+UMXHXxye6wRRFDr4FdVRI6YTxajoV2aHE8jqC14xeAMVZvLqYqS7isHL+g==
|
||||
|
||||
"@next/swc-linux-x64-gnu@14.2.20":
|
||||
version "14.2.20"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.20.tgz#6d29fa8cdb6a9f8250c2048aaa24538f0cd0b02d"
|
||||
integrity sha512-ao5HCbw9+iG1Kxm8XsGa3X174Ahn17mSYBQlY6VGsdsYDAbz/ZP13wSLfvlYoIDn1Ger6uYA+yt/3Y9KTIupRg==
|
||||
"@next/swc-linux-x64-gnu@14.2.23":
|
||||
version "14.2.23"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.23.tgz#7f5856df080f58ba058268b30429a2ab52500536"
|
||||
integrity sha512-Ybfqlyzm4sMSEQO6lDksggAIxnvWSG2cDWnG2jgd+MLbHYn2pvFA8DQ4pT2Vjk3Cwrv+HIg7vXJ8lCiLz79qoQ==
|
||||
|
||||
"@next/swc-linux-x64-musl@14.2.20":
|
||||
version "14.2.20"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.20.tgz#bfc57482bc033fda8455e8aab1c3cbc44f0c4690"
|
||||
integrity sha512-CXm/kpnltKTT7945np6Td3w7shj/92TMRPyI/VvveFe8+YE+/YOJ5hyAWK5rpx711XO1jBCgXl211TWaxOtkaA==
|
||||
"@next/swc-linux-x64-musl@14.2.23":
|
||||
version "14.2.23"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.23.tgz#d494ebdf26421c91be65f9b1d095df0191c956d8"
|
||||
integrity sha512-OSQX94sxd1gOUz3jhhdocnKsy4/peG8zV1HVaW6DLEbEmRRtUCUQZcKxUD9atLYa3RZA+YJx+WZdOnTkDuNDNA==
|
||||
|
||||
"@next/swc-win32-arm64-msvc@14.2.20":
|
||||
version "14.2.20"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.20.tgz#6f7783e643310510240a981776532ffe0e02af95"
|
||||
integrity sha512-upJn2HGQgKNDbXVfIgmqT2BN8f3z/mX8ddoyi1I565FHbfowVK5pnMEwauvLvaJf4iijvuKq3kw/b6E9oIVRWA==
|
||||
"@next/swc-win32-arm64-msvc@14.2.23":
|
||||
version "14.2.23"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.23.tgz#62786e7ba4822a20b6666e3e03e5a389b0e7eb3b"
|
||||
integrity sha512-ezmbgZy++XpIMTcTNd0L4k7+cNI4ET5vMv/oqNfTuSXkZtSA9BURElPFyarjjGtRgZ9/zuKDHoMdZwDZIY3ehQ==
|
||||
|
||||
"@next/swc-win32-ia32-msvc@14.2.20":
|
||||
version "14.2.20"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.20.tgz#58c7720687e80a13795e22c29d5860fa142e44fc"
|
||||
integrity sha512-igQW/JWciTGJwj3G1ipalD2V20Xfx3ywQy17IV0ciOUBbFhNfyU1DILWsTi32c8KmqgIDviUEulW/yPb2FF90w==
|
||||
"@next/swc-win32-ia32-msvc@14.2.23":
|
||||
version "14.2.23"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.23.tgz#ef028af91e1c40a4ebba0d2c47b23c1eeb299594"
|
||||
integrity sha512-zfHZOGguFCqAJ7zldTKg4tJHPJyJCOFhpoJcVxKL9BSUHScVDnMdDuOU1zPPGdOzr/GWxbhYTjyiEgLEpAoFPA==
|
||||
|
||||
"@next/swc-win32-x64-msvc@14.2.20":
|
||||
version "14.2.20"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.20.tgz#689bc7beb8005b73c95d926e7edfb7f73efc78f2"
|
||||
integrity sha512-AFmqeLW6LtxeFTuoB+MXFeM5fm5052i3MU6xD0WzJDOwku6SkZaxb1bxjBaRC8uNqTRTSPl0yMFtjNowIVI67w==
|
||||
"@next/swc-win32-x64-msvc@14.2.23":
|
||||
version "14.2.23"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.23.tgz#c81838f02f2f16a321b7533890fb63c1edec68e1"
|
||||
integrity sha512-xCtq5BD553SzOgSZ7UH5LH+OATQihydObTrCTvVzOro8QiWYKdBVwcB2Mn2MLMo6DGW9yH1LSPw7jS7HhgJgjw==
|
||||
|
||||
"@next/third-parties@^15.1.3":
|
||||
version "15.1.3"
|
||||
|
@ -2879,18 +2879,20 @@
|
|||
dependencies:
|
||||
"@sinonjs/commons" "^3.0.0"
|
||||
|
||||
"@storybook/addon-a11y@^8.3.5":
|
||||
version "8.4.7"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-a11y/-/addon-a11y-8.4.7.tgz#0073090d8d4e0748249317a292ac27dc2c2b9ef2"
|
||||
integrity sha512-GpUvXp6n25U1ZSv+hmDC+05BEqxWdlWjQTb/GaboRXZQeMBlze6zckpVb66spjmmtQAIISo0eZxX1+mGcVR7lA==
|
||||
"@storybook/addon-a11y@^8.5.0":
|
||||
version "8.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-a11y/-/addon-a11y-8.5.0.tgz#803ef2a4330e41475eef5b75b5154e228b7b4a6c"
|
||||
integrity sha512-dTKlnhOaDsAXxkmHz7m6/qb98IENoaXTCG3fXo2iwJ1xT27fZF+i8fz8oQprLAN5r7xlnz66ARJvEIKJ+Lxjgw==
|
||||
dependencies:
|
||||
"@storybook/addon-highlight" "8.4.7"
|
||||
"@storybook/addon-highlight" "8.5.0"
|
||||
"@storybook/test" "8.5.0"
|
||||
axe-core "^4.2.0"
|
||||
vitest-axe "^0.1.0"
|
||||
|
||||
"@storybook/addon-actions@8.4.7":
|
||||
version "8.4.7"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-8.4.7.tgz#210c6bb5a7e17c3664c300b4b69b6243ec34b9cd"
|
||||
integrity sha512-mjtD5JxcPuW74T6h7nqMxWTvDneFtokg88p6kQ5OnC1M259iAXb//yiSZgu/quunMHPCXSiqn4FNOSgASTSbsA==
|
||||
"@storybook/addon-actions@8.5.0":
|
||||
version "8.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-8.5.0.tgz#b2dfb6430637762de302174e650c8cec4109a232"
|
||||
integrity sha512-6CW9+17rk5eNx6I8EKqCxRKtsJFTR/lHL+xiJ6/iBWApIm8sg63vhXvUTJ58UixmIkT5oLh0+ESNPh+x10D8fw==
|
||||
dependencies:
|
||||
"@storybook/global" "^5.0.0"
|
||||
"@types/uuid" "^9.0.1"
|
||||
|
@ -2898,131 +2900,128 @@
|
|||
polished "^4.2.2"
|
||||
uuid "^9.0.0"
|
||||
|
||||
"@storybook/addon-backgrounds@8.4.7":
|
||||
version "8.4.7"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-backgrounds/-/addon-backgrounds-8.4.7.tgz#56856bdafc5a2ba18cc19422320883c9e8f66c1c"
|
||||
integrity sha512-I4/aErqtFiazcoWyKafOAm3bLpxTj6eQuH/woSbk1Yx+EzN+Dbrgx1Updy8//bsNtKkcrXETITreqHC+a57DHQ==
|
||||
"@storybook/addon-backgrounds@8.5.0":
|
||||
version "8.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-backgrounds/-/addon-backgrounds-8.5.0.tgz#eb554e59e8bb53cf386bcbe77f03b5547a096bf4"
|
||||
integrity sha512-lzyFLs7niNsqlhH5kdUrp7htLiMIcjY50VLWe0PaeJ6T6GZ7X9qhQzROAUV6cGqzyd8A6y/LzIUntDPMVEm/6g==
|
||||
dependencies:
|
||||
"@storybook/global" "^5.0.0"
|
||||
memoizerific "^1.11.3"
|
||||
ts-dedent "^2.0.0"
|
||||
|
||||
"@storybook/addon-controls@8.4.7":
|
||||
version "8.4.7"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-controls/-/addon-controls-8.4.7.tgz#0c2ace0c7056248577f08f90471f29e861b485be"
|
||||
integrity sha512-377uo5IsJgXLnQLJixa47+11V+7Wn9KcDEw+96aGCBCfLbWNH8S08tJHHnSu+jXg9zoqCAC23MetntVp6LetHA==
|
||||
"@storybook/addon-controls@8.5.0":
|
||||
version "8.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-controls/-/addon-controls-8.5.0.tgz#7fd9379b88305194014771fdbe1010af5206e4f1"
|
||||
integrity sha512-1fivx77A/ahObrPl0L66o9i9MUNfqXxsrpekne5gjMNXw9XJFIRNUe/ddL4CMmwu7SgVbj2QV+q5E5mlnZNTJw==
|
||||
dependencies:
|
||||
"@storybook/global" "^5.0.0"
|
||||
dequal "^2.0.2"
|
||||
ts-dedent "^2.0.0"
|
||||
|
||||
"@storybook/addon-docs@8.4.7":
|
||||
version "8.4.7"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-docs/-/addon-docs-8.4.7.tgz#556515da1049f97023427301e11ecb52d0b9dbe7"
|
||||
integrity sha512-NwWaiTDT5puCBSUOVuf6ME7Zsbwz7Y79WF5tMZBx/sLQ60vpmJVQsap6NSjvK1Ravhc21EsIXqemAcBjAWu80w==
|
||||
"@storybook/addon-docs@8.5.0":
|
||||
version "8.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-docs/-/addon-docs-8.5.0.tgz#0636e460fb948e1fa8377d2f09a85335d55b01ba"
|
||||
integrity sha512-REwLSr1VgOVNJZwP3y3mldhOjBHlM5fqTvq/tC8NaYpAzx9O4rZdoUSZxW3tYtoNoYrHpB8kzRTeZl8WSdKllw==
|
||||
dependencies:
|
||||
"@mdx-js/react" "^3.0.0"
|
||||
"@storybook/blocks" "8.4.7"
|
||||
"@storybook/csf-plugin" "8.4.7"
|
||||
"@storybook/react-dom-shim" "8.4.7"
|
||||
"@storybook/blocks" "8.5.0"
|
||||
"@storybook/csf-plugin" "8.5.0"
|
||||
"@storybook/react-dom-shim" "8.5.0"
|
||||
react "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
react-dom "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
ts-dedent "^2.0.0"
|
||||
|
||||
"@storybook/addon-essentials@^8.4.2":
|
||||
version "8.4.7"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-essentials/-/addon-essentials-8.4.7.tgz#381c74230d1b1a209d5fdc017d241c016b98affe"
|
||||
integrity sha512-+BtZHCBrYtQKILtejKxh0CDRGIgTl9PumfBOKRaihYb4FX1IjSAxoV/oo/IfEjlkF5f87vouShWsRa8EUauFDw==
|
||||
"@storybook/addon-essentials@^8.5.0":
|
||||
version "8.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-essentials/-/addon-essentials-8.5.0.tgz#dc83caa6cc3aad312c2afb88345915fd59129fc6"
|
||||
integrity sha512-RrHRdaw2j3ugZiYQ6OHt3Ff08ID4hwAvipqULEsbEnEw3VlXOaW/MT5e2M7kW3MHskQ3iJ6XAD1Y1rNm432Pzw==
|
||||
dependencies:
|
||||
"@storybook/addon-actions" "8.4.7"
|
||||
"@storybook/addon-backgrounds" "8.4.7"
|
||||
"@storybook/addon-controls" "8.4.7"
|
||||
"@storybook/addon-docs" "8.4.7"
|
||||
"@storybook/addon-highlight" "8.4.7"
|
||||
"@storybook/addon-measure" "8.4.7"
|
||||
"@storybook/addon-outline" "8.4.7"
|
||||
"@storybook/addon-toolbars" "8.4.7"
|
||||
"@storybook/addon-viewport" "8.4.7"
|
||||
"@storybook/addon-actions" "8.5.0"
|
||||
"@storybook/addon-backgrounds" "8.5.0"
|
||||
"@storybook/addon-controls" "8.5.0"
|
||||
"@storybook/addon-docs" "8.5.0"
|
||||
"@storybook/addon-highlight" "8.5.0"
|
||||
"@storybook/addon-measure" "8.5.0"
|
||||
"@storybook/addon-outline" "8.5.0"
|
||||
"@storybook/addon-toolbars" "8.5.0"
|
||||
"@storybook/addon-viewport" "8.5.0"
|
||||
ts-dedent "^2.0.0"
|
||||
|
||||
"@storybook/addon-highlight@8.4.7":
|
||||
version "8.4.7"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-highlight/-/addon-highlight-8.4.7.tgz#06b9752977e38884007e9446f9a2b0c04c873229"
|
||||
integrity sha512-whQIDBd3PfVwcUCrRXvCUHWClXe9mQ7XkTPCdPo4B/tZ6Z9c6zD8JUHT76ddyHivixFLowMnA8PxMU6kCMAiNw==
|
||||
"@storybook/addon-highlight@8.5.0":
|
||||
version "8.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-highlight/-/addon-highlight-8.5.0.tgz#a6fe48434826c1e869135e47dcfe34d68bcf287c"
|
||||
integrity sha512-/JxYzMK5aJSYs0K/0eAEFyER2dMoxqwM891MdnkNwLFdyrM58lzHee00F9oEX6zeQoRUNQPRepq0ui2PvbTMGw==
|
||||
dependencies:
|
||||
"@storybook/global" "^5.0.0"
|
||||
|
||||
"@storybook/addon-interactions@^8.4.2":
|
||||
version "8.4.7"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-interactions/-/addon-interactions-8.4.7.tgz#d34545db5ea6f03a5499ad6742c3317fb9e02d55"
|
||||
integrity sha512-fnufT3ym8ht3HHUIRVXAH47iOJW/QOb0VSM+j269gDuvyDcY03D1civCu1v+eZLGaXPKJ8vtjr0L8zKQ/4P0JQ==
|
||||
"@storybook/addon-interactions@^8.5.0":
|
||||
version "8.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-interactions/-/addon-interactions-8.5.0.tgz#467ab13beedb2bc3113a76b33c2105ce757a7ae8"
|
||||
integrity sha512-vX1a8qS7o/W3kEzfL/CqOj/Rr6UlGLT/n0KXMpfIhx63tzxe1a1qGpFLL0h0zqAVPHZIOu9humWMKri5Iny6oA==
|
||||
dependencies:
|
||||
"@storybook/global" "^5.0.0"
|
||||
"@storybook/instrumenter" "8.4.7"
|
||||
"@storybook/test" "8.4.7"
|
||||
"@storybook/instrumenter" "8.5.0"
|
||||
"@storybook/test" "8.5.0"
|
||||
polished "^4.2.2"
|
||||
ts-dedent "^2.2.0"
|
||||
|
||||
"@storybook/addon-links@^8.4.2":
|
||||
version "8.4.7"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-links/-/addon-links-8.4.7.tgz#c38b2b63c3b0308adacff4b0758464a0657154c4"
|
||||
integrity sha512-L/1h4dMeMKF+MM0DanN24v5p3faNYbbtOApMgg7SlcBT/tgo3+cAjkgmNpYA8XtKnDezm+T2mTDhB8mmIRZpIQ==
|
||||
"@storybook/addon-links@^8.5.0":
|
||||
version "8.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-links/-/addon-links-8.5.0.tgz#3c49062954467ae36b9e0f446368074a8722257d"
|
||||
integrity sha512-Y11GIByAYqn0TibI/xsy0vCe+ZxJS9PVAAoHngLxkf9J4WodAXcJABr8ZPlWDNdaEhSS/FF7UQUmNag0UC2/pw==
|
||||
dependencies:
|
||||
"@storybook/csf" "^0.1.11"
|
||||
"@storybook/csf" "0.1.12"
|
||||
"@storybook/global" "^5.0.0"
|
||||
ts-dedent "^2.0.0"
|
||||
|
||||
"@storybook/addon-measure@8.4.7":
|
||||
version "8.4.7"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-measure/-/addon-measure-8.4.7.tgz#9d556ba34b57c13ad8d00bd953b27ec405a64d23"
|
||||
integrity sha512-QfvqYWDSI5F68mKvafEmZic3SMiK7zZM8VA0kTXx55hF/+vx61Mm0HccApUT96xCXIgmwQwDvn9gS4TkX81Dmw==
|
||||
"@storybook/addon-measure@8.5.0":
|
||||
version "8.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-measure/-/addon-measure-8.5.0.tgz#3459a8e2877c0c36082e3d5fd62cbc6e198cb879"
|
||||
integrity sha512-e8pJy2sICyj0Ff0W1PFc6HPE6PqcjnnHtfuDaO3M9uSKJLYkpTWJ8i1VSP178f8seq44r5/PdQCHqs5q5l3zgw==
|
||||
dependencies:
|
||||
"@storybook/global" "^5.0.0"
|
||||
tiny-invariant "^1.3.1"
|
||||
|
||||
"@storybook/addon-onboarding@^8.4.2":
|
||||
version "8.4.7"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-onboarding/-/addon-onboarding-8.4.7.tgz#212a5e27db1ee8440a2dd0d5c67ac29f0e6efda5"
|
||||
integrity sha512-FdC2NV60VNYeMxf6DVe0qV9ucSBAzMh1//C0Qqwq8CcjthMbmKlVZ7DqbVsbIHKnFaSCaUC88eR5olAfMaauCQ==
|
||||
dependencies:
|
||||
react-confetti "^6.1.0"
|
||||
"@storybook/addon-onboarding@^8.5.0":
|
||||
version "8.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-onboarding/-/addon-onboarding-8.5.0.tgz#eec71fd7fda64bef4ff25401c9d13ade11bc11d7"
|
||||
integrity sha512-77ebcHkKR744ciPbT4ZgqW4W7KrLv1uAdSb3mX3gWukSl4oxP9D/HjmNiX5fBDYWUC4wsf6q5barOs4Hqn8ivw==
|
||||
|
||||
"@storybook/addon-outline@8.4.7":
|
||||
version "8.4.7"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-outline/-/addon-outline-8.4.7.tgz#8a35fe519dd639bb287a370da2222e6ffdce4020"
|
||||
integrity sha512-6LYRqUZxSodmAIl8icr585Oi8pmzbZ90aloZJIpve+dBAzo7ydYrSQxxoQEVltXbKf3VeVcrs64ouAYqjisMYA==
|
||||
"@storybook/addon-outline@8.5.0":
|
||||
version "8.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-outline/-/addon-outline-8.5.0.tgz#9fdb88edbd756da04ff28de665cf06cacd383595"
|
||||
integrity sha512-r12sk1b38Ph6NroWAOTfjbJ/V+gDobm7tKQQlbSDf6fgX7cqyPHmKjfNDCOCQpXouZm/Jm+41zd758PW+Yt4ng==
|
||||
dependencies:
|
||||
"@storybook/global" "^5.0.0"
|
||||
ts-dedent "^2.0.0"
|
||||
|
||||
"@storybook/addon-toolbars@8.4.7":
|
||||
version "8.4.7"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-toolbars/-/addon-toolbars-8.4.7.tgz#b898d4deaf6f5a58f3b70bd8d136cd4ec2844b79"
|
||||
integrity sha512-OSfdv5UZs+NdGB+nZmbafGUWimiweJ/56gShlw8Neo/4jOJl1R3rnRqqY7MYx8E4GwoX+i3GF5C3iWFNQqlDcw==
|
||||
"@storybook/addon-toolbars@8.5.0":
|
||||
version "8.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-toolbars/-/addon-toolbars-8.5.0.tgz#6a17113d74b92d86433b8789d847d5eeadf316c8"
|
||||
integrity sha512-q3yYYO2WX8K2DYNM++FzixGDjzYaeREincgsl2WXYXrcuGb5hkOoOgRiAQL8Nz9NQ1Eo+B/yZxrhG/5VoVhUUQ==
|
||||
|
||||
"@storybook/addon-viewport@8.4.7":
|
||||
version "8.4.7"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-viewport/-/addon-viewport-8.4.7.tgz#e65c53608f52149c06347b395487960605fc4805"
|
||||
integrity sha512-hvczh/jjuXXcOogih09a663sRDDSATXwbE866al1DXgbDFraYD/LxX/QDb38W9hdjU9+Qhx8VFIcNWoMQns5HQ==
|
||||
"@storybook/addon-viewport@8.5.0":
|
||||
version "8.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/addon-viewport/-/addon-viewport-8.5.0.tgz#4e8011c9c5cbd2273a4122b209d7d4f4977d2a2f"
|
||||
integrity sha512-MlhVELImk9YzjEgGR2ciLC8d5tUSGcO7my4kWIClN0VyTRcvG4ZfwrsEC+jN3/l52nrgjLmKrDX5UAGZm6w5mQ==
|
||||
dependencies:
|
||||
memoizerific "^1.11.3"
|
||||
|
||||
"@storybook/blocks@8.4.7", "@storybook/blocks@^8.4.2":
|
||||
version "8.4.7"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/blocks/-/blocks-8.4.7.tgz#ee17f59dd52d11c97c39b0f6b03957085a80ad95"
|
||||
integrity sha512-+QH7+JwXXXIyP3fRCxz/7E2VZepAanXJM7G8nbR3wWsqWgrRp4Wra6MvybxAYCxU7aNfJX5c+RW84SNikFpcIA==
|
||||
"@storybook/blocks@8.5.0", "@storybook/blocks@^8.5.0":
|
||||
version "8.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/blocks/-/blocks-8.5.0.tgz#6994c44bab2df46cbe925f18a064a6433bd66eff"
|
||||
integrity sha512-2sTOgjH/JFOgWnpqkKjpKVvKAgUaC9ZBjH1gnCoA5dne/SDafYaCAYfv6yZn7g2Xm1sTxWCAmMIUkYSALeWr+w==
|
||||
dependencies:
|
||||
"@storybook/csf" "^0.1.11"
|
||||
"@storybook/csf" "0.1.12"
|
||||
"@storybook/icons" "^1.2.12"
|
||||
ts-dedent "^2.0.0"
|
||||
|
||||
"@storybook/builder-webpack5@8.4.7":
|
||||
version "8.4.7"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/builder-webpack5/-/builder-webpack5-8.4.7.tgz#5bc15568716bbf4f45a88fc389e25fa2ce50a8c2"
|
||||
integrity sha512-O8LpsQ+4g2x5kh7rI9+jEUdX8k1a5egBQU1lbudmHchqsV0IKiVqBD9LL5Gj3wpit4vB8coSW4ZWTFBw8FQb4Q==
|
||||
"@storybook/builder-webpack5@8.5.0":
|
||||
version "8.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/builder-webpack5/-/builder-webpack5-8.5.0.tgz#e44f0a197b40e8d3caae21e9459cb9cd4accf57c"
|
||||
integrity sha512-MyCj11cktyN2HeK8NsLv+L0Km36qAz2UGqu6j1VKJUgPelgpCCi4StCW/KaSBeOFAwGD52xjAdNu+c1h/vfiMg==
|
||||
dependencies:
|
||||
"@storybook/core-webpack" "8.4.7"
|
||||
"@types/node" "^22.0.0"
|
||||
"@storybook/core-webpack" "8.5.0"
|
||||
"@types/semver" "^7.3.4"
|
||||
browser-assert "^1.2.1"
|
||||
case-sensitive-paths-webpack-plugin "^2.4.0"
|
||||
|
@ -3047,25 +3046,24 @@
|
|||
webpack-hot-middleware "^2.25.1"
|
||||
webpack-virtual-modules "^0.6.0"
|
||||
|
||||
"@storybook/components@8.4.7":
|
||||
version "8.4.7"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/components/-/components-8.4.7.tgz#09eeffa07aa672ad3966ca1764a43003731b1d30"
|
||||
integrity sha512-uyJIcoyeMWKAvjrG9tJBUCKxr2WZk+PomgrgrUwejkIfXMO76i6jw9BwLa0NZjYdlthDv30r9FfbYZyeNPmF0g==
|
||||
"@storybook/components@8.5.0":
|
||||
version "8.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/components/-/components-8.5.0.tgz#fab176ee7b863e4abc3c8581f6cb7b3d46bf80b0"
|
||||
integrity sha512-DhaHtwfEcfWYj3ih/5RBSDHe3Idxyf+oHw2/DmaLKJX6MluhdK3ZqigjRcTmA9Gj/SbR4CkHEEtDzAvBlW0BYw==
|
||||
|
||||
"@storybook/core-webpack@8.4.7":
|
||||
version "8.4.7"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/core-webpack/-/core-webpack-8.4.7.tgz#660d1cbd03a91fee27b65e6acc2f9269ed1fbfc8"
|
||||
integrity sha512-Tj+CjQLpFyBJxhhMms+vbPT3+gTRAiQlrhY3L1IEVwBa3wtRMS0qjozH26d1hK4G6mUIEdwu13L54HMU/w33Sg==
|
||||
"@storybook/core-webpack@8.5.0":
|
||||
version "8.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/core-webpack/-/core-webpack-8.5.0.tgz#4d4b355d3eeb8b8b1056dd98540d47b3db07bc61"
|
||||
integrity sha512-bJAcF9TwNO2qNa7Jef4h5U9ka4399HDiHiQec1AxdqUIy/2zfbetgV6+2Fr5mtejPqJgbs7kXNGErI+fFByLGg==
|
||||
dependencies:
|
||||
"@types/node" "^22.0.0"
|
||||
ts-dedent "^2.0.0"
|
||||
|
||||
"@storybook/core@8.4.7":
|
||||
version "8.4.7"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/core/-/core-8.4.7.tgz#af9cbb3f26f0b6c98c679a134ce776c202570d66"
|
||||
integrity sha512-7Z8Z0A+1YnhrrSXoKKwFFI4gnsLbWzr8fnDCU6+6HlDukFYh8GHRcZ9zKfqmy6U3hw2h8H5DrHsxWfyaYUUOoA==
|
||||
"@storybook/core@8.5.0":
|
||||
version "8.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/core/-/core-8.5.0.tgz#16125d1cb3f4a26c0af4f0b8e56d7eaffed2e951"
|
||||
integrity sha512-apborO6ynns7SeydBSqE9o0zT6JSU+VY4gLFPJROGcconvSW4bS5xtJCsgjlulceyWVxepFHGXl4jEZw+SktXA==
|
||||
dependencies:
|
||||
"@storybook/csf" "^0.1.11"
|
||||
"@storybook/csf" "0.1.12"
|
||||
better-opn "^3.0.2"
|
||||
browser-assert "^1.2.1"
|
||||
esbuild "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0"
|
||||
|
@ -3077,14 +3075,14 @@
|
|||
util "^0.12.5"
|
||||
ws "^8.2.3"
|
||||
|
||||
"@storybook/csf-plugin@8.4.7":
|
||||
version "8.4.7"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/csf-plugin/-/csf-plugin-8.4.7.tgz#0117c872b05bf033eec089ab0224e0fab01da810"
|
||||
integrity sha512-Fgogplu4HImgC+AYDcdGm1rmL6OR1rVdNX1Be9C/NEXwOCpbbBwi0BxTf/2ZxHRk9fCeaPEcOdP5S8QHfltc1g==
|
||||
"@storybook/csf-plugin@8.5.0":
|
||||
version "8.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/csf-plugin/-/csf-plugin-8.5.0.tgz#f071c2fb9de5941654b00c95354ee7337d61fc7e"
|
||||
integrity sha512-cs6ogviNyLG1h9J8Sb47U3DqIrQmn2EHm4ta3fpCeV3ABbrMgbzYyxtmybz4g/AwlDgjAZAt6PPcXkfCJ6p2CQ==
|
||||
dependencies:
|
||||
unplugin "^1.3.1"
|
||||
|
||||
"@storybook/csf@^0.1.11":
|
||||
"@storybook/csf@0.1.12", "@storybook/csf@^0.1.11":
|
||||
version "0.1.12"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/csf/-/csf-0.1.12.tgz#1dcfa0f398a69b834c563884b5f747db3d5a81df"
|
||||
integrity sha512-9/exVhabisyIVL0VxTCxo01Tdm8wefIXKXfltAPTSr8cbLn5JAxGQ6QV3mjdecLGEOucfoVhAKtJfVHxEK1iqw==
|
||||
|
@ -3101,23 +3099,23 @@
|
|||
resolved "https://registry.yarnpkg.com/@storybook/icons/-/icons-1.3.0.tgz#a5c1460fb15a7260e0b638ab86163f7347a0061e"
|
||||
integrity sha512-Nz/UzeYQdUZUhacrPyfkiiysSjydyjgg/p0P9HxB4p/WaJUUjMAcaoaLgy3EXx61zZJ3iD36WPuDkZs5QYrA0A==
|
||||
|
||||
"@storybook/instrumenter@8.4.7":
|
||||
version "8.4.7"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/instrumenter/-/instrumenter-8.4.7.tgz#5a37876fee8f828241a1e7fd76891c6effc1805a"
|
||||
integrity sha512-k6NSD3jaRCCHAFtqXZ7tw8jAzD/yTEWXGya+REgZqq5RCkmJ+9S4Ytp/6OhQMPtPFX23gAuJJzTQVLcCr+gjRg==
|
||||
"@storybook/instrumenter@8.5.0":
|
||||
version "8.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/instrumenter/-/instrumenter-8.5.0.tgz#e8c38ab912f2b7119cd92efe3977bc33246fa808"
|
||||
integrity sha512-eZ/UY6w4U2vay+wX7QVwKiRoyMzZscuv6v4k4r8BlmHPFWbhiZDO9S2GsG16UkyKnrQrYk432he70n7hn1Xvmg==
|
||||
dependencies:
|
||||
"@storybook/global" "^5.0.0"
|
||||
"@vitest/utils" "^2.1.1"
|
||||
|
||||
"@storybook/manager-api@8.4.7":
|
||||
version "8.4.7"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/manager-api/-/manager-api-8.4.7.tgz#4e13debf645c9300d7d6d49195e720d0c7ecd261"
|
||||
integrity sha512-ELqemTviCxAsZ5tqUz39sDmQkvhVAvAgiplYy9Uf15kO0SP2+HKsCMzlrm2ue2FfkUNyqbDayCPPCB0Cdn/mpQ==
|
||||
"@storybook/manager-api@8.5.0":
|
||||
version "8.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/manager-api/-/manager-api-8.5.0.tgz#4024ff316677ca9f4175eb4d1818488dd6f18095"
|
||||
integrity sha512-Ildriueo3eif4M+gMlMxu/mrBIbAnz8+oesmQJKdzZfe/U9eQTI9OUqJsxx/IVBmdzQ3ySsgNmzj5VweRkse4A==
|
||||
|
||||
"@storybook/nextjs@^8.4.2":
|
||||
version "8.4.7"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/nextjs/-/nextjs-8.4.7.tgz#429b7f9b36b3117ed4d6f194c7caea0a88eeb239"
|
||||
integrity sha512-6dVt6VKBndSqn91egZx2fWl44i1TnIggRgmnk5jyl2KHDRvXziFNa2ujBz1nveriAWmwRchhce0OLDx9zQ9b4w==
|
||||
"@storybook/nextjs@^8.5.0":
|
||||
version "8.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/nextjs/-/nextjs-8.5.0.tgz#6aefe2f6cd48577cf2e7e2cba53ae914a64d4c1d"
|
||||
integrity sha512-zUU0wQd4F2p006gZX0XC3+Zsj0tB4DOz+7FjSlnyGbzf5cDE6cD74l0Azj6aZluR4Q2say7gWDIpHu05YvIJsg==
|
||||
dependencies:
|
||||
"@babel/core" "^7.24.4"
|
||||
"@babel/plugin-syntax-bigint" "^7.8.3"
|
||||
|
@ -3133,11 +3131,10 @@
|
|||
"@babel/preset-typescript" "^7.24.1"
|
||||
"@babel/runtime" "^7.24.4"
|
||||
"@pmmmwh/react-refresh-webpack-plugin" "^0.5.11"
|
||||
"@storybook/builder-webpack5" "8.4.7"
|
||||
"@storybook/preset-react-webpack" "8.4.7"
|
||||
"@storybook/react" "8.4.7"
|
||||
"@storybook/test" "8.4.7"
|
||||
"@types/node" "^22.0.0"
|
||||
"@storybook/builder-webpack5" "8.5.0"
|
||||
"@storybook/preset-react-webpack" "8.5.0"
|
||||
"@storybook/react" "8.5.0"
|
||||
"@storybook/test" "8.5.0"
|
||||
"@types/semver" "^7.3.4"
|
||||
babel-loader "^9.1.3"
|
||||
css-loader "^6.7.3"
|
||||
|
@ -3150,7 +3147,7 @@
|
|||
postcss-loader "^8.1.1"
|
||||
react-refresh "^0.14.0"
|
||||
resolve-url-loader "^5.0.0"
|
||||
sass-loader "^13.2.0"
|
||||
sass-loader "^14.2.1"
|
||||
semver "^7.3.5"
|
||||
style-loader "^3.3.1"
|
||||
styled-jsx "^5.1.6"
|
||||
|
@ -3160,15 +3157,14 @@
|
|||
optionalDependencies:
|
||||
sharp "^0.33.3"
|
||||
|
||||
"@storybook/preset-react-webpack@8.4.7":
|
||||
version "8.4.7"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/preset-react-webpack/-/preset-react-webpack-8.4.7.tgz#7b303f464228035a919ea18a3cd6193f6776c2bc"
|
||||
integrity sha512-geTSBKyrBagVihil5MF7LkVFynbfHhCinvnbCZZqXW7M1vgcxvatunUENB+iV8eWg/0EJ+8O7scZL+BAxQ/2qg==
|
||||
"@storybook/preset-react-webpack@8.5.0":
|
||||
version "8.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/preset-react-webpack/-/preset-react-webpack-8.5.0.tgz#59da1eb0dc8a5981c8f793900a299b8bd2437ba6"
|
||||
integrity sha512-KJwVcQVYQWuMT5QUF06be60UuBfazBIO+90erfoYoIx0UwOxKMVnQz0HfG2JMc4EIoNLIl0/cm5mb2k4BWyhbA==
|
||||
dependencies:
|
||||
"@storybook/core-webpack" "8.4.7"
|
||||
"@storybook/react" "8.4.7"
|
||||
"@storybook/core-webpack" "8.5.0"
|
||||
"@storybook/react" "8.5.0"
|
||||
"@storybook/react-docgen-typescript-plugin" "1.0.6--canary.9.0c3f3b7.0"
|
||||
"@types/node" "^22.0.0"
|
||||
"@types/semver" "^7.3.4"
|
||||
find-up "^5.0.0"
|
||||
magic-string "^0.30.5"
|
||||
|
@ -3178,10 +3174,10 @@
|
|||
tsconfig-paths "^4.2.0"
|
||||
webpack "5"
|
||||
|
||||
"@storybook/preview-api@8.4.7":
|
||||
version "8.4.7"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/preview-api/-/preview-api-8.4.7.tgz#85e01a97f4182b974581765d725f6c7a7d190013"
|
||||
integrity sha512-0QVQwHw+OyZGHAJEXo6Knx+6/4er7n2rTDE5RYJ9F2E2Lg42E19pfdLlq2Jhoods2Xrclo3wj6GWR//Ahi39Eg==
|
||||
"@storybook/preview-api@8.5.0":
|
||||
version "8.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/preview-api/-/preview-api-8.5.0.tgz#32dbff12d5299897df2ccf68ad5007e814b8b067"
|
||||
integrity sha512-g0XbD54zMUkl6bpuA7qEBCE9rW1QV6KKmwkO4bkxMOJcMke3x9l00JTaYn7Un8wItjXiS3BIG15B6mnfBG7fng==
|
||||
|
||||
"@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0":
|
||||
version "1.0.6--canary.9.0c3f3b7.0"
|
||||
|
@ -3196,22 +3192,22 @@
|
|||
react-docgen-typescript "^2.2.2"
|
||||
tslib "^2.0.0"
|
||||
|
||||
"@storybook/react-dom-shim@8.4.7":
|
||||
version "8.4.7"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/react-dom-shim/-/react-dom-shim-8.4.7.tgz#f0dd5bbf2fc185def72d9d08a11c8de22f152c2a"
|
||||
integrity sha512-6bkG2jvKTmWrmVzCgwpTxwIugd7Lu+2btsLAqhQSzDyIj2/uhMNp8xIMr/NBDtLgq3nomt9gefNa9xxLwk/OMg==
|
||||
"@storybook/react-dom-shim@8.5.0":
|
||||
version "8.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/react-dom-shim/-/react-dom-shim-8.5.0.tgz#ada85ed027113b0e05debfd5c5e6ffa44843b069"
|
||||
integrity sha512-7P8xg4FiuFpM6kQOzZynno+0zyLVs8NgsmRK58t3JRZXbda1tzlxTXzvqx4hUevvbPJGjmrB0F3xTFH+8Otnvw==
|
||||
|
||||
"@storybook/react@8.4.7", "@storybook/react@^8.3.5":
|
||||
version "8.4.7"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/react/-/react-8.4.7.tgz#e2cf62b3c1d8e4bfe5eff82ced07ec473d4e4fd1"
|
||||
integrity sha512-nQ0/7i2DkaCb7dy0NaT95llRVNYWQiPIVuhNfjr1mVhEP7XD090p0g7eqUmsx8vfdHh2BzWEo6CoBFRd3+EXxw==
|
||||
"@storybook/react@8.5.0", "@storybook/react@^8.3.5":
|
||||
version "8.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/react/-/react-8.5.0.tgz#da6667d8a6f49d5ea4ec95c75f1ab853edada2a4"
|
||||
integrity sha512-/jbkmGGc95N7KduIennL/k8grNTP5ye/YBnkcS4TbF7uDWBtKy3/Wqvx5BIlFXq3qeUnZJ8YtZc0lPIYeCY8XQ==
|
||||
dependencies:
|
||||
"@storybook/components" "8.4.7"
|
||||
"@storybook/components" "8.5.0"
|
||||
"@storybook/global" "^5.0.0"
|
||||
"@storybook/manager-api" "8.4.7"
|
||||
"@storybook/preview-api" "8.4.7"
|
||||
"@storybook/react-dom-shim" "8.4.7"
|
||||
"@storybook/theming" "8.4.7"
|
||||
"@storybook/manager-api" "8.5.0"
|
||||
"@storybook/preview-api" "8.5.0"
|
||||
"@storybook/react-dom-shim" "8.5.0"
|
||||
"@storybook/theming" "8.5.0"
|
||||
|
||||
"@storybook/test-runner@^0.21.0":
|
||||
version "0.21.0"
|
||||
|
@ -3238,24 +3234,29 @@
|
|||
nyc "^15.1.0"
|
||||
playwright "^1.14.0"
|
||||
|
||||
"@storybook/test@8.4.7", "@storybook/test@^8.3.5":
|
||||
version "8.4.7"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/test/-/test-8.4.7.tgz#7f58f2cdf3a6d810bf3ff4e0e2fee634040c678f"
|
||||
integrity sha512-AhvJsu5zl3uG40itSQVuSy5WByp3UVhS6xAnme4FWRwgSxhvZjATJ3AZkkHWOYjnnk+P2/sbz/XuPli1FVCWoQ==
|
||||
"@storybook/test@8.5.0", "@storybook/test@^8.3.5":
|
||||
version "8.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/test/-/test-8.5.0.tgz#d7157a87436000142cf6dedee37a74b86ad776f2"
|
||||
integrity sha512-M/DdPlI6gwL7NGkK5o7GYjdEBp95AsFEUtW29zQfnVIAngYugzi3nIuM/XkQHunidVdAZCYjw2s2Yhhsx/m9sw==
|
||||
dependencies:
|
||||
"@storybook/csf" "^0.1.11"
|
||||
"@storybook/csf" "0.1.12"
|
||||
"@storybook/global" "^5.0.0"
|
||||
"@storybook/instrumenter" "8.4.7"
|
||||
"@storybook/instrumenter" "8.5.0"
|
||||
"@testing-library/dom" "10.4.0"
|
||||
"@testing-library/jest-dom" "6.5.0"
|
||||
"@testing-library/user-event" "14.5.2"
|
||||
"@vitest/expect" "2.0.5"
|
||||
"@vitest/spy" "2.0.5"
|
||||
|
||||
"@storybook/theming@8.4.7":
|
||||
version "8.4.7"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-8.4.7.tgz#c308f6a883999bd35e87826738ab8a76515932b5"
|
||||
integrity sha512-99rgLEjf7iwfSEmdqlHkSG3AyLcK0sfExcr0jnc6rLiAkBhzuIsvcHjjUwkR210SOCgXqBPW0ZA6uhnuyppHLw==
|
||||
"@storybook/theming@8.5.0":
|
||||
version "8.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-8.5.0.tgz#c51c938ae6d695ecb73604e46d19d9db33859408"
|
||||
integrity sha512-591LbOj/HMmHYUfLgrMerxhF1A9mY61HWKxcRpB6xxalc1Xw1kRtQ49DcwuTXnUu9ktBB3nuOzPNPQPFSh/7PQ==
|
||||
|
||||
"@stripe/stripe-js@^5.3.0":
|
||||
version "5.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@stripe/stripe-js/-/stripe-js-5.4.0.tgz#847e870ddfe9283432526867857a4c1fba9b11ed"
|
||||
integrity sha512-3tfMbSvLGB+OsJ2MsjWjWo+7sp29dwx+3+9kG/TEnZQJt+EwbF/Nomm43cSK+6oXZA9uhspgyrB+BbrPRumx4g==
|
||||
|
||||
"@supabase/auth-js@2.67.3":
|
||||
version "2.67.3"
|
||||
|
@ -3714,10 +3715,10 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/negotiator/-/negotiator-0.6.3.tgz#29e8fce64e35f57f6fe9c624f8e4ed304357745a"
|
||||
integrity sha512-JkXTOdKs5MF086b/pt8C3+yVp3iDUwG635L7oCH6HvJvvr6lSUU5oe/gLXnPEfYRROHjJIPgCV6cuAg8gGkntQ==
|
||||
|
||||
"@types/node@*", "@types/node@^22.0.0", "@types/node@^22.10.5":
|
||||
version "22.10.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.10.5.tgz#95af89a3fb74a2bb41ef9927f206e6472026e48b"
|
||||
integrity sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==
|
||||
"@types/node@*", "@types/node@^22.10.7":
|
||||
version "22.10.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.10.7.tgz#14a1ca33fd0ebdd9d63593ed8d3fbc882a6d28d7"
|
||||
integrity sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==
|
||||
dependencies:
|
||||
undici-types "~6.20.0"
|
||||
|
||||
|
@ -4494,7 +4495,7 @@ available-typed-arrays@^1.0.7:
|
|||
dependencies:
|
||||
possible-typed-array-names "^1.0.0"
|
||||
|
||||
axe-core@^4.10.0, axe-core@^4.2.0:
|
||||
axe-core@^4.10.0, axe-core@^4.2.0, axe-core@^4.4.2:
|
||||
version "4.10.2"
|
||||
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.10.2.tgz#85228e3e1d8b8532a27659b332e39b7fa0e022df"
|
||||
integrity sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==
|
||||
|
@ -4935,6 +4936,11 @@ chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2:
|
|||
ansi-styles "^4.1.0"
|
||||
supports-color "^7.1.0"
|
||||
|
||||
chalk@^5.0.1:
|
||||
version "5.4.1"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.4.1.tgz#1b48bf0963ec158dce2aacf69c093ae2dd2092d8"
|
||||
integrity sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==
|
||||
|
||||
chalk@^5.2.0:
|
||||
version "5.3.0"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385"
|
||||
|
@ -4990,10 +4996,10 @@ chokidar@^3.5.3, chokidar@^3.6.0:
|
|||
optionalDependencies:
|
||||
fsevents "~2.3.2"
|
||||
|
||||
chromatic@^11.15.0, chromatic@^11.22.0:
|
||||
version "11.22.0"
|
||||
resolved "https://registry.yarnpkg.com/chromatic/-/chromatic-11.22.0.tgz#9c2b05a0c5a94c5c0cc2b6be6969c112ac4543d0"
|
||||
integrity sha512-u1kAPR9lj9aFzsCp0iWPXBbsKgcxFU7iJO6mFbgNHGVg+YPBqiJMuvgB8EQHdNbHjk5amFnGnIz/Ww8fK3t9Hw==
|
||||
chromatic@^11.15.0, chromatic@^11.25.0:
|
||||
version "11.25.0"
|
||||
resolved "https://registry.yarnpkg.com/chromatic/-/chromatic-11.25.0.tgz#64d48d8446e6a3d1312c7d5049a6e4beeea3d265"
|
||||
integrity sha512-P2BVe0rRLS9WM+eSG3u1SRg0Mi2vopsdPs2FiXwUiPqZ6hs9fe66d3Pnt7CfQ22v2jThuPEXYjYEeuL75a16Bw==
|
||||
|
||||
chrome-trace-event@^1.0.2:
|
||||
version "1.0.4"
|
||||
|
@ -5726,7 +5732,7 @@ doctrine@^3.0.0:
|
|||
dependencies:
|
||||
esutils "^2.0.2"
|
||||
|
||||
dom-accessibility-api@^0.5.9:
|
||||
dom-accessibility-api@^0.5.14, dom-accessibility-api@^0.5.9:
|
||||
version "0.5.16"
|
||||
resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz#5a7429e6066eb3664d911e33fb0e45de8eb08453"
|
||||
integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==
|
||||
|
@ -6125,12 +6131,12 @@ escape-string-regexp@^4.0.0:
|
|||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
|
||||
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
|
||||
|
||||
eslint-config-next@15.1.3:
|
||||
version "15.1.3"
|
||||
resolved "https://registry.yarnpkg.com/eslint-config-next/-/eslint-config-next-15.1.3.tgz#7656b47591745bcdbd60d396282924d89f82eea6"
|
||||
integrity sha512-wGYlNuWnh4ujuKtZvH+7B2Z2vy9nONZE6ztd+DKF7hAsIabkrxmD4TzYHzASHENo42lmz2tnT2B+zN2sOHvpJg==
|
||||
eslint-config-next@15.1.5:
|
||||
version "15.1.5"
|
||||
resolved "https://registry.yarnpkg.com/eslint-config-next/-/eslint-config-next-15.1.5.tgz#15b6cdd2469c7bb69af8d832bf6a10224d19c397"
|
||||
integrity sha512-Awm7iUJY8toOR+fU8yTxZnA7/LyOGUGOd6cENCuDfJ3gucHOSmLdOSGJ4u+nlrs8p5qXemua42bZmq+uOzxl6Q==
|
||||
dependencies:
|
||||
"@next/eslint-plugin-next" "15.1.3"
|
||||
"@next/eslint-plugin-next" "15.1.5"
|
||||
"@rushstack/eslint-patch" "^1.10.3"
|
||||
"@typescript-eslint/eslint-plugin" "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||
"@typescript-eslint/parser" "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||
|
@ -8342,6 +8348,11 @@ locate-path@^7.1.0:
|
|||
dependencies:
|
||||
p-locate "^6.0.0"
|
||||
|
||||
lodash-es@^4.17.21:
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
|
||||
integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
|
||||
|
||||
lodash.camelcase@^4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
|
||||
|
@ -8956,7 +8967,7 @@ mz@^2.7.0:
|
|||
object-assign "^4.0.1"
|
||||
thenify-all "^1.0.0"
|
||||
|
||||
nanoid@^3.3.6, nanoid@^3.3.7:
|
||||
nanoid@^3.3.6, nanoid@^3.3.8:
|
||||
version "3.3.8"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf"
|
||||
integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==
|
||||
|
@ -8976,12 +8987,12 @@ next-themes@^0.4.4:
|
|||
resolved "https://registry.yarnpkg.com/next-themes/-/next-themes-0.4.4.tgz#ce6f68a4af543821bbc4755b59c0d3ced55c2d13"
|
||||
integrity sha512-LDQ2qIOJF0VnuVrrMSMLrWGjRMkq+0mpgl6e0juCLqdJ+oo8Q84JRWT6Wh11VDQKkMMe+dVzDKLWs5n87T+PkQ==
|
||||
|
||||
next@^14.2.13:
|
||||
version "14.2.20"
|
||||
resolved "https://registry.yarnpkg.com/next/-/next-14.2.20.tgz#99b551d87ca6505ce63074904cb31a35e21dac9b"
|
||||
integrity sha512-yPvIiWsiyVYqJlSQxwmzMIReXn5HxFNq4+tlVQ812N1FbvhmE+fDpIAD7bcS2mGYQwPJ5vAsQouyme2eKsxaug==
|
||||
next@^14.2.21:
|
||||
version "14.2.23"
|
||||
resolved "https://registry.yarnpkg.com/next/-/next-14.2.23.tgz#37edc9a4d42c135fd97a4092f829e291e2e7c943"
|
||||
integrity sha512-mjN3fE6u/tynneLiEg56XnthzuYw+kD7mCujgVqioxyPqbmiotUCGJpIZGS/VaPg3ZDT1tvWxiVyRzeqJFm/kw==
|
||||
dependencies:
|
||||
"@next/env" "14.2.20"
|
||||
"@next/env" "14.2.23"
|
||||
"@swc/helpers" "0.5.5"
|
||||
busboy "1.6.0"
|
||||
caniuse-lite "^1.0.30001579"
|
||||
|
@ -8989,15 +9000,15 @@ next@^14.2.13:
|
|||
postcss "8.4.31"
|
||||
styled-jsx "5.1.1"
|
||||
optionalDependencies:
|
||||
"@next/swc-darwin-arm64" "14.2.20"
|
||||
"@next/swc-darwin-x64" "14.2.20"
|
||||
"@next/swc-linux-arm64-gnu" "14.2.20"
|
||||
"@next/swc-linux-arm64-musl" "14.2.20"
|
||||
"@next/swc-linux-x64-gnu" "14.2.20"
|
||||
"@next/swc-linux-x64-musl" "14.2.20"
|
||||
"@next/swc-win32-arm64-msvc" "14.2.20"
|
||||
"@next/swc-win32-ia32-msvc" "14.2.20"
|
||||
"@next/swc-win32-x64-msvc" "14.2.20"
|
||||
"@next/swc-darwin-arm64" "14.2.23"
|
||||
"@next/swc-darwin-x64" "14.2.23"
|
||||
"@next/swc-linux-arm64-gnu" "14.2.23"
|
||||
"@next/swc-linux-arm64-musl" "14.2.23"
|
||||
"@next/swc-linux-x64-gnu" "14.2.23"
|
||||
"@next/swc-linux-x64-musl" "14.2.23"
|
||||
"@next/swc-win32-arm64-msvc" "14.2.23"
|
||||
"@next/swc-win32-ia32-msvc" "14.2.23"
|
||||
"@next/swc-win32-x64-msvc" "14.2.23"
|
||||
|
||||
no-case@^3.0.4:
|
||||
version "3.0.4"
|
||||
|
@ -9665,11 +9676,11 @@ postcss@8.4.31:
|
|||
source-map-js "^1.0.2"
|
||||
|
||||
postcss@^8, postcss@^8.2.14, postcss@^8.4.33, postcss@^8.4.38, postcss@^8.4.47:
|
||||
version "8.4.49"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.49.tgz#4ea479048ab059ab3ae61d082190fabfd994fe19"
|
||||
integrity sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==
|
||||
version "8.5.1"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.1.tgz#e2272a1f8a807fafa413218245630b5db10a3214"
|
||||
integrity sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==
|
||||
dependencies:
|
||||
nanoid "^3.3.7"
|
||||
nanoid "^3.3.8"
|
||||
picocolors "^1.1.1"
|
||||
source-map-js "^1.2.1"
|
||||
|
||||
|
@ -9727,10 +9738,10 @@ prelude-ls@^1.2.1:
|
|||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
|
||||
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
|
||||
|
||||
prettier-plugin-tailwindcss@^0.6.9:
|
||||
version "0.6.9"
|
||||
resolved "https://registry.yarnpkg.com/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.9.tgz#db84c32918eae9b44e5a5f0aa4d1249cc39fa739"
|
||||
integrity sha512-r0i3uhaZAXYP0At5xGfJH876W3HHGHDp+LCRUJrs57PBeQ6mYHMwr25KH8NPX44F2yGTvdnH7OqCshlQx183Eg==
|
||||
prettier-plugin-tailwindcss@^0.6.10:
|
||||
version "0.6.10"
|
||||
resolved "https://registry.yarnpkg.com/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.10.tgz#ecd6303cdb89b368af4bdf0d4b3653abdd1f23bf"
|
||||
integrity sha512-ndj2WLDaMzACnr1gAYZiZZLs5ZdOeBYgOsbBmHj3nvW/6q8h8PymsXiEnKvj/9qgCCAoHyvLOisoQdIcsDvIgw==
|
||||
|
||||
prettier@^3.3.3:
|
||||
version "3.4.2"
|
||||
|
@ -10442,10 +10453,10 @@ safe-regex-test@^1.0.3, safe-regex-test@^1.1.0:
|
|||
es-errors "^1.3.0"
|
||||
is-regex "^1.2.1"
|
||||
|
||||
sass-loader@^13.2.0:
|
||||
version "13.3.3"
|
||||
resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-13.3.3.tgz#60df5e858788cffb1a3215e5b92e9cba61e7e133"
|
||||
integrity sha512-mt5YN2F1MOZr3d/wBRcZxeFgwgkH44wVc2zohO2YF6JiOMkiXe4BYRZpSu2sO1g71mo/j16txzUhsKZlqjVGzA==
|
||||
sass-loader@^14.2.1:
|
||||
version "14.2.1"
|
||||
resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-14.2.1.tgz#db9ad96b56dc1c1ea546101e76375d5b008fec70"
|
||||
integrity sha512-G0VcnMYU18a4N7VoNDegg2OuMjYtxnqzQWARVWCIVSZwJeiL9kg8QMsuIZOplsJgTzZLF6jGxI3AClj8I9nRdQ==
|
||||
dependencies:
|
||||
neo-async "^2.6.2"
|
||||
|
||||
|
@ -10756,12 +10767,12 @@ statuses@^2.0.1:
|
|||
resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"
|
||||
integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==
|
||||
|
||||
storybook@^8.4.5:
|
||||
version "8.4.7"
|
||||
resolved "https://registry.yarnpkg.com/storybook/-/storybook-8.4.7.tgz#a3068787a58074cec1b4197eed1c4427ec644b3f"
|
||||
integrity sha512-RP/nMJxiWyFc8EVMH5gp20ID032Wvk+Yr3lmKidoegto5Iy+2dVQnUoElZb2zpbVXNHWakGuAkfI0dY1Hfp/vw==
|
||||
storybook@^8.5.0:
|
||||
version "8.5.0"
|
||||
resolved "https://registry.yarnpkg.com/storybook/-/storybook-8.5.0.tgz#cb48ff743a98229f6d18e8b7017913ecb0d703fa"
|
||||
integrity sha512-cEx42OlCetManF+cONVJVYP7SYsnI2K922DfWKmZhebP0it0n6TUof4y5/XzJ8YUruwPgyclGLdX8TvdRuNSfw==
|
||||
dependencies:
|
||||
"@storybook/core" "8.4.7"
|
||||
"@storybook/core" "8.5.0"
|
||||
|
||||
stream-browserify@^3.0.0:
|
||||
version "3.0.0"
|
||||
|
@ -11363,9 +11374,9 @@ typedarray-to-buffer@^3.1.5:
|
|||
is-typedarray "^1.0.0"
|
||||
|
||||
typescript@^5:
|
||||
version "5.7.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.2.tgz#3169cf8c4c8a828cde53ba9ecb3d2b1d5dd67be6"
|
||||
integrity sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==
|
||||
version "5.7.3"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.3.tgz#919b44a7dbb8583a9b856d162be24a54bf80073e"
|
||||
integrity sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==
|
||||
|
||||
unbox-primitive@^1.0.2:
|
||||
version "1.0.2"
|
||||
|
@ -11621,6 +11632,18 @@ victory-vendor@^36.6.8:
|
|||
d3-time "^3.0.0"
|
||||
d3-timer "^3.0.1"
|
||||
|
||||
vitest-axe@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/vitest-axe/-/vitest-axe-0.1.0.tgz#34f714e5af31d579d7e2ca906d6b95207a1006ec"
|
||||
integrity sha512-jvtXxeQPg8R/2ANTY8QicA5pvvdRP4F0FsVUAHANJ46YCDASie/cuhlSzu0DGcLmZvGBSBNsNuK3HqfaeknyvA==
|
||||
dependencies:
|
||||
aria-query "^5.0.0"
|
||||
axe-core "^4.4.2"
|
||||
chalk "^5.0.1"
|
||||
dom-accessibility-api "^0.5.14"
|
||||
lodash-es "^4.17.21"
|
||||
redent "^3.0.0"
|
||||
|
||||
vm-browserify@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 81 KiB |
|
@ -45,13 +45,7 @@ Now that both Ollama and the AutoGPT platform are running we can move onto using
|
|||
2. In the "LLM Model" dropdown, select "llama3.2" (This is the model we downloaded earlier)
|
||||

|
||||
|
||||
3. You will see it ask for "Ollama Credentials", simply press "Enter API key"
|
||||

|
||||
|
||||
And you will see "Add new API key for Ollama", In the API key field you can enter anything you want as Ollama does not require an API key, I usually just enter a space, for the Name call it "Ollama" then press "Save & use this API key"
|
||||

|
||||
|
||||
4. After that you will now see the block again, add your prompts then save and then run the graph:
|
||||
3. Now we need to add some prompts then save and then run the graph:
|
||||

|
||||
|
||||
That's it! You've successfully setup the AutoGPT platform and made a LLM call to Ollama.
|
||||
|
|
Loading…
Reference in New Issue