mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-01-08 22:58:01 -05:00
feat(platform): setup and configure orval (#10209)
This pull request sets up and configures Orval for API client generation. It automates the process of creating TypeScript clients from the backend's OpenAPI specification, improving development efficiency and reducing manual code maintenance. ### Changes 🏗️ - Configures Orval with a new configuration file (`orval.config.ts`). - Adds scripts to `package.json` for fetching the OpenAPI spec and generating the API client. - Implements a custom mutator for handling authentication. - Adds API client generation as a step in the CI workflow. - Adds `.gitignore` entry for generated API client files. - Adds a security middleware to prevent caching of sensitive data. ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] Verified that the API client is generated correctly. - [x] Confirmed that the custom mutator is functioning as expected for authentication. - [x] Ensured that the new CI workflow step for API client generation is successful. - [x] Tested generated API calls #### For configuration changes: - [x] `.env.example` is updated or already compatible with my changes - [ ] `docker-compose.yml` is updated or already compatible with my changes - [x] I have included a list of my configuration changes in the PR description (under **Changes**)
This commit is contained in:
3
.github/workflows/platform-frontend-ci.yml
vendored
3
.github/workflows/platform-frontend-ci.yml
vendored
@@ -55,6 +55,9 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Generate API client
|
||||
run: pnpm generate:api-client
|
||||
|
||||
- name: Run tsc check
|
||||
run: pnpm type-check
|
||||
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -165,7 +165,7 @@ package-lock.json
|
||||
|
||||
# Allow for locally private items
|
||||
# private
|
||||
pri*
|
||||
pri*
|
||||
# ignore
|
||||
ig*
|
||||
.github_access_token
|
||||
@@ -177,3 +177,6 @@ autogpt_platform/backend/settings.py
|
||||
*.ign.*
|
||||
.test-contents
|
||||
.claude/settings.local.json
|
||||
|
||||
# Auto generated client
|
||||
autogpt_platform/frontend/src/api/__generated__
|
||||
|
||||
@@ -62,6 +62,12 @@ To run the AutoGPT Platform, follow these steps:
|
||||
pnpm i
|
||||
```
|
||||
|
||||
Generate the API client (this step is required before running the frontend):
|
||||
|
||||
```
|
||||
pnpm generate:api-client
|
||||
```
|
||||
|
||||
Then start the frontend application in development mode:
|
||||
|
||||
```
|
||||
@@ -164,3 +170,27 @@ To persist data for PostgreSQL and Redis, you can modify the `docker-compose.yml
|
||||
3. Save the file and run `docker compose up -d` to apply the changes.
|
||||
|
||||
This configuration will create named volumes for PostgreSQL and Redis, ensuring that your data persists across container restarts.
|
||||
|
||||
### API Client Generation
|
||||
|
||||
The platform includes scripts for generating and managing the API client:
|
||||
|
||||
- `pnpm fetch:openapi`: Fetches the OpenAPI specification from the backend service (requires backend to be running on port 8006)
|
||||
- `pnpm generate:api-client`: Generates the TypeScript API client from the OpenAPI specification using Orval
|
||||
- `pnpm generate:api-all`: Runs both fetch and generate commands in sequence
|
||||
|
||||
#### Manual API Client Updates
|
||||
|
||||
If you need to update the API client after making changes to the backend API:
|
||||
|
||||
1. Ensure the backend services are running:
|
||||
```
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
2. Generate the updated API client:
|
||||
```
|
||||
pnpm generate:api-all
|
||||
```
|
||||
|
||||
This will fetch the latest OpenAPI specification and regenerate the TypeScript client code.
|
||||
|
||||
@@ -191,10 +191,12 @@ app.include_router(
|
||||
backend.server.v2.library.routes.router, tags=["v2"], prefix="/api/library"
|
||||
)
|
||||
app.include_router(
|
||||
backend.server.v2.otto.routes.router, tags=["v2"], prefix="/api/otto"
|
||||
backend.server.v2.otto.routes.router, tags=["v2", "otto"], prefix="/api/otto"
|
||||
)
|
||||
app.include_router(
|
||||
backend.server.v2.turnstile.routes.router, tags=["v2"], prefix="/api/turnstile"
|
||||
backend.server.v2.turnstile.routes.router,
|
||||
tags=["v2", "turnstile"],
|
||||
prefix="/api/turnstile",
|
||||
)
|
||||
|
||||
app.include_router(
|
||||
|
||||
@@ -34,7 +34,7 @@ router = APIRouter()
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@router.post("/unsubscribe")
|
||||
@router.post("/unsubscribe", summary="One Click Email Unsubscribe")
|
||||
async def unsubscribe_via_one_click(token: Annotated[str, Query()]):
|
||||
logger.info("Received unsubscribe request from One Click Unsubscribe")
|
||||
try:
|
||||
@@ -48,7 +48,11 @@ async def unsubscribe_via_one_click(token: Annotated[str, Query()]):
|
||||
return JSONResponse(status_code=200, content={"status": "ok"})
|
||||
|
||||
|
||||
@router.post("/", dependencies=[Depends(postmark_validator.get_dependency())])
|
||||
@router.post(
|
||||
"/",
|
||||
dependencies=[Depends(postmark_validator.get_dependency())],
|
||||
summary="Handle Postmark Email Webhooks",
|
||||
)
|
||||
async def postmark_webhook_handler(
|
||||
webhook: Annotated[
|
||||
PostmarkWebhook,
|
||||
|
||||
@@ -113,14 +113,22 @@ v1_router.include_router(
|
||||
########################################################
|
||||
|
||||
|
||||
@v1_router.post("/auth/user", tags=["auth"], dependencies=[Depends(auth_middleware)])
|
||||
@v1_router.post(
|
||||
"/auth/user",
|
||||
summary="Get or create user",
|
||||
tags=["auth"],
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
)
|
||||
async def get_or_create_user_route(user_data: dict = Depends(auth_middleware)):
|
||||
user = await get_or_create_user(user_data)
|
||||
return user.model_dump()
|
||||
|
||||
|
||||
@v1_router.post(
|
||||
"/auth/user/email", tags=["auth"], dependencies=[Depends(auth_middleware)]
|
||||
"/auth/user/email",
|
||||
summary="Update user email",
|
||||
tags=["auth"],
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
)
|
||||
async def update_user_email_route(
|
||||
user_id: Annotated[str, Depends(get_user_id)], email: str = Body(...)
|
||||
@@ -132,6 +140,7 @@ async def update_user_email_route(
|
||||
|
||||
@v1_router.get(
|
||||
"/auth/user/preferences",
|
||||
summary="Get notification preferences",
|
||||
tags=["auth"],
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
)
|
||||
@@ -144,6 +153,7 @@ async def get_preferences(
|
||||
|
||||
@v1_router.post(
|
||||
"/auth/user/preferences",
|
||||
summary="Update notification preferences",
|
||||
tags=["auth"],
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
)
|
||||
@@ -161,14 +171,20 @@ async def update_preferences(
|
||||
|
||||
|
||||
@v1_router.get(
|
||||
"/onboarding", tags=["onboarding"], dependencies=[Depends(auth_middleware)]
|
||||
"/onboarding",
|
||||
summary="Get onboarding status",
|
||||
tags=["onboarding"],
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
)
|
||||
async def get_onboarding(user_id: Annotated[str, Depends(get_user_id)]):
|
||||
return await get_user_onboarding(user_id)
|
||||
|
||||
|
||||
@v1_router.patch(
|
||||
"/onboarding", tags=["onboarding"], dependencies=[Depends(auth_middleware)]
|
||||
"/onboarding",
|
||||
summary="Update onboarding progress",
|
||||
tags=["onboarding"],
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
)
|
||||
async def update_onboarding(
|
||||
user_id: Annotated[str, Depends(get_user_id)], data: UserOnboardingUpdate
|
||||
@@ -178,6 +194,7 @@ async def update_onboarding(
|
||||
|
||||
@v1_router.get(
|
||||
"/onboarding/agents",
|
||||
summary="Get recommended agents",
|
||||
tags=["onboarding"],
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
)
|
||||
@@ -189,6 +206,7 @@ async def get_onboarding_agents(
|
||||
|
||||
@v1_router.get(
|
||||
"/onboarding/enabled",
|
||||
summary="Check onboarding enabled",
|
||||
tags=["onboarding", "public"],
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
)
|
||||
@@ -201,7 +219,12 @@ async def is_onboarding_enabled():
|
||||
########################################################
|
||||
|
||||
|
||||
@v1_router.get(path="/blocks", tags=["blocks"], dependencies=[Depends(auth_middleware)])
|
||||
@v1_router.get(
|
||||
path="/blocks",
|
||||
summary="List available blocks",
|
||||
tags=["blocks"],
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
)
|
||||
def get_graph_blocks() -> Sequence[dict[Any, Any]]:
|
||||
blocks = [block() for block in get_blocks().values()]
|
||||
costs = get_block_costs()
|
||||
@@ -212,6 +235,7 @@ def get_graph_blocks() -> Sequence[dict[Any, Any]]:
|
||||
|
||||
@v1_router.post(
|
||||
path="/blocks/{block_id}/execute",
|
||||
summary="Execute graph block",
|
||||
tags=["blocks"],
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
)
|
||||
@@ -231,7 +255,12 @@ async def execute_graph_block(block_id: str, data: BlockInput) -> CompletedBlock
|
||||
########################################################
|
||||
|
||||
|
||||
@v1_router.get(path="/credits", dependencies=[Depends(auth_middleware)])
|
||||
@v1_router.get(
|
||||
path="/credits",
|
||||
tags=["credits"],
|
||||
summary="Get user credits",
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
)
|
||||
async def get_user_credits(
|
||||
user_id: Annotated[str, Depends(get_user_id)],
|
||||
) -> dict[str, int]:
|
||||
@@ -239,7 +268,10 @@ async def get_user_credits(
|
||||
|
||||
|
||||
@v1_router.post(
|
||||
path="/credits", tags=["credits"], dependencies=[Depends(auth_middleware)]
|
||||
path="/credits",
|
||||
summary="Request credit top up",
|
||||
tags=["credits"],
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
)
|
||||
async def request_top_up(
|
||||
request: RequestTopUp, user_id: Annotated[str, Depends(get_user_id)]
|
||||
@@ -252,6 +284,7 @@ async def request_top_up(
|
||||
|
||||
@v1_router.post(
|
||||
path="/credits/{transaction_key}/refund",
|
||||
summary="Refund credit transaction",
|
||||
tags=["credits"],
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
)
|
||||
@@ -264,7 +297,10 @@ async def refund_top_up(
|
||||
|
||||
|
||||
@v1_router.patch(
|
||||
path="/credits", tags=["credits"], dependencies=[Depends(auth_middleware)]
|
||||
path="/credits",
|
||||
summary="Fulfill checkout session",
|
||||
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)
|
||||
@@ -273,6 +309,7 @@ async def fulfill_checkout(user_id: Annotated[str, Depends(get_user_id)]):
|
||||
|
||||
@v1_router.post(
|
||||
path="/credits/auto-top-up",
|
||||
summary="Configure auto top up",
|
||||
tags=["credits"],
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
)
|
||||
@@ -301,6 +338,7 @@ async def configure_user_auto_top_up(
|
||||
|
||||
@v1_router.get(
|
||||
path="/credits/auto-top-up",
|
||||
summary="Get auto top up",
|
||||
tags=["credits"],
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
)
|
||||
@@ -310,7 +348,9 @@ async def get_user_auto_top_up(
|
||||
return await get_auto_top_up(user_id)
|
||||
|
||||
|
||||
@v1_router.post(path="/credits/stripe_webhook", tags=["credits"])
|
||||
@v1_router.post(
|
||||
path="/credits/stripe_webhook", summary="Handle Stripe webhooks", tags=["credits"]
|
||||
)
|
||||
async def stripe_webhook(request: Request):
|
||||
# Get the raw request body
|
||||
payload = await request.body()
|
||||
@@ -345,14 +385,24 @@ async def stripe_webhook(request: Request):
|
||||
return Response(status_code=200)
|
||||
|
||||
|
||||
@v1_router.get(path="/credits/manage", dependencies=[Depends(auth_middleware)])
|
||||
@v1_router.get(
|
||||
path="/credits/manage",
|
||||
tags=["credits"],
|
||||
summary="Manage payment methods",
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
)
|
||||
async def manage_payment_method(
|
||||
user_id: Annotated[str, Depends(get_user_id)],
|
||||
) -> dict[str, str]:
|
||||
return {"url": await _user_credit_model.create_billing_portal_session(user_id)}
|
||||
|
||||
|
||||
@v1_router.get(path="/credits/transactions", dependencies=[Depends(auth_middleware)])
|
||||
@v1_router.get(
|
||||
path="/credits/transactions",
|
||||
tags=["credits"],
|
||||
summary="Get credit history",
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
)
|
||||
async def get_credit_history(
|
||||
user_id: Annotated[str, Depends(get_user_id)],
|
||||
transaction_time: datetime | None = None,
|
||||
@@ -370,7 +420,12 @@ async def get_credit_history(
|
||||
)
|
||||
|
||||
|
||||
@v1_router.get(path="/credits/refunds", dependencies=[Depends(auth_middleware)])
|
||||
@v1_router.get(
|
||||
path="/credits/refunds",
|
||||
tags=["credits"],
|
||||
summary="Get refund requests",
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
)
|
||||
async def get_refund_requests(
|
||||
user_id: Annotated[str, Depends(get_user_id)],
|
||||
) -> list[RefundRequest]:
|
||||
@@ -386,7 +441,12 @@ class DeleteGraphResponse(TypedDict):
|
||||
version_counts: int
|
||||
|
||||
|
||||
@v1_router.get(path="/graphs", tags=["graphs"], dependencies=[Depends(auth_middleware)])
|
||||
@v1_router.get(
|
||||
path="/graphs",
|
||||
summary="List user graphs",
|
||||
tags=["graphs"],
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
)
|
||||
async def get_graphs(
|
||||
user_id: Annotated[str, Depends(get_user_id)],
|
||||
) -> Sequence[graph_db.GraphModel]:
|
||||
@@ -394,10 +454,14 @@ async def get_graphs(
|
||||
|
||||
|
||||
@v1_router.get(
|
||||
path="/graphs/{graph_id}", tags=["graphs"], dependencies=[Depends(auth_middleware)]
|
||||
path="/graphs/{graph_id}",
|
||||
summary="Get specific graph",
|
||||
tags=["graphs"],
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
)
|
||||
@v1_router.get(
|
||||
path="/graphs/{graph_id}/versions/{version}",
|
||||
summary="Get graph version",
|
||||
tags=["graphs"],
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
)
|
||||
@@ -421,6 +485,7 @@ async def get_graph(
|
||||
|
||||
@v1_router.get(
|
||||
path="/graphs/{graph_id}/versions",
|
||||
summary="Get all graph versions",
|
||||
tags=["graphs"],
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
)
|
||||
@@ -434,7 +499,10 @@ async def get_graph_all_versions(
|
||||
|
||||
|
||||
@v1_router.post(
|
||||
path="/graphs", tags=["graphs"], dependencies=[Depends(auth_middleware)]
|
||||
path="/graphs",
|
||||
summary="Create new graph",
|
||||
tags=["graphs"],
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
)
|
||||
async def create_new_graph(
|
||||
create_graph: CreateGraph,
|
||||
@@ -457,7 +525,10 @@ async def create_new_graph(
|
||||
|
||||
|
||||
@v1_router.delete(
|
||||
path="/graphs/{graph_id}", tags=["graphs"], dependencies=[Depends(auth_middleware)]
|
||||
path="/graphs/{graph_id}",
|
||||
summary="Delete graph permanently",
|
||||
tags=["graphs"],
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
)
|
||||
async def delete_graph(
|
||||
graph_id: str, user_id: Annotated[str, Depends(get_user_id)]
|
||||
@@ -469,7 +540,10 @@ async def delete_graph(
|
||||
|
||||
|
||||
@v1_router.put(
|
||||
path="/graphs/{graph_id}", tags=["graphs"], dependencies=[Depends(auth_middleware)]
|
||||
path="/graphs/{graph_id}",
|
||||
summary="Update graph version",
|
||||
tags=["graphs"],
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
)
|
||||
async def update_graph(
|
||||
graph_id: str,
|
||||
@@ -515,6 +589,7 @@ async def update_graph(
|
||||
|
||||
@v1_router.put(
|
||||
path="/graphs/{graph_id}/versions/active",
|
||||
summary="Set active graph version",
|
||||
tags=["graphs"],
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
)
|
||||
@@ -553,6 +628,7 @@ async def set_graph_active_version(
|
||||
|
||||
@v1_router.post(
|
||||
path="/graphs/{graph_id}/execute/{graph_version}",
|
||||
summary="Execute graph agent",
|
||||
tags=["graphs"],
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
)
|
||||
@@ -586,6 +662,7 @@ async def execute_graph(
|
||||
|
||||
@v1_router.post(
|
||||
path="/graphs/{graph_id}/executions/{graph_exec_id}/stop",
|
||||
summary="Stop graph execution",
|
||||
tags=["graphs"],
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
)
|
||||
@@ -613,6 +690,7 @@ async def stop_graph_run(
|
||||
|
||||
@v1_router.get(
|
||||
path="/executions",
|
||||
summary="Get all executions",
|
||||
tags=["graphs"],
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
)
|
||||
@@ -624,6 +702,7 @@ async def get_graphs_executions(
|
||||
|
||||
@v1_router.get(
|
||||
path="/graphs/{graph_id}/executions",
|
||||
summary="Get graph executions",
|
||||
tags=["graphs"],
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
)
|
||||
@@ -636,6 +715,7 @@ async def get_graph_executions(
|
||||
|
||||
@v1_router.get(
|
||||
path="/graphs/{graph_id}/executions/{graph_exec_id}",
|
||||
summary="Get execution details",
|
||||
tags=["graphs"],
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
)
|
||||
@@ -665,6 +745,7 @@ async def get_graph_execution(
|
||||
|
||||
@v1_router.delete(
|
||||
path="/executions/{graph_exec_id}",
|
||||
summary="Delete graph execution",
|
||||
tags=["graphs"],
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
status_code=HTTP_204_NO_CONTENT,
|
||||
@@ -692,6 +773,7 @@ class ScheduleCreationRequest(pydantic.BaseModel):
|
||||
|
||||
@v1_router.post(
|
||||
path="/schedules",
|
||||
summary="Create execution schedule",
|
||||
tags=["schedules"],
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
)
|
||||
@@ -719,6 +801,7 @@ async def create_schedule(
|
||||
|
||||
@v1_router.delete(
|
||||
path="/schedules/{schedule_id}",
|
||||
summary="Delete execution schedule",
|
||||
tags=["schedules"],
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
)
|
||||
@@ -732,6 +815,7 @@ async def delete_schedule(
|
||||
|
||||
@v1_router.get(
|
||||
path="/schedules",
|
||||
summary="List execution schedules",
|
||||
tags=["schedules"],
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
)
|
||||
@@ -752,6 +836,7 @@ async def get_execution_schedules(
|
||||
|
||||
@v1_router.post(
|
||||
"/api-keys",
|
||||
summary="Create new API key",
|
||||
response_model=CreateAPIKeyResponse,
|
||||
tags=["api-keys"],
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
@@ -782,6 +867,7 @@ async def create_api_key(
|
||||
|
||||
@v1_router.get(
|
||||
"/api-keys",
|
||||
summary="List user API keys",
|
||||
response_model=list[APIKeyWithoutHash] | dict[str, str],
|
||||
tags=["api-keys"],
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
@@ -802,6 +888,7 @@ async def get_api_keys(
|
||||
|
||||
@v1_router.get(
|
||||
"/api-keys/{key_id}",
|
||||
summary="Get specific API key",
|
||||
response_model=APIKeyWithoutHash,
|
||||
tags=["api-keys"],
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
@@ -825,6 +912,7 @@ async def get_api_key(
|
||||
|
||||
@v1_router.delete(
|
||||
"/api-keys/{key_id}",
|
||||
summary="Revoke API key",
|
||||
response_model=APIKeyWithoutHash,
|
||||
tags=["api-keys"],
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
@@ -853,6 +941,7 @@ async def delete_api_key(
|
||||
|
||||
@v1_router.post(
|
||||
"/api-keys/{key_id}/suspend",
|
||||
summary="Suspend API key",
|
||||
response_model=APIKeyWithoutHash,
|
||||
tags=["api-keys"],
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
@@ -878,6 +967,7 @@ async def suspend_key(
|
||||
|
||||
@v1_router.put(
|
||||
"/api-keys/{key_id}/permissions",
|
||||
summary="Update key permissions",
|
||||
response_model=APIKeyWithoutHash,
|
||||
tags=["api-keys"],
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
|
||||
@@ -22,7 +22,9 @@ router = APIRouter(
|
||||
)
|
||||
|
||||
|
||||
@router.post("/add_credits", response_model=AddUserCreditsResponse)
|
||||
@router.post(
|
||||
"/add_credits", response_model=AddUserCreditsResponse, summary="Add Credits to User"
|
||||
)
|
||||
async def add_user_credits(
|
||||
user_id: typing.Annotated[str, Body()],
|
||||
amount: typing.Annotated[int, Body()],
|
||||
@@ -49,6 +51,7 @@ async def add_user_credits(
|
||||
@router.get(
|
||||
"/users_history",
|
||||
response_model=UserHistoryResponse,
|
||||
summary="Get All Users History",
|
||||
)
|
||||
async def admin_get_all_user_history(
|
||||
admin_user: typing.Annotated[
|
||||
|
||||
@@ -19,6 +19,7 @@ router = fastapi.APIRouter(prefix="/admin", tags=["store", "admin"])
|
||||
|
||||
@router.get(
|
||||
"/listings",
|
||||
summary="Get Admin Listings History",
|
||||
response_model=backend.server.v2.store.model.StoreListingsWithVersionsResponse,
|
||||
dependencies=[fastapi.Depends(autogpt_libs.auth.depends.requires_admin_user)],
|
||||
)
|
||||
@@ -63,6 +64,7 @@ async def get_admin_listings_with_versions(
|
||||
|
||||
@router.post(
|
||||
"/submissions/{store_listing_version_id}/review",
|
||||
summary="Review Store Submission",
|
||||
response_model=backend.server.v2.store.model.StoreSubmission,
|
||||
dependencies=[fastapi.Depends(autogpt_libs.auth.depends.requires_admin_user)],
|
||||
)
|
||||
@@ -104,6 +106,7 @@ async def review_submission(
|
||||
|
||||
@router.get(
|
||||
"/submissions/download/{store_listing_version_id}",
|
||||
summary="Admin Download Agent File",
|
||||
tags=["store", "admin"],
|
||||
dependencies=[fastapi.Depends(autogpt_libs.auth.depends.requires_admin_user)],
|
||||
)
|
||||
|
||||
@@ -20,6 +20,7 @@ router = APIRouter(
|
||||
|
||||
@router.get(
|
||||
"",
|
||||
summary="List Library Agents",
|
||||
responses={
|
||||
500: {"description": "Server error", "content": {"application/json": {}}},
|
||||
},
|
||||
@@ -77,7 +78,7 @@ async def list_library_agents(
|
||||
) from e
|
||||
|
||||
|
||||
@router.get("/{library_agent_id}")
|
||||
@router.get("/{library_agent_id}", summary="Get Library Agent")
|
||||
async def get_library_agent(
|
||||
library_agent_id: str,
|
||||
user_id: str = Depends(autogpt_auth_lib.depends.get_user_id),
|
||||
@@ -87,6 +88,7 @@ async def get_library_agent(
|
||||
|
||||
@router.get(
|
||||
"/marketplace/{store_listing_version_id}",
|
||||
summary="Get Agent By Store ID",
|
||||
tags=["store, library"],
|
||||
response_model=library_model.LibraryAgent | None,
|
||||
)
|
||||
@@ -118,6 +120,7 @@ async def get_library_agent_by_store_listing_version_id(
|
||||
|
||||
@router.post(
|
||||
"",
|
||||
summary="Add Marketplace Agent",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={
|
||||
201: {"description": "Agent added successfully"},
|
||||
@@ -180,6 +183,7 @@ async def add_marketplace_agent_to_library(
|
||||
|
||||
@router.put(
|
||||
"/{library_agent_id}",
|
||||
summary="Update Library Agent",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={
|
||||
204: {"description": "Agent updated successfully"},
|
||||
@@ -232,7 +236,7 @@ async def update_library_agent(
|
||||
) from e
|
||||
|
||||
|
||||
@router.post("/{library_agent_id}/fork")
|
||||
@router.post("/{library_agent_id}/fork", summary="Fork Library Agent")
|
||||
async def fork_library_agent(
|
||||
library_agent_id: str,
|
||||
user_id: str = Depends(autogpt_auth_lib.depends.get_user_id),
|
||||
|
||||
@@ -11,7 +11,9 @@ from backend.util.exceptions import NotFoundError
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter()
|
||||
router = APIRouter(
|
||||
tags=["presets"],
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
|
||||
@@ -14,7 +14,10 @@ router = APIRouter()
|
||||
|
||||
|
||||
@router.post(
|
||||
"/ask", response_model=ApiResponse, dependencies=[Depends(auth_middleware)]
|
||||
"/ask",
|
||||
response_model=ApiResponse,
|
||||
dependencies=[Depends(auth_middleware)],
|
||||
summary="Proxy Otto Chat Request",
|
||||
)
|
||||
async def proxy_otto_request(
|
||||
request: ChatRequest, user_id: str = Depends(get_user_id)
|
||||
|
||||
@@ -29,6 +29,7 @@ router = fastapi.APIRouter()
|
||||
|
||||
@router.get(
|
||||
"/profile",
|
||||
summary="Get user profile",
|
||||
tags=["store", "private"],
|
||||
response_model=backend.server.v2.store.model.ProfileDetails,
|
||||
)
|
||||
@@ -61,6 +62,7 @@ async def get_profile(
|
||||
|
||||
@router.post(
|
||||
"/profile",
|
||||
summary="Update user profile",
|
||||
tags=["store", "private"],
|
||||
dependencies=[fastapi.Depends(autogpt_libs.auth.middleware.auth_middleware)],
|
||||
response_model=backend.server.v2.store.model.CreatorDetails,
|
||||
@@ -107,6 +109,7 @@ async def update_or_create_profile(
|
||||
|
||||
@router.get(
|
||||
"/agents",
|
||||
summary="List store agents",
|
||||
tags=["store", "public"],
|
||||
response_model=backend.server.v2.store.model.StoreAgentsResponse,
|
||||
)
|
||||
@@ -179,6 +182,7 @@ async def get_agents(
|
||||
|
||||
@router.get(
|
||||
"/agents/{username}/{agent_name}",
|
||||
summary="Get specific agent",
|
||||
tags=["store", "public"],
|
||||
response_model=backend.server.v2.store.model.StoreAgentDetails,
|
||||
)
|
||||
@@ -208,6 +212,7 @@ async def get_agent(username: str, agent_name: str):
|
||||
|
||||
@router.get(
|
||||
"/graph/{store_listing_version_id}",
|
||||
summary="Get agent graph",
|
||||
tags=["store"],
|
||||
)
|
||||
async def get_graph_meta_by_store_listing_version_id(
|
||||
@@ -232,6 +237,7 @@ async def get_graph_meta_by_store_listing_version_id(
|
||||
|
||||
@router.get(
|
||||
"/agents/{store_listing_version_id}",
|
||||
summary="Get agent by version",
|
||||
tags=["store"],
|
||||
response_model=backend.server.v2.store.model.StoreAgentDetails,
|
||||
)
|
||||
@@ -257,6 +263,7 @@ async def get_store_agent(
|
||||
|
||||
@router.post(
|
||||
"/agents/{username}/{agent_name}/review",
|
||||
summary="Create agent review",
|
||||
tags=["store"],
|
||||
dependencies=[fastapi.Depends(autogpt_libs.auth.middleware.auth_middleware)],
|
||||
response_model=backend.server.v2.store.model.StoreReview,
|
||||
@@ -308,6 +315,7 @@ async def create_review(
|
||||
|
||||
@router.get(
|
||||
"/creators",
|
||||
summary="List store creators",
|
||||
tags=["store", "public"],
|
||||
response_model=backend.server.v2.store.model.CreatorsResponse,
|
||||
)
|
||||
@@ -359,6 +367,7 @@ async def get_creators(
|
||||
|
||||
@router.get(
|
||||
"/creator/{username}",
|
||||
summary="Get creator details",
|
||||
tags=["store", "public"],
|
||||
response_model=backend.server.v2.store.model.CreatorDetails,
|
||||
)
|
||||
@@ -390,6 +399,7 @@ async def get_creator(
|
||||
############################################
|
||||
@router.get(
|
||||
"/myagents",
|
||||
summary="Get my agents",
|
||||
tags=["store", "private"],
|
||||
dependencies=[fastapi.Depends(autogpt_libs.auth.middleware.auth_middleware)],
|
||||
response_model=backend.server.v2.store.model.MyAgentsResponse,
|
||||
@@ -412,6 +422,7 @@ async def get_my_agents(
|
||||
|
||||
@router.delete(
|
||||
"/submissions/{submission_id}",
|
||||
summary="Delete store submission",
|
||||
tags=["store", "private"],
|
||||
dependencies=[fastapi.Depends(autogpt_libs.auth.middleware.auth_middleware)],
|
||||
response_model=bool,
|
||||
@@ -448,6 +459,7 @@ async def delete_submission(
|
||||
|
||||
@router.get(
|
||||
"/submissions",
|
||||
summary="List my submissions",
|
||||
tags=["store", "private"],
|
||||
dependencies=[fastapi.Depends(autogpt_libs.auth.middleware.auth_middleware)],
|
||||
response_model=backend.server.v2.store.model.StoreSubmissionsResponse,
|
||||
@@ -501,6 +513,7 @@ async def get_submissions(
|
||||
|
||||
@router.post(
|
||||
"/submissions",
|
||||
summary="Create store submission",
|
||||
tags=["store", "private"],
|
||||
dependencies=[fastapi.Depends(autogpt_libs.auth.middleware.auth_middleware)],
|
||||
response_model=backend.server.v2.store.model.StoreSubmission,
|
||||
@@ -548,6 +561,7 @@ async def create_submission(
|
||||
|
||||
@router.post(
|
||||
"/submissions/media",
|
||||
summary="Upload submission media",
|
||||
tags=["store", "private"],
|
||||
dependencies=[fastapi.Depends(autogpt_libs.auth.middleware.auth_middleware)],
|
||||
)
|
||||
@@ -585,6 +599,7 @@ async def upload_submission_media(
|
||||
|
||||
@router.post(
|
||||
"/submissions/generate_image",
|
||||
summary="Generate submission image",
|
||||
tags=["store", "private"],
|
||||
dependencies=[fastapi.Depends(autogpt_libs.auth.middleware.auth_middleware)],
|
||||
)
|
||||
@@ -646,6 +661,7 @@ async def generate_image(
|
||||
|
||||
@router.get(
|
||||
"/download/agents/{store_listing_version_id}",
|
||||
summary="Download agent file",
|
||||
tags=["store", "public"],
|
||||
)
|
||||
async def download_agent_file(
|
||||
|
||||
@@ -13,7 +13,9 @@ router = APIRouter()
|
||||
settings = Settings()
|
||||
|
||||
|
||||
@router.post("/verify", response_model=TurnstileVerifyResponse)
|
||||
@router.post(
|
||||
"/verify", response_model=TurnstileVerifyResponse, summary="Verify Turnstile Token"
|
||||
)
|
||||
async def verify_turnstile_token(
|
||||
request: TurnstileVerifyRequest,
|
||||
) -> TurnstileVerifyResponse:
|
||||
|
||||
@@ -8,6 +8,8 @@ NEXT_PUBLIC_LAUNCHDARKLY_ENABLED=false
|
||||
NEXT_PUBLIC_LAUNCHDARKLY_CLIENT_ID=
|
||||
NEXT_PUBLIC_APP_ENV=local
|
||||
|
||||
NEXT_PUBLIC_AGPT_SERVER_BASE_URL=http://localhost:8006
|
||||
|
||||
## Locale settings
|
||||
|
||||
NEXT_PUBLIC_DEFAULT_LOCALE=en
|
||||
@@ -35,4 +37,4 @@ NEXT_PUBLIC_CLOUDFLARE_TURNSTILE_SITE_KEY=
|
||||
NEXT_PUBLIC_TURNSTILE=disabled
|
||||
|
||||
# Devtools
|
||||
NEXT_PUBLIC_REACT_QUERY_DEVTOOL=true
|
||||
NEXT_PUBLIC_REACT_QUERY_DEVTOOL=true
|
||||
|
||||
59
autogpt_platform/frontend/orval.config.ts
Normal file
59
autogpt_platform/frontend/orval.config.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { defineConfig } from "orval";
|
||||
|
||||
export default defineConfig({
|
||||
autogpt_api_client: {
|
||||
input: {
|
||||
target: `./src/api/openapi.json`,
|
||||
override: {
|
||||
transformer: "./src/api/transformers/fix-tags.mjs",
|
||||
},
|
||||
},
|
||||
output: {
|
||||
workspace: "./src/api",
|
||||
target: `./__generated__/endpoints`,
|
||||
schemas: "./__generated__/models",
|
||||
mode: "tags-split",
|
||||
client: "react-query",
|
||||
httpClient: "fetch",
|
||||
indexFiles: false,
|
||||
mock: {
|
||||
type: "msw",
|
||||
delay: 1000, // artifical latency
|
||||
generateEachHttpStatus: true, // helps us test error-handling scenarios and generate mocks for all HTTP statuses
|
||||
},
|
||||
override: {
|
||||
mutator: {
|
||||
path: "./mutators/custom-mutator.ts",
|
||||
name: "customMutator",
|
||||
},
|
||||
query: {
|
||||
useQuery: true,
|
||||
useMutation: true,
|
||||
// Will add more as their use cases arise
|
||||
},
|
||||
},
|
||||
},
|
||||
hooks: {
|
||||
afterAllFilesWrite: "prettier --write",
|
||||
},
|
||||
},
|
||||
autogpt_zod_schema: {
|
||||
input: {
|
||||
target: `./src/api/openapi.json`,
|
||||
override: {
|
||||
transformer: "./src/api/transformers/fix-tags.mjs",
|
||||
},
|
||||
},
|
||||
output: {
|
||||
workspace: "./src/api",
|
||||
target: `./__generated__/zod-schema`,
|
||||
schemas: "./__generated__/models",
|
||||
mode: "tags-split",
|
||||
client: "zod",
|
||||
indexFiles: false,
|
||||
},
|
||||
hooks: {
|
||||
afterAllFilesWrite: "prettier --write",
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -5,7 +5,7 @@
|
||||
"scripts": {
|
||||
"dev": "next dev --turbo",
|
||||
"dev:test": "NODE_ENV=test && next dev --turbo",
|
||||
"build": "SKIP_STORYBOOK_TESTS=true next build",
|
||||
"build": "pnpm run generate:api-client && SKIP_STORYBOOK_TESTS=true next build",
|
||||
"start": "next start",
|
||||
"start:standalone": "cd .next/standalone && node server.js",
|
||||
"lint": "next lint && prettier --check .",
|
||||
@@ -18,7 +18,10 @@
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"build-storybook": "storybook build",
|
||||
"test-storybook": "test-storybook",
|
||||
"test-storybook:ci": "concurrently -k -s first -n \"SB,TEST\" -c \"magenta,blue\" \"pnpm run build-storybook -- --quiet && npx http-server storybook-static --port 6006 --silent\" \"wait-on tcp:6006 && pnpm run test-storybook\""
|
||||
"test-storybook:ci": "concurrently -k -s first -n \"SB,TEST\" -c \"magenta,blue\" \"pnpm run build-storybook -- --quiet && npx http-server storybook-static --port 6006 --silent\" \"wait-on tcp:6006 && pnpm run test-storybook\"",
|
||||
"fetch:openapi": "curl http://localhost:8006/openapi.json > ./src/api/openapi.json && prettier --write ./src/api/openapi.json",
|
||||
"generate:api-client": "orval --config ./orval.config.ts",
|
||||
"generate:api-all": "pnpm run fetch:openapi && pnpm run generate:api-client"
|
||||
},
|
||||
"browserslist": [
|
||||
"defaults"
|
||||
@@ -116,6 +119,7 @@
|
||||
"import-in-the-middle": "1.14.2",
|
||||
"msw": "2.10.2",
|
||||
"msw-storybook-addon": "2.0.5",
|
||||
"orval": "7.10.0",
|
||||
"postcss": "8.5.6",
|
||||
"prettier": "3.5.3",
|
||||
"prettier-plugin-tailwindcss": "0.6.12",
|
||||
|
||||
1202
autogpt_platform/frontend/pnpm-lock.yaml
generated
1202
autogpt_platform/frontend/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
81
autogpt_platform/frontend/src/api/mutators/custom-mutator.ts
Normal file
81
autogpt_platform/frontend/src/api/mutators/custom-mutator.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { getSupabaseClient } from "@/lib/supabase/getSupabaseClient";
|
||||
|
||||
const BASE_URL =
|
||||
process.env.NEXT_PUBLIC_AGPT_SERVER_BASE_URL || "http://localhost:8006";
|
||||
|
||||
const getBody = <T>(c: Response | Request): Promise<T> => {
|
||||
const contentType = c.headers.get("content-type");
|
||||
|
||||
if (contentType && contentType.includes("application/json")) {
|
||||
return c.json();
|
||||
}
|
||||
|
||||
if (contentType && contentType.includes("application/pdf")) {
|
||||
return c.blob() as Promise<T>;
|
||||
}
|
||||
|
||||
return c.text() as Promise<T>;
|
||||
};
|
||||
|
||||
const getSupabaseToken = async () => {
|
||||
const supabase = await getSupabaseClient();
|
||||
|
||||
const {
|
||||
data: { session },
|
||||
} = (await supabase?.auth.getSession()) || {
|
||||
data: { session: null },
|
||||
};
|
||||
|
||||
return session?.access_token;
|
||||
};
|
||||
|
||||
export const customMutator = async <T = any>(
|
||||
url: string,
|
||||
options: RequestInit & {
|
||||
params?: any;
|
||||
} = {},
|
||||
): Promise<T> => {
|
||||
const { params, ...requestOptions } = options;
|
||||
const method = (requestOptions.method || "GET") as
|
||||
| "GET"
|
||||
| "POST"
|
||||
| "PUT"
|
||||
| "DELETE"
|
||||
| "PATCH";
|
||||
const data = requestOptions.body;
|
||||
const headers: Record<string, string> = {
|
||||
...((requestOptions.headers as Record<string, string>) || {}),
|
||||
};
|
||||
|
||||
const token = await getSupabaseToken();
|
||||
|
||||
if (token) {
|
||||
headers["Authorization"] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
const isFormData = data instanceof FormData;
|
||||
|
||||
// Currently, only two content types are handled here: application/json and multipart/form-data
|
||||
if (!isFormData && data && !headers["Content-Type"]) {
|
||||
headers["Content-Type"] = "application/json";
|
||||
}
|
||||
|
||||
const queryString = params
|
||||
? "?" + new URLSearchParams(params).toString()
|
||||
: "";
|
||||
|
||||
const response = await fetch(`${BASE_URL}${url}${queryString}`, {
|
||||
...requestOptions,
|
||||
method,
|
||||
headers,
|
||||
body: data,
|
||||
});
|
||||
|
||||
const response_data = await getBody<T>(response);
|
||||
|
||||
return {
|
||||
status: response.status,
|
||||
response_data,
|
||||
headers: response.headers,
|
||||
} as T;
|
||||
};
|
||||
6093
autogpt_platform/frontend/src/api/openapi.json
Normal file
6093
autogpt_platform/frontend/src/api/openapi.json
Normal file
File diff suppressed because it is too large
Load Diff
57
autogpt_platform/frontend/src/api/transformers/fix-tags.mjs
Normal file
57
autogpt_platform/frontend/src/api/transformers/fix-tags.mjs
Normal file
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Transformer function for orval that fixes tags in OpenAPI spec.
|
||||
* 1. Create a set of tags so we have unique values
|
||||
* 2. Then remove public, private, v1, and v2 tags from tags array
|
||||
* 3. Then arrange remaining tags alphabetically and only keep the first one
|
||||
*
|
||||
* @param {OpenAPIObject} inputSchema
|
||||
* @return {OpenAPIObject}
|
||||
*/
|
||||
|
||||
export const tagTransformer = (inputSchema) => {
|
||||
const processedPaths = Object.entries(inputSchema.paths || {}).reduce(
|
||||
(acc, [path, pathItem]) => ({
|
||||
...acc,
|
||||
[path]: Object.entries(pathItem || {}).reduce(
|
||||
(pathItemAcc, [verb, operation]) => {
|
||||
if (typeof operation === "object" && operation !== null) {
|
||||
// 1. Create a set of tags so we have unique values
|
||||
const uniqueTags = Array.from(new Set(operation.tags || []));
|
||||
|
||||
// 2. Remove public, private, v1, and v2 tags from tags array
|
||||
const filteredTags = uniqueTags.filter(
|
||||
(tag) =>
|
||||
!["public", "private"].includes(tag.toLowerCase()) &&
|
||||
!/^v[12]$/i.test(tag),
|
||||
);
|
||||
|
||||
// 3. Arrange tags alphabetically and only keep the first one
|
||||
const sortedTags = filteredTags.sort((a, b) => a.localeCompare(b));
|
||||
const firstTag = sortedTags.length > 0 ? [sortedTags[0]] : [];
|
||||
|
||||
return {
|
||||
...pathItemAcc,
|
||||
[verb]: {
|
||||
...operation,
|
||||
tags: firstTag,
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
...pathItemAcc,
|
||||
[verb]: operation,
|
||||
};
|
||||
},
|
||||
{},
|
||||
),
|
||||
}),
|
||||
{},
|
||||
);
|
||||
|
||||
return {
|
||||
...inputSchema,
|
||||
paths: processedPaths,
|
||||
};
|
||||
};
|
||||
|
||||
export default tagTransformer;
|
||||
@@ -0,0 +1,14 @@
|
||||
import { getServerSupabase } from "@/lib/supabase/server/getServerSupabase";
|
||||
import { createBrowserClient } from "@supabase/ssr";
|
||||
|
||||
const isClient = typeof window !== "undefined";
|
||||
|
||||
export const getSupabaseClient = async () => {
|
||||
return isClient
|
||||
? createBrowserClient(
|
||||
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
||||
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
||||
{ isSingleton: true },
|
||||
)
|
||||
: await getServerSupabase();
|
||||
};
|
||||
Reference in New Issue
Block a user