mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-01-22 13:38:10 -05:00
Compare commits
38 Commits
testing-cl
...
swiftyos/s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b9c6af302 | ||
|
|
4d9bd00cc1 | ||
|
|
6cc758a895 | ||
|
|
a7c8096b5b | ||
|
|
5bd5452855 | ||
|
|
95c0335332 | ||
|
|
b9bb1df6db | ||
|
|
b83cf67780 | ||
|
|
277e2a3c66 | ||
|
|
bb1825e959 | ||
|
|
4d30483250 | ||
|
|
379b5e1021 | ||
|
|
cb3aeecb39 | ||
|
|
fbc36e3138 | ||
|
|
0cb7172167 | ||
|
|
e412a08222 | ||
|
|
a61c1addcf | ||
|
|
0ef9a2403c | ||
|
|
f8d56dce3a | ||
|
|
267b83460a | ||
|
|
444a1a9ed2 | ||
|
|
ca703fa1f0 | ||
|
|
3c3311c506 | ||
|
|
7e62ce3159 | ||
|
|
9509762a94 | ||
|
|
3eff0a8df2 | ||
|
|
d53da6f572 | ||
|
|
fdf4785ae2 | ||
|
|
f1b5128715 | ||
|
|
d26edd8dac | ||
|
|
15bc5cf228 | ||
|
|
2d3842bdd1 | ||
|
|
cae4a6e145 | ||
|
|
e71a422173 | ||
|
|
6912e3ade3 | ||
|
|
7d90376eb2 | ||
|
|
993e123f1b | ||
|
|
863a9e98ec |
@@ -197,6 +197,10 @@ SMARTLEAD_API_KEY=
|
||||
# ZeroBounce
|
||||
ZEROBOUNCE_API_KEY=
|
||||
|
||||
# Ayrshare
|
||||
AYRSHARE_API_KEY=
|
||||
AYRSHARE_JWT_KEY=
|
||||
|
||||
## ===== OPTIONAL API KEYS END ===== ##
|
||||
|
||||
# Block Error Rate Monitoring
|
||||
|
||||
2387
autogpt_platform/backend/backend/blocks/ayrshare/post.py
Normal file
2387
autogpt_platform/backend/backend/blocks/ayrshare/post.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -55,6 +55,7 @@ class BlockType(Enum):
|
||||
WEBHOOK_MANUAL = "Webhook (manual)"
|
||||
AGENT = "Agent"
|
||||
AI = "AI"
|
||||
AYRSHARE = "Ayrshare"
|
||||
|
||||
|
||||
class BlockCategory(Enum):
|
||||
|
||||
@@ -14,7 +14,6 @@ from typing import (
|
||||
Generic,
|
||||
Literal,
|
||||
Optional,
|
||||
TypedDict,
|
||||
TypeVar,
|
||||
cast,
|
||||
get_args,
|
||||
@@ -38,6 +37,7 @@ from pydantic_core import (
|
||||
ValidationError,
|
||||
core_schema,
|
||||
)
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from backend.integrations.providers import ProviderName
|
||||
from backend.util.settings import Secrets
|
||||
@@ -316,15 +316,32 @@ class OAuthState(BaseModel):
|
||||
|
||||
class UserMetadata(BaseModel):
|
||||
integration_credentials: list[Credentials] = Field(default_factory=list)
|
||||
"""⚠️ Deprecated; use `UserIntegrations.credentials` instead"""
|
||||
integration_oauth_states: list[OAuthState] = Field(default_factory=list)
|
||||
"""⚠️ Deprecated; use `UserIntegrations.oauth_states` instead"""
|
||||
|
||||
|
||||
class UserMetadataRaw(TypedDict, total=False):
|
||||
integration_credentials: list[dict]
|
||||
"""⚠️ Deprecated; use `UserIntegrations.credentials` instead"""
|
||||
integration_oauth_states: list[dict]
|
||||
"""⚠️ Deprecated; use `UserIntegrations.oauth_states` instead"""
|
||||
|
||||
|
||||
class UserIntegrations(BaseModel):
|
||||
|
||||
class ManagedCredentials(BaseModel):
|
||||
"""Integration credentials managed by us, rather than by the user"""
|
||||
|
||||
ayrshare_profile_key: Optional[SecretStr] = None
|
||||
|
||||
@field_serializer("*")
|
||||
def dump_secret_strings(value: Any, _info):
|
||||
if isinstance(value, SecretStr):
|
||||
return value.get_secret_value()
|
||||
return value
|
||||
|
||||
managed_credentials: ManagedCredentials = Field(default_factory=ManagedCredentials)
|
||||
credentials: list[Credentials] = Field(default_factory=list)
|
||||
oauth_states: list[OAuthState] = Field(default_factory=list)
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ from autogpt_libs.utils.cache import thread_cached
|
||||
from prometheus_client import Gauge, start_http_server
|
||||
|
||||
from backend.blocks.agent import AgentExecutorBlock
|
||||
from backend.blocks.ayrshare.post import AYRSHARE_BLOCK_IDS
|
||||
from backend.data import redis_client as redis
|
||||
from backend.data.block import (
|
||||
BlockData,
|
||||
@@ -182,6 +183,10 @@ async def execute_node(
|
||||
)
|
||||
extra_exec_kwargs[field_name] = credentials
|
||||
|
||||
if node_block.id in AYRSHARE_BLOCK_IDS:
|
||||
profile_key = creds_manager.store.get_ayrshare_profile_key(user_id)
|
||||
extra_exec_kwargs["profile_key"] = profile_key
|
||||
|
||||
output_size = 0
|
||||
try:
|
||||
async for output_name, output_data in node_block.execute(
|
||||
|
||||
474
autogpt_platform/backend/backend/integrations/ayrshare.py
Normal file
474
autogpt_platform/backend/backend/integrations/ayrshare.py
Normal file
@@ -0,0 +1,474 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
from enum import Enum
|
||||
from typing import Any, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from backend.util.exceptions import MissingConfigError
|
||||
from backend.util.request import Requests
|
||||
from backend.util.settings import Settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
settings = Settings()
|
||||
|
||||
|
||||
class AyrshareAPIException(Exception):
|
||||
def __init__(self, message: str, status_code: int):
|
||||
super().__init__(message)
|
||||
self.status_code = status_code
|
||||
|
||||
|
||||
class SocialPlatform(str, Enum):
|
||||
BLUESKY = "bluesky"
|
||||
FACEBOOK = "facebook"
|
||||
TWITTER = "twitter"
|
||||
LINKEDIN = "linkedin"
|
||||
INSTAGRAM = "instagram"
|
||||
YOUTUBE = "youtube"
|
||||
REDDIT = "reddit"
|
||||
TELEGRAM = "telegram"
|
||||
GOOGLE_MY_BUSINESS = "gmb"
|
||||
PINTEREST = "pinterest"
|
||||
TIKTOK = "tiktok"
|
||||
SNAPCHAT = "snapchat"
|
||||
THREADS = "threads"
|
||||
|
||||
|
||||
class EmailConfig(BaseModel):
|
||||
to: str
|
||||
subject: Optional[str] = None
|
||||
body: Optional[str] = None
|
||||
from_name: Optional[str] = None
|
||||
from_email: Optional[str] = None
|
||||
|
||||
|
||||
class JWTResponse(BaseModel):
|
||||
status: str
|
||||
title: str
|
||||
token: str
|
||||
url: str
|
||||
emailSent: Optional[bool] = None
|
||||
expiresIn: Optional[str] = None
|
||||
|
||||
|
||||
class ProfileResponse(BaseModel):
|
||||
status: str
|
||||
title: str
|
||||
refId: str
|
||||
profileKey: str
|
||||
messagingActive: Optional[bool] = None
|
||||
|
||||
|
||||
class PostResponse(BaseModel):
|
||||
status: str
|
||||
id: str
|
||||
refId: str
|
||||
profileTitle: str
|
||||
post: str
|
||||
postIds: Optional[list[dict[str, Any]]] = None
|
||||
scheduleDate: Optional[str] = None
|
||||
errors: Optional[list[str]] = None
|
||||
|
||||
|
||||
class AutoHashtag(BaseModel):
|
||||
max: Optional[int] = None
|
||||
position: Optional[str] = None
|
||||
|
||||
|
||||
class FirstComment(BaseModel):
|
||||
text: str
|
||||
platforms: Optional[list[SocialPlatform]] = None
|
||||
|
||||
|
||||
class AutoSchedule(BaseModel):
|
||||
interval: str
|
||||
platforms: Optional[list[SocialPlatform]] = None
|
||||
startDate: Optional[str] = None
|
||||
endDate: Optional[str] = None
|
||||
|
||||
|
||||
class AutoRepost(BaseModel):
|
||||
interval: str
|
||||
platforms: Optional[list[SocialPlatform]] = None
|
||||
startDate: Optional[str] = None
|
||||
endDate: Optional[str] = None
|
||||
|
||||
|
||||
class AyrshareClient:
|
||||
"""Client for the Ayrshare Social Media Post API"""
|
||||
|
||||
API_URL = "https://api.ayrshare.com/api"
|
||||
POST_ENDPOINT = f"{API_URL}/post"
|
||||
PROFILES_ENDPOINT = f"{API_URL}/profiles"
|
||||
JWT_ENDPOINT = f"{PROFILES_ENDPOINT}/generateJWT"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
custom_requests: Optional[Requests] = None,
|
||||
):
|
||||
if not settings.secrets.ayrshare_api_key:
|
||||
raise MissingConfigError("AYRSHARE_API_KEY is not configured")
|
||||
|
||||
headers: dict[str, str] = {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {settings.secrets.ayrshare_api_key}",
|
||||
}
|
||||
self.headers = headers
|
||||
|
||||
if custom_requests:
|
||||
self._requests = custom_requests
|
||||
else:
|
||||
self._requests = Requests(
|
||||
extra_headers=headers,
|
||||
trusted_origins=["https://api.ayrshare.com"],
|
||||
raise_for_status=False,
|
||||
)
|
||||
|
||||
async def generate_jwt(
|
||||
self,
|
||||
private_key: str,
|
||||
profile_key: str,
|
||||
logout: Optional[bool] = None,
|
||||
redirect: Optional[str] = None,
|
||||
allowed_social: Optional[list[SocialPlatform]] = None,
|
||||
verify: Optional[bool] = None,
|
||||
base64: Optional[bool] = None,
|
||||
expires_in: Optional[int] = None,
|
||||
email: Optional[EmailConfig] = None,
|
||||
) -> JWTResponse:
|
||||
"""
|
||||
Generate a JSON Web Token (JWT) for use with single sign on.
|
||||
|
||||
Args:
|
||||
domain: Domain of app. Must match the domain given during onboarding.
|
||||
private_key: Private Key used for encryption.
|
||||
profile_key: User Profile Key (not the API Key).
|
||||
logout: Automatically logout the current session.
|
||||
redirect: URL to redirect to when the "Done" button or logo is clicked.
|
||||
allowed_social: List of social networks to display in the linking page.
|
||||
verify: Verify that the generated token is valid (recommended for non-production).
|
||||
base64: Whether the private key is base64 encoded.
|
||||
expires_in: Token longevity in minutes (1-2880).
|
||||
email: Configuration for sending Connect Accounts email.
|
||||
|
||||
Returns:
|
||||
JWTResponse object containing the JWT token and URL.
|
||||
|
||||
Raises:
|
||||
AyrshareAPIException: If the API request fails or private key is invalid.
|
||||
"""
|
||||
payload: dict[str, Any] = {
|
||||
"domain": "id-pojeg",
|
||||
"privateKey": private_key,
|
||||
"profileKey": profile_key,
|
||||
}
|
||||
|
||||
headers = self.headers
|
||||
headers["Profile-Key"] = profile_key
|
||||
if logout is not None:
|
||||
payload["logout"] = logout
|
||||
if redirect is not None:
|
||||
payload["redirect"] = redirect
|
||||
if allowed_social is not None:
|
||||
payload["allowedSocial"] = [p.value for p in allowed_social]
|
||||
if verify is not None:
|
||||
payload["verify"] = verify
|
||||
if base64 is not None:
|
||||
payload["base64"] = base64
|
||||
if expires_in is not None:
|
||||
payload["expiresIn"] = expires_in
|
||||
if email is not None:
|
||||
payload["email"] = email.model_dump(exclude_none=True)
|
||||
|
||||
response = await self._requests.post(
|
||||
self.JWT_ENDPOINT, json=payload, headers=headers
|
||||
)
|
||||
|
||||
if not response.ok:
|
||||
try:
|
||||
error_data = response.json()
|
||||
error_message = error_data.get("message", "Unknown error")
|
||||
except json.JSONDecodeError:
|
||||
error_message = response.text()
|
||||
|
||||
raise AyrshareAPIException(
|
||||
f"Ayrshare API request failed ({response.status}): {error_message}",
|
||||
response.status,
|
||||
)
|
||||
|
||||
response_data = response.json()
|
||||
if response_data.get("status") != "success":
|
||||
raise AyrshareAPIException(
|
||||
f"Ayrshare API returned error: {response_data.get('message', 'Unknown error')}",
|
||||
response.status,
|
||||
)
|
||||
|
||||
return JWTResponse(**response_data)
|
||||
|
||||
async def create_profile(
|
||||
self,
|
||||
title: str,
|
||||
messaging_active: Optional[bool] = None,
|
||||
hide_top_header: Optional[bool] = None,
|
||||
top_header: Optional[str] = None,
|
||||
disable_social: Optional[list[SocialPlatform]] = None,
|
||||
team: Optional[bool] = None,
|
||||
email: Optional[str] = None,
|
||||
sub_header: Optional[str] = None,
|
||||
tags: Optional[list[str]] = None,
|
||||
) -> ProfileResponse:
|
||||
"""
|
||||
Create a new User Profile under your Primary Profile.
|
||||
|
||||
Args:
|
||||
title: Title of the new profile. Must be unique.
|
||||
messaging_active: Set to true to activate messaging for this user profile.
|
||||
hide_top_header: Hide the top header on the social accounts linkage page.
|
||||
top_header: Change the header on the social accounts linkage page.
|
||||
disable_social: Array of social networks that are disabled for this user's profile.
|
||||
team: Create a new user profile as a team member.
|
||||
email: Email address for team member invite (required if team is true).
|
||||
sub_header: Change the sub header on the social accounts linkage page.
|
||||
tags: Array of strings to tag user profiles.
|
||||
|
||||
Returns:
|
||||
ProfileResponse object containing the profile details and profile key.
|
||||
|
||||
Raises:
|
||||
AyrshareAPIException: If the API request fails or profile title already exists.
|
||||
"""
|
||||
payload: dict[str, Any] = {
|
||||
"title": title,
|
||||
}
|
||||
|
||||
if messaging_active is not None:
|
||||
payload["messagingActive"] = messaging_active
|
||||
if hide_top_header is not None:
|
||||
payload["hideTopHeader"] = hide_top_header
|
||||
if top_header is not None:
|
||||
payload["topHeader"] = top_header
|
||||
if disable_social is not None:
|
||||
payload["disableSocial"] = [p.value for p in disable_social]
|
||||
if team is not None:
|
||||
payload["team"] = team
|
||||
if email is not None:
|
||||
payload["email"] = email
|
||||
if sub_header is not None:
|
||||
payload["subHeader"] = sub_header
|
||||
if tags is not None:
|
||||
payload["tags"] = tags
|
||||
|
||||
response = await self._requests.post(self.PROFILES_ENDPOINT, json=payload)
|
||||
|
||||
if not response.ok:
|
||||
try:
|
||||
error_data = response.json()
|
||||
error_message = error_data.get("message", "Unknown error")
|
||||
except json.JSONDecodeError:
|
||||
error_message = response.text()
|
||||
|
||||
raise AyrshareAPIException(
|
||||
f"Ayrshare API request failed ({response.status}): {error_message}",
|
||||
response.status,
|
||||
)
|
||||
|
||||
response_data = response.json()
|
||||
if response_data.get("status") != "success":
|
||||
raise AyrshareAPIException(
|
||||
f"Ayrshare API returned error: {response_data.get('message', 'Unknown error')}",
|
||||
response.status,
|
||||
)
|
||||
|
||||
return ProfileResponse(**response_data)
|
||||
|
||||
async def create_post(
|
||||
self,
|
||||
post: str,
|
||||
platforms: list[SocialPlatform],
|
||||
*,
|
||||
media_urls: Optional[list[str]] = None,
|
||||
is_video: Optional[bool] = None,
|
||||
schedule_date: Optional[str] = None,
|
||||
first_comment: Optional[FirstComment] = None,
|
||||
disable_comments: Optional[bool] = None,
|
||||
shorten_links: Optional[bool] = None,
|
||||
auto_schedule: Optional[AutoSchedule] = None,
|
||||
auto_repost: Optional[AutoRepost] = None,
|
||||
auto_hashtag: Optional[AutoHashtag | bool] = None,
|
||||
unsplash: Optional[str] = None,
|
||||
bluesky_options: Optional[dict[str, Any]] = None,
|
||||
facebook_options: Optional[dict[str, Any]] = None,
|
||||
gmb_options: Optional[dict[str, Any]] = None,
|
||||
instagram_options: Optional[dict[str, Any]] = None,
|
||||
linkedin_options: Optional[dict[str, Any]] = None,
|
||||
pinterest_options: Optional[dict[str, Any]] = None,
|
||||
reddit_options: Optional[dict[str, Any]] = None,
|
||||
snapchat_options: Optional[dict[str, Any]] = None,
|
||||
telegram_options: Optional[dict[str, Any]] = None,
|
||||
threads_options: Optional[dict[str, Any]] = None,
|
||||
tiktok_options: Optional[dict[str, Any]] = None,
|
||||
twitter_options: Optional[dict[str, Any]] = None,
|
||||
youtube_options: Optional[dict[str, Any]] = None,
|
||||
requires_approval: Optional[bool] = None,
|
||||
random_post: Optional[bool] = None,
|
||||
random_media_url: Optional[bool] = None,
|
||||
idempotency_key: Optional[str] = None,
|
||||
notes: Optional[str] = None,
|
||||
profile_key: Optional[str] = None,
|
||||
) -> PostResponse:
|
||||
"""
|
||||
Create a post across multiple social media platforms.
|
||||
|
||||
Args:
|
||||
post: The post text to be published
|
||||
platforms: List of platforms to post to (e.g. [SocialPlatform.TWITTER, SocialPlatform.FACEBOOK])
|
||||
media_urls: Optional list of media URLs to include
|
||||
is_video: Whether the media is a video
|
||||
schedule_date: UTC datetime for scheduling (YYYY-MM-DDThh:mm:ssZ)
|
||||
first_comment: Configuration for first comment
|
||||
disable_comments: Whether to disable comments
|
||||
shorten_links: Whether to shorten links
|
||||
auto_schedule: Configuration for automatic scheduling
|
||||
auto_repost: Configuration for automatic reposting
|
||||
auto_hashtag: Configuration for automatic hashtags
|
||||
unsplash: Unsplash image configuration
|
||||
bluesky_options: Bluesky-specific options
|
||||
facebook_options: Facebook-specific options
|
||||
gmb_options: Google Business Profile options
|
||||
instagram_options: Instagram-specific options
|
||||
linkedin_options: LinkedIn-specific options
|
||||
pinterest_options: Pinterest-specific options
|
||||
reddit_options: Reddit-specific options
|
||||
snapchat_options: Snapchat-specific options
|
||||
telegram_options: Telegram-specific options
|
||||
threads_options: Threads-specific options
|
||||
tiktok_options: TikTok-specific options
|
||||
twitter_options: Twitter-specific options
|
||||
youtube_options: YouTube-specific options
|
||||
requires_approval: Whether to enable approval workflow
|
||||
random_post: Whether to generate random post text
|
||||
random_media_url: Whether to generate random media
|
||||
idempotency_key: Unique ID for the post
|
||||
notes: Additional notes for the post
|
||||
|
||||
Returns:
|
||||
PostResponse object containing the post details and status
|
||||
|
||||
Raises:
|
||||
AyrshareAPIException: If the API request fails
|
||||
"""
|
||||
|
||||
payload: dict[str, Any] = {
|
||||
"post": post,
|
||||
"platforms": [p.value for p in platforms],
|
||||
}
|
||||
|
||||
# Add optional parameters if provided
|
||||
if media_urls:
|
||||
payload["mediaUrls"] = media_urls
|
||||
if is_video is not None:
|
||||
payload["isVideo"] = is_video
|
||||
if schedule_date:
|
||||
payload["scheduleDate"] = schedule_date
|
||||
if first_comment:
|
||||
first_comment_dict = first_comment.model_dump(exclude_none=True)
|
||||
if first_comment.platforms:
|
||||
first_comment_dict["platforms"] = [
|
||||
p.value for p in first_comment.platforms
|
||||
]
|
||||
payload["firstComment"] = first_comment_dict
|
||||
if disable_comments is not None:
|
||||
payload["disableComments"] = disable_comments
|
||||
if shorten_links is not None:
|
||||
payload["shortenLinks"] = shorten_links
|
||||
if auto_schedule:
|
||||
auto_schedule_dict = auto_schedule.model_dump(exclude_none=True)
|
||||
if auto_schedule.platforms:
|
||||
auto_schedule_dict["platforms"] = [
|
||||
p.value for p in auto_schedule.platforms
|
||||
]
|
||||
payload["autoSchedule"] = auto_schedule_dict
|
||||
if auto_repost:
|
||||
auto_repost_dict = auto_repost.model_dump(exclude_none=True)
|
||||
if auto_repost.platforms:
|
||||
auto_repost_dict["platforms"] = [p.value for p in auto_repost.platforms]
|
||||
payload["autoRepost"] = auto_repost_dict
|
||||
if auto_hashtag:
|
||||
payload["autoHashtag"] = (
|
||||
auto_hashtag.model_dump(exclude_none=True)
|
||||
if isinstance(auto_hashtag, AutoHashtag)
|
||||
else auto_hashtag
|
||||
)
|
||||
if unsplash:
|
||||
payload["unsplash"] = unsplash
|
||||
if bluesky_options:
|
||||
payload["blueskyOptions"] = bluesky_options
|
||||
if facebook_options:
|
||||
payload["faceBookOptions"] = facebook_options
|
||||
if gmb_options:
|
||||
payload["gmbOptions"] = gmb_options
|
||||
if instagram_options:
|
||||
payload["instagramOptions"] = instagram_options
|
||||
if linkedin_options:
|
||||
payload["linkedInOptions"] = linkedin_options
|
||||
if pinterest_options:
|
||||
payload["pinterestOptions"] = pinterest_options
|
||||
if reddit_options:
|
||||
payload["redditOptions"] = reddit_options
|
||||
if snapchat_options:
|
||||
payload["snapchatOptions"] = snapchat_options
|
||||
if telegram_options:
|
||||
payload["telegramOptions"] = telegram_options
|
||||
if threads_options:
|
||||
payload["threadsOptions"] = threads_options
|
||||
if tiktok_options:
|
||||
payload["tikTokOptions"] = tiktok_options
|
||||
if twitter_options:
|
||||
payload["twitterOptions"] = twitter_options
|
||||
if youtube_options:
|
||||
payload["youTubeOptions"] = youtube_options
|
||||
if requires_approval is not None:
|
||||
payload["requiresApproval"] = requires_approval
|
||||
if random_post is not None:
|
||||
payload["randomPost"] = random_post
|
||||
if random_media_url is not None:
|
||||
payload["randomMediaUrl"] = random_media_url
|
||||
if idempotency_key:
|
||||
payload["idempotencyKey"] = idempotency_key
|
||||
if notes:
|
||||
payload["notes"] = notes
|
||||
|
||||
headers = self.headers
|
||||
if profile_key:
|
||||
headers["Profile-Key"] = profile_key
|
||||
|
||||
response = await self._requests.post(
|
||||
self.POST_ENDPOINT, json=payload, headers=headers
|
||||
)
|
||||
|
||||
if not response.ok:
|
||||
try:
|
||||
error_data = response.json()
|
||||
error_message = error_data.get("message", "Unknown error")
|
||||
except json.JSONDecodeError:
|
||||
error_message = response.text()
|
||||
|
||||
raise AyrshareAPIException(
|
||||
f"Ayrshare API request failed ({response.status}): {error_message}",
|
||||
response.status,
|
||||
)
|
||||
|
||||
response_data = response.json()
|
||||
if response_data.get("status") != "success":
|
||||
raise AyrshareAPIException(
|
||||
f"Ayrshare API returned error: {response_data.get('message', 'Unknown error')}",
|
||||
response.status,
|
||||
)
|
||||
|
||||
# Return the first post from the response
|
||||
# This is because Ayrshare returns an array of posts even for single posts
|
||||
return PostResponse(**response_data["posts"][0])
|
||||
@@ -1,6 +1,7 @@
|
||||
import base64
|
||||
import hashlib
|
||||
import secrets
|
||||
from contextlib import asynccontextmanager
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Optional
|
||||
|
||||
@@ -182,6 +183,7 @@ zerobounce_credentials = APIKeyCredentials(
|
||||
expires_at=None,
|
||||
)
|
||||
|
||||
|
||||
llama_api_credentials = APIKeyCredentials(
|
||||
id="d44045af-1c33-4833-9e19-752313214de2",
|
||||
provider="llama_api",
|
||||
@@ -240,6 +242,7 @@ class IntegrationCredentialsStore:
|
||||
|
||||
return get_service_client(DatabaseManagerAsyncClient)
|
||||
|
||||
# =============== USER-MANAGED CREDENTIALS =============== #
|
||||
async def add_creds(self, user_id: str, credentials: Credentials) -> None:
|
||||
async with await self.locked_user_integrations(user_id):
|
||||
if await self.get_creds_by_id(user_id, credentials.id):
|
||||
@@ -359,6 +362,19 @@ class IntegrationCredentialsStore:
|
||||
]
|
||||
await self._set_user_integration_creds(user_id, filtered_credentials)
|
||||
|
||||
# ============== SYSTEM-MANAGED CREDENTIALS ============== #
|
||||
|
||||
async def get_ayrshare_profile_key(self, user_id: str) -> SecretStr | None:
|
||||
user_integrations = await self._get_user_integrations(user_id)
|
||||
return user_integrations.managed_credentials.ayrshare_profile_key
|
||||
|
||||
async def set_ayrshare_profile_key(self, user_id: str, profile_key: str) -> None:
|
||||
_profile_key = SecretStr(profile_key)
|
||||
async with self.edit_user_integrations(user_id) as user_integrations:
|
||||
user_integrations.managed_credentials.ayrshare_profile_key = _profile_key
|
||||
|
||||
# ===================== OAUTH STATES ===================== #
|
||||
|
||||
async def store_state_token(
|
||||
self, user_id: str, provider: str, scopes: list[str], use_pkce: bool = False
|
||||
) -> tuple[str, str]:
|
||||
@@ -375,6 +391,9 @@ class IntegrationCredentialsStore:
|
||||
scopes=scopes,
|
||||
)
|
||||
|
||||
async with self.edit_user_integrations(user_id) as user_integrations:
|
||||
user_integrations.oauth_states.append(state)
|
||||
|
||||
async with await self.locked_user_integrations(user_id):
|
||||
|
||||
user_integrations = await self._get_user_integrations(user_id)
|
||||
@@ -428,6 +447,17 @@ class IntegrationCredentialsStore:
|
||||
|
||||
return None
|
||||
|
||||
# =================== GET/SET HELPERS =================== #
|
||||
|
||||
@asynccontextmanager
|
||||
async def edit_user_integrations(self, user_id: str):
|
||||
async with await self.locked_user_integrations(user_id):
|
||||
user_integrations = await self._get_user_integrations(user_id)
|
||||
yield user_integrations # yield to allow edits
|
||||
await self.db_manager.update_user_integrations(
|
||||
user_id=user_id, data=user_integrations
|
||||
)
|
||||
|
||||
async def _set_user_integration_creds(
|
||||
self, user_id: str, credentials: list[Credentials]
|
||||
) -> None:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import asyncio
|
||||
import logging
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import TYPE_CHECKING, Annotated, Awaitable, List, Literal
|
||||
|
||||
from fastapi import (
|
||||
@@ -12,7 +13,8 @@ from fastapi import (
|
||||
Request,
|
||||
status,
|
||||
)
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import BaseModel, Field, SecretStr
|
||||
from starlette.status import HTTP_500_INTERNAL_SERVER_ERROR, HTTP_502_BAD_GATEWAY
|
||||
|
||||
from backend.data.graph import get_graph, set_node_webhook
|
||||
from backend.data.integrations import (
|
||||
@@ -29,6 +31,7 @@ from backend.data.model import (
|
||||
OAuth2Credentials,
|
||||
)
|
||||
from backend.executor.utils import add_graph_execution
|
||||
from backend.integrations.ayrshare import AyrshareClient, SocialPlatform
|
||||
from backend.integrations.creds_manager import IntegrationCredentialsManager
|
||||
from backend.integrations.oauth import CREDENTIALS_BY_PROVIDER, HANDLERS_BY_NAME
|
||||
from backend.integrations.providers import ProviderName
|
||||
@@ -39,7 +42,7 @@ from backend.server.integrations.models import (
|
||||
get_all_provider_names,
|
||||
)
|
||||
from backend.server.v2.library.db import set_preset_webhook, update_preset
|
||||
from backend.util.exceptions import NeedConfirmation, NotFoundError
|
||||
from backend.util.exceptions import MissingConfigError, NeedConfirmation, NotFoundError
|
||||
from backend.util.settings import Settings
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -271,6 +274,11 @@ class CredentialsDeletionNeedsConfirmationResponse(BaseModel):
|
||||
message: str
|
||||
|
||||
|
||||
class AyrshareSSOResponse(BaseModel):
|
||||
sso_url: str = Field(..., description="The SSO URL for Ayrshare integration")
|
||||
expires_at: str = Field(..., description="ISO timestamp when the URL expires")
|
||||
|
||||
|
||||
@router.delete("/{provider}/credentials/{cred_id}")
|
||||
async def delete_credentials(
|
||||
request: Request,
|
||||
@@ -548,9 +556,90 @@ def _get_provider_oauth_handler(
|
||||
)
|
||||
|
||||
|
||||
@router.get("/ayrshare/sso_url")
|
||||
async def get_ayrshare_sso_url(
|
||||
user_id: Annotated[str, Depends(get_user_id)],
|
||||
) -> AyrshareSSOResponse:
|
||||
"""
|
||||
Generate an SSO URL for Ayrshare social media integration.
|
||||
|
||||
Returns:
|
||||
dict: Contains the SSO URL for Ayrshare integration
|
||||
"""
|
||||
# Generate JWT and get SSO URL
|
||||
try:
|
||||
client = AyrshareClient()
|
||||
except MissingConfigError:
|
||||
raise HTTPException(
|
||||
status_code=HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Ayrshare integration is not configured",
|
||||
)
|
||||
|
||||
# Get or create profile key
|
||||
profile_key = await creds_manager.store.get_ayrshare_profile_key(user_id)
|
||||
if not profile_key:
|
||||
logger.debug(f"Creating new Ayrshare profile for user {user_id}")
|
||||
# Create new profile if none exists
|
||||
try:
|
||||
profile = await client.create_profile(
|
||||
title=f"User {user_id}", messaging_active=True
|
||||
)
|
||||
profile_key = profile.profileKey
|
||||
await creds_manager.store.set_ayrshare_profile_key(user_id, profile_key)
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating Ayrshare profile for user {user_id}: {e}")
|
||||
raise HTTPException(
|
||||
status_code=HTTP_502_BAD_GATEWAY,
|
||||
detail="Failed to create Ayrshare profile",
|
||||
)
|
||||
else:
|
||||
logger.debug(f"Using existing Ayrshare profile for user {user_id}")
|
||||
|
||||
# Convert SecretStr to string if needed
|
||||
profile_key_str = (
|
||||
profile_key.get_secret_value()
|
||||
if isinstance(profile_key, SecretStr)
|
||||
else str(profile_key)
|
||||
)
|
||||
|
||||
private_key = settings.secrets.ayrshare_jwt_key
|
||||
expiry_minutes = 2880
|
||||
try:
|
||||
logger.debug(f"Generating Ayrshare JWT for user {user_id}")
|
||||
jwt_response = await client.generate_jwt(
|
||||
private_key=private_key,
|
||||
profile_key=profile_key_str,
|
||||
allowed_social=[
|
||||
SocialPlatform.FACEBOOK,
|
||||
SocialPlatform.TWITTER,
|
||||
SocialPlatform.LINKEDIN,
|
||||
SocialPlatform.INSTAGRAM,
|
||||
SocialPlatform.YOUTUBE,
|
||||
SocialPlatform.REDDIT,
|
||||
SocialPlatform.TELEGRAM,
|
||||
SocialPlatform.GOOGLE_MY_BUSINESS,
|
||||
SocialPlatform.PINTEREST,
|
||||
SocialPlatform.TIKTOK,
|
||||
SocialPlatform.BLUESKY,
|
||||
SocialPlatform.SNAPCHAT,
|
||||
SocialPlatform.THREADS,
|
||||
],
|
||||
expires_in=expiry_minutes,
|
||||
verify=True,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error generating Ayrshare JWT for user {user_id}: {e}")
|
||||
raise HTTPException(
|
||||
status_code=HTTP_502_BAD_GATEWAY, detail="Failed to generate JWT"
|
||||
)
|
||||
|
||||
expires_at = datetime.now(timezone.utc) + timedelta(minutes=expiry_minutes)
|
||||
return AyrshareSSOResponse(
|
||||
sso_url=jwt_response.url, expires_at=expires_at.isoformat()
|
||||
)
|
||||
|
||||
|
||||
# === PROVIDER DISCOVERY ENDPOINTS ===
|
||||
|
||||
|
||||
@router.get("/providers", response_model=List[str])
|
||||
async def list_providers() -> List[str]:
|
||||
"""
|
||||
|
||||
@@ -495,7 +495,8 @@ class Secrets(UpdateTrackingModel["Secrets"], BaseSettings):
|
||||
apollo_api_key: str = Field(default="", description="Apollo API Key")
|
||||
smartlead_api_key: str = Field(default="", description="SmartLead API Key")
|
||||
zerobounce_api_key: str = Field(default="", description="ZeroBounce API Key")
|
||||
|
||||
ayrshare_api_key: str = Field(default="", description="Ayrshare API Key")
|
||||
ayrshare_jwt_key: str = Field(default="", description="Ayrshare private Key")
|
||||
# Add more secret fields as needed
|
||||
|
||||
model_config = SettingsConfigDict(
|
||||
|
||||
184
autogpt_platform/frontend/src/app/api/__generated__/endpoints/files/files.ts
generated
Normal file
184
autogpt_platform/frontend/src/app/api/__generated__/endpoints/files/files.ts
generated
Normal file
@@ -0,0 +1,184 @@
|
||||
/**
|
||||
* Generated by orval v7.10.0 🍺
|
||||
* Do not edit manually.
|
||||
* AutoGPT Agent Server
|
||||
* This server is used to execute agents that are created by the AutoGPT system.
|
||||
* OpenAPI spec version: 0.1
|
||||
*/
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import type {
|
||||
MutationFunction,
|
||||
QueryClient,
|
||||
UseMutationOptions,
|
||||
UseMutationResult,
|
||||
} from "@tanstack/react-query";
|
||||
|
||||
import type { BodyPostV1UploadFileToCloudStorage } from "../../models/bodyPostV1UploadFileToCloudStorage";
|
||||
|
||||
import type { HTTPValidationError } from "../../models/hTTPValidationError";
|
||||
|
||||
import type { PostV1UploadFileToCloudStorageParams } from "../../models/postV1UploadFileToCloudStorageParams";
|
||||
|
||||
import type { UploadFileResponse } from "../../models/uploadFileResponse";
|
||||
|
||||
import { customMutator } from "../../../mutators/custom-mutator";
|
||||
|
||||
type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
|
||||
|
||||
/**
|
||||
* Upload a file to cloud storage and return a storage key that can be used
|
||||
with FileStoreBlock and AgentFileInputBlock.
|
||||
|
||||
Args:
|
||||
file: The file to upload
|
||||
user_id: The user ID
|
||||
provider: Cloud storage provider ("gcs", "s3", "azure")
|
||||
expiration_hours: Hours until file expires (1-48)
|
||||
|
||||
Returns:
|
||||
Dict containing the cloud storage path and signed URL
|
||||
* @summary Upload file to cloud storage
|
||||
*/
|
||||
export type postV1UploadFileToCloudStorageResponse200 = {
|
||||
data: UploadFileResponse;
|
||||
status: 200;
|
||||
};
|
||||
|
||||
export type postV1UploadFileToCloudStorageResponse422 = {
|
||||
data: HTTPValidationError;
|
||||
status: 422;
|
||||
};
|
||||
|
||||
export type postV1UploadFileToCloudStorageResponseComposite =
|
||||
| postV1UploadFileToCloudStorageResponse200
|
||||
| postV1UploadFileToCloudStorageResponse422;
|
||||
|
||||
export type postV1UploadFileToCloudStorageResponse =
|
||||
postV1UploadFileToCloudStorageResponseComposite & {
|
||||
headers: Headers;
|
||||
};
|
||||
|
||||
export const getPostV1UploadFileToCloudStorageUrl = (
|
||||
params?: PostV1UploadFileToCloudStorageParams,
|
||||
) => {
|
||||
const normalizedParams = new URLSearchParams();
|
||||
|
||||
Object.entries(params || {}).forEach(([key, value]) => {
|
||||
if (value !== undefined) {
|
||||
normalizedParams.append(key, value === null ? "null" : value.toString());
|
||||
}
|
||||
});
|
||||
|
||||
const stringifiedParams = normalizedParams.toString();
|
||||
|
||||
return stringifiedParams.length > 0
|
||||
? `/api/files/upload?${stringifiedParams}`
|
||||
: `/api/files/upload`;
|
||||
};
|
||||
|
||||
export const postV1UploadFileToCloudStorage = async (
|
||||
bodyPostV1UploadFileToCloudStorage: BodyPostV1UploadFileToCloudStorage,
|
||||
params?: PostV1UploadFileToCloudStorageParams,
|
||||
options?: RequestInit,
|
||||
): Promise<postV1UploadFileToCloudStorageResponse> => {
|
||||
const formData = new FormData();
|
||||
formData.append(`file`, bodyPostV1UploadFileToCloudStorage.file);
|
||||
|
||||
return customMutator<postV1UploadFileToCloudStorageResponse>(
|
||||
getPostV1UploadFileToCloudStorageUrl(params),
|
||||
{
|
||||
...options,
|
||||
method: "POST",
|
||||
body: formData,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
export const getPostV1UploadFileToCloudStorageMutationOptions = <
|
||||
TError = HTTPValidationError,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof postV1UploadFileToCloudStorage>>,
|
||||
TError,
|
||||
{
|
||||
data: BodyPostV1UploadFileToCloudStorage;
|
||||
params?: PostV1UploadFileToCloudStorageParams;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customMutator>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof postV1UploadFileToCloudStorage>>,
|
||||
TError,
|
||||
{
|
||||
data: BodyPostV1UploadFileToCloudStorage;
|
||||
params?: PostV1UploadFileToCloudStorageParams;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ["postV1UploadFileToCloudStorage"];
|
||||
const { mutation: mutationOptions, request: requestOptions } = options
|
||||
? options.mutation &&
|
||||
"mutationKey" in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey }, request: undefined };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof postV1UploadFileToCloudStorage>>,
|
||||
{
|
||||
data: BodyPostV1UploadFileToCloudStorage;
|
||||
params?: PostV1UploadFileToCloudStorageParams;
|
||||
}
|
||||
> = (props) => {
|
||||
const { data, params } = props ?? {};
|
||||
|
||||
return postV1UploadFileToCloudStorage(data, params, requestOptions);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type PostV1UploadFileToCloudStorageMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof postV1UploadFileToCloudStorage>>
|
||||
>;
|
||||
export type PostV1UploadFileToCloudStorageMutationBody =
|
||||
BodyPostV1UploadFileToCloudStorage;
|
||||
export type PostV1UploadFileToCloudStorageMutationError = HTTPValidationError;
|
||||
|
||||
/**
|
||||
* @summary Upload file to cloud storage
|
||||
*/
|
||||
export const usePostV1UploadFileToCloudStorage = <
|
||||
TError = HTTPValidationError,
|
||||
TContext = unknown,
|
||||
>(
|
||||
options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof postV1UploadFileToCloudStorage>>,
|
||||
TError,
|
||||
{
|
||||
data: BodyPostV1UploadFileToCloudStorage;
|
||||
params?: PostV1UploadFileToCloudStorageParams;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customMutator>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseMutationResult<
|
||||
Awaited<ReturnType<typeof postV1UploadFileToCloudStorage>>,
|
||||
TError,
|
||||
{
|
||||
data: BodyPostV1UploadFileToCloudStorage;
|
||||
params?: PostV1UploadFileToCloudStorageParams;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions =
|
||||
getPostV1UploadFileToCloudStorageMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions, queryClient);
|
||||
};
|
||||
@@ -21,6 +21,8 @@ import type {
|
||||
UseQueryResult,
|
||||
} from "@tanstack/react-query";
|
||||
|
||||
import type { AyrshareSSOResponse } from "../../models/ayrshareSSOResponse";
|
||||
|
||||
import type { BodyPostV1Callback } from "../../models/bodyPostV1Callback";
|
||||
|
||||
import type { CredentialsMetaResponse } from "../../models/credentialsMetaResponse";
|
||||
@@ -1530,6 +1532,210 @@ export const usePostV1WebhookPing = <
|
||||
|
||||
return useMutation(mutationOptions, queryClient);
|
||||
};
|
||||
/**
|
||||
* Generate an SSO URL for Ayrshare social media integration.
|
||||
|
||||
Returns:
|
||||
dict: Contains the SSO URL for Ayrshare integration
|
||||
* @summary Get Ayrshare Sso Url
|
||||
*/
|
||||
export type getV1GetAyrshareSsoUrlResponse200 = {
|
||||
data: AyrshareSSOResponse;
|
||||
status: 200;
|
||||
};
|
||||
|
||||
export type getV1GetAyrshareSsoUrlResponseComposite =
|
||||
getV1GetAyrshareSsoUrlResponse200;
|
||||
|
||||
export type getV1GetAyrshareSsoUrlResponse =
|
||||
getV1GetAyrshareSsoUrlResponseComposite & {
|
||||
headers: Headers;
|
||||
};
|
||||
|
||||
export const getGetV1GetAyrshareSsoUrlUrl = () => {
|
||||
return `/api/integrations/ayrshare/sso_url`;
|
||||
};
|
||||
|
||||
export const getV1GetAyrshareSsoUrl = async (
|
||||
options?: RequestInit,
|
||||
): Promise<getV1GetAyrshareSsoUrlResponse> => {
|
||||
return customMutator<getV1GetAyrshareSsoUrlResponse>(
|
||||
getGetV1GetAyrshareSsoUrlUrl(),
|
||||
{
|
||||
...options,
|
||||
method: "GET",
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
export const getGetV1GetAyrshareSsoUrlQueryKey = () => {
|
||||
return [`/api/integrations/ayrshare/sso_url`] as const;
|
||||
};
|
||||
|
||||
export const getGetV1GetAyrshareSsoUrlQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getV1GetAyrshareSsoUrl>>,
|
||||
TError = unknown,
|
||||
>(options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getV1GetAyrshareSsoUrl>>,
|
||||
TError,
|
||||
TData
|
||||
>
|
||||
>;
|
||||
request?: SecondParameter<typeof customMutator>;
|
||||
}) => {
|
||||
const { query: queryOptions, request: requestOptions } = options ?? {};
|
||||
|
||||
const queryKey =
|
||||
queryOptions?.queryKey ?? getGetV1GetAyrshareSsoUrlQueryKey();
|
||||
|
||||
const queryFn: QueryFunction<
|
||||
Awaited<ReturnType<typeof getV1GetAyrshareSsoUrl>>
|
||||
> = ({ signal }) => getV1GetAyrshareSsoUrl({ signal, ...requestOptions });
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getV1GetAyrshareSsoUrl>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||
};
|
||||
|
||||
export type GetV1GetAyrshareSsoUrlQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof getV1GetAyrshareSsoUrl>>
|
||||
>;
|
||||
export type GetV1GetAyrshareSsoUrlQueryError = unknown;
|
||||
|
||||
export function useGetV1GetAyrshareSsoUrl<
|
||||
TData = Awaited<ReturnType<typeof getV1GetAyrshareSsoUrl>>,
|
||||
TError = unknown,
|
||||
>(
|
||||
options: {
|
||||
query: Partial<
|
||||
UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getV1GetAyrshareSsoUrl>>,
|
||||
TError,
|
||||
TData
|
||||
>
|
||||
> &
|
||||
Pick<
|
||||
DefinedInitialDataOptions<
|
||||
Awaited<ReturnType<typeof getV1GetAyrshareSsoUrl>>,
|
||||
TError,
|
||||
Awaited<ReturnType<typeof getV1GetAyrshareSsoUrl>>
|
||||
>,
|
||||
"initialData"
|
||||
>;
|
||||
request?: SecondParameter<typeof customMutator>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): DefinedUseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
export function useGetV1GetAyrshareSsoUrl<
|
||||
TData = Awaited<ReturnType<typeof getV1GetAyrshareSsoUrl>>,
|
||||
TError = unknown,
|
||||
>(
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getV1GetAyrshareSsoUrl>>,
|
||||
TError,
|
||||
TData
|
||||
>
|
||||
> &
|
||||
Pick<
|
||||
UndefinedInitialDataOptions<
|
||||
Awaited<ReturnType<typeof getV1GetAyrshareSsoUrl>>,
|
||||
TError,
|
||||
Awaited<ReturnType<typeof getV1GetAyrshareSsoUrl>>
|
||||
>,
|
||||
"initialData"
|
||||
>;
|
||||
request?: SecondParameter<typeof customMutator>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
export function useGetV1GetAyrshareSsoUrl<
|
||||
TData = Awaited<ReturnType<typeof getV1GetAyrshareSsoUrl>>,
|
||||
TError = unknown,
|
||||
>(
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getV1GetAyrshareSsoUrl>>,
|
||||
TError,
|
||||
TData
|
||||
>
|
||||
>;
|
||||
request?: SecondParameter<typeof customMutator>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
/**
|
||||
* @summary Get Ayrshare Sso Url
|
||||
*/
|
||||
|
||||
export function useGetV1GetAyrshareSsoUrl<
|
||||
TData = Awaited<ReturnType<typeof getV1GetAyrshareSsoUrl>>,
|
||||
TError = unknown,
|
||||
>(
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getV1GetAyrshareSsoUrl>>,
|
||||
TError,
|
||||
TData
|
||||
>
|
||||
>;
|
||||
request?: SecondParameter<typeof customMutator>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
} {
|
||||
const queryOptions = getGetV1GetAyrshareSsoUrlQueryOptions(options);
|
||||
|
||||
const query = useQuery(queryOptions, queryClient) as UseQueryResult<
|
||||
TData,
|
||||
TError
|
||||
> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||
|
||||
query.queryKey = queryOptions.queryKey;
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get Ayrshare Sso Url
|
||||
*/
|
||||
export const prefetchGetV1GetAyrshareSsoUrlQuery = async <
|
||||
TData = Awaited<ReturnType<typeof getV1GetAyrshareSsoUrl>>,
|
||||
TError = unknown,
|
||||
>(
|
||||
queryClient: QueryClient,
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getV1GetAyrshareSsoUrl>>,
|
||||
TError,
|
||||
TData
|
||||
>
|
||||
>;
|
||||
request?: SecondParameter<typeof customMutator>;
|
||||
},
|
||||
): Promise<QueryClient> => {
|
||||
const queryOptions = getGetV1GetAyrshareSsoUrlQueryOptions(options);
|
||||
|
||||
await queryClient.prefetchQuery(queryOptions);
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a list of all available provider names.
|
||||
|
||||
|
||||
14
autogpt_platform/frontend/src/app/api/__generated__/models/ayrshareSSOResponse.ts
generated
Normal file
14
autogpt_platform/frontend/src/app/api/__generated__/models/ayrshareSSOResponse.ts
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Generated by orval v7.10.0 🍺
|
||||
* Do not edit manually.
|
||||
* AutoGPT Agent Server
|
||||
* This server is used to execute agents that are created by the AutoGPT system.
|
||||
* OpenAPI spec version: 0.1
|
||||
*/
|
||||
|
||||
export interface AyrshareSSOResponse {
|
||||
/** The SSO URL for Ayrshare integration */
|
||||
sso_url: string;
|
||||
/** ISO timestamp when the URL expires */
|
||||
expires_at: string;
|
||||
}
|
||||
11
autogpt_platform/frontend/src/app/api/__generated__/models/bodyPostV1UploadFileToCloudStorage.ts
generated
Normal file
11
autogpt_platform/frontend/src/app/api/__generated__/models/bodyPostV1UploadFileToCloudStorage.ts
generated
Normal file
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Generated by orval v7.10.0 🍺
|
||||
* Do not edit manually.
|
||||
* AutoGPT Agent Server
|
||||
* This server is used to execute agents that are created by the AutoGPT system.
|
||||
* OpenAPI spec version: 0.1
|
||||
*/
|
||||
|
||||
export interface BodyPostV1UploadFileToCloudStorage {
|
||||
file: Blob;
|
||||
}
|
||||
12
autogpt_platform/frontend/src/app/api/__generated__/models/postV1UploadFileToCloudStorageParams.ts
generated
Normal file
12
autogpt_platform/frontend/src/app/api/__generated__/models/postV1UploadFileToCloudStorageParams.ts
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Generated by orval v7.10.0 🍺
|
||||
* Do not edit manually.
|
||||
* AutoGPT Agent Server
|
||||
* This server is used to execute agents that are created by the AutoGPT system.
|
||||
* OpenAPI spec version: 0.1
|
||||
*/
|
||||
|
||||
export type PostV1UploadFileToCloudStorageParams = {
|
||||
provider?: string;
|
||||
expiration_hours?: number;
|
||||
};
|
||||
15
autogpt_platform/frontend/src/app/api/__generated__/models/uploadFileResponse.ts
generated
Normal file
15
autogpt_platform/frontend/src/app/api/__generated__/models/uploadFileResponse.ts
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Generated by orval v7.10.0 🍺
|
||||
* Do not edit manually.
|
||||
* AutoGPT Agent Server
|
||||
* This server is used to execute agents that are created by the AutoGPT system.
|
||||
* OpenAPI spec version: 0.1
|
||||
*/
|
||||
|
||||
export interface UploadFileResponse {
|
||||
file_uri: string;
|
||||
file_name: string;
|
||||
size: number;
|
||||
content_type: string;
|
||||
expires_in_hours: number;
|
||||
}
|
||||
@@ -443,6 +443,24 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/integrations/ayrshare/sso_url": {
|
||||
"get": {
|
||||
"tags": ["v1", "integrations"],
|
||||
"summary": "Get Ayrshare Sso Url",
|
||||
"description": "Generate an SSO URL for Ayrshare social media integration.\n\nReturns:\n dict: Contains the SSO URL for Ayrshare integration",
|
||||
"operationId": "getV1GetAyrshareSsoUrl",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/AyrshareSSOResponse" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/integrations/providers": {
|
||||
"get": {
|
||||
"tags": ["v1", "integrations"],
|
||||
@@ -823,6 +841,64 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/files/upload": {
|
||||
"post": {
|
||||
"tags": ["v1", "files"],
|
||||
"summary": "Upload file to cloud storage",
|
||||
"description": "Upload a file to cloud storage and return a storage key that can be used\nwith FileStoreBlock and AgentFileInputBlock.\n\nArgs:\n file: The file to upload\n user_id: The user ID\n provider: Cloud storage provider (\"gcs\", \"s3\", \"azure\")\n expiration_hours: Hours until file expires (1-48)\n\nReturns:\n Dict containing the cloud storage path and signed URL",
|
||||
"operationId": "postV1Upload file to cloud storage",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "provider",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"default": "gcs",
|
||||
"title": "Provider"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "expiration_hours",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"default": 24,
|
||||
"title": "Expiration Hours"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"multipart/form-data": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Body_postV1Upload_file_to_cloud_storage"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/UploadFileResponse" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/HTTPValidationError" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/credits": {
|
||||
"get": {
|
||||
"tags": ["v1", "credits"],
|
||||
@@ -3790,6 +3866,23 @@
|
||||
"required": ["amount", "threshold"],
|
||||
"title": "AutoTopUpConfig"
|
||||
},
|
||||
"AyrshareSSOResponse": {
|
||||
"properties": {
|
||||
"sso_url": {
|
||||
"type": "string",
|
||||
"title": "Sso Url",
|
||||
"description": "The SSO URL for Ayrshare integration"
|
||||
},
|
||||
"expires_at": {
|
||||
"type": "string",
|
||||
"title": "Expires At",
|
||||
"description": "ISO timestamp when the URL expires"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": ["sso_url", "expires_at"],
|
||||
"title": "AyrshareSSOResponse"
|
||||
},
|
||||
"BaseGraph-Input": {
|
||||
"properties": {
|
||||
"id": { "type": "string", "title": "Id" },
|
||||
@@ -3934,6 +4027,14 @@
|
||||
"required": ["type", "data", "data_index"],
|
||||
"title": "Body_postV1LogRawAnalytics"
|
||||
},
|
||||
"Body_postV1Upload_file_to_cloud_storage": {
|
||||
"properties": {
|
||||
"file": { "type": "string", "format": "binary", "title": "File" }
|
||||
},
|
||||
"type": "object",
|
||||
"required": ["file"],
|
||||
"title": "Body_postV1Upload file to cloud storage"
|
||||
},
|
||||
"Body_postV2Add_credits_to_user": {
|
||||
"properties": {
|
||||
"user_id": { "type": "string", "title": "User Id" },
|
||||
@@ -6348,6 +6449,24 @@
|
||||
"required": ["permissions"],
|
||||
"title": "UpdatePermissionsRequest"
|
||||
},
|
||||
"UploadFileResponse": {
|
||||
"properties": {
|
||||
"file_uri": { "type": "string", "title": "File Uri" },
|
||||
"file_name": { "type": "string", "title": "File Name" },
|
||||
"size": { "type": "integer", "title": "Size" },
|
||||
"content_type": { "type": "string", "title": "Content Type" },
|
||||
"expires_in_hours": { "type": "integer", "title": "Expires In Hours" }
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"file_uri",
|
||||
"file_name",
|
||||
"size",
|
||||
"content_type",
|
||||
"expires_in_hours"
|
||||
],
|
||||
"title": "UploadFileResponse"
|
||||
},
|
||||
"UserHistoryResponse": {
|
||||
"properties": {
|
||||
"history": {
|
||||
|
||||
@@ -31,7 +31,7 @@ import {
|
||||
parseKeys,
|
||||
setNestedProperty,
|
||||
} from "@/lib/utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Button } from "@/components/atoms/Button/Button";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { TextRenderer } from "@/components/ui/render";
|
||||
import { history } from "./history";
|
||||
@@ -54,8 +54,10 @@ import {
|
||||
CopyIcon,
|
||||
ExitIcon,
|
||||
} from "@radix-ui/react-icons";
|
||||
|
||||
import { Key } from "@phosphor-icons/react";
|
||||
import useCredits from "@/hooks/useCredits";
|
||||
import { getV1GetAyrshareSsoUrl } from "@/app/api/__generated__/endpoints/integrations/integrations";
|
||||
import { toast } from "@/components/molecules/Toast/use-toast";
|
||||
|
||||
export type ConnectionData = Array<{
|
||||
edge_id: string;
|
||||
@@ -112,6 +114,8 @@ export const CustomNode = React.memo(
|
||||
const flowContext = useContext(FlowContext);
|
||||
const api = useBackendAPI();
|
||||
const { formatCredits } = useCredits();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
let nodeFlowId = "";
|
||||
|
||||
if (data.uiType === BlockUIType.AGENT) {
|
||||
@@ -241,6 +245,59 @@ export const CustomNode = React.memo(
|
||||
return renderHandles(schema.properties);
|
||||
};
|
||||
|
||||
const generateAyrshareSSOHandles = () => {
|
||||
const handleSSOLogin = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const {
|
||||
data: { sso_url },
|
||||
} = await getV1GetAyrshareSsoUrl();
|
||||
const popup = window.open(sso_url, "_blank", "popup=true");
|
||||
if (!popup) {
|
||||
throw new Error(
|
||||
"Please allow popups for this site to be able to login with Ayrshare",
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: "Error",
|
||||
description: `Error getting SSO URL: ${error}`,
|
||||
variant: "destructive",
|
||||
});
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
className="w-full"
|
||||
onClick={handleSSOLogin}
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading ? (
|
||||
"Loading..."
|
||||
) : (
|
||||
<>
|
||||
<Key className="mr-2 h-4 w-4" />
|
||||
Connect Social Media Accounts
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
<NodeHandle
|
||||
title="SSO Token"
|
||||
keyName="sso_token"
|
||||
isConnected={false}
|
||||
schema={{ type: "string" }}
|
||||
side="right"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const generateInputHandles = (
|
||||
schema: BlockIORootSchema,
|
||||
nodeType: BlockUIType,
|
||||
@@ -827,8 +884,18 @@ export const CustomNode = React.memo(
|
||||
(A Webhook URL will be generated when you save the agent)
|
||||
</p>
|
||||
))}
|
||||
{data.inputSchema &&
|
||||
generateInputHandles(data.inputSchema, data.uiType)}
|
||||
{data.uiType === BlockUIType.AYRSHARE ? (
|
||||
<>
|
||||
{generateAyrshareSSOHandles()}
|
||||
{generateInputHandles(
|
||||
data.inputSchema,
|
||||
BlockUIType.STANDARD,
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
data.inputSchema &&
|
||||
generateInputHandles(data.inputSchema, data.uiType)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
|
||||
@@ -230,7 +230,7 @@ export default function CredentialsProvider({
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoggedIn || providerNames.length === 0) {
|
||||
if (isLoggedIn == false) setProviders(null);
|
||||
if (isLoggedIn == false) setProviders({});
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -604,6 +604,7 @@ export enum BlockUIType {
|
||||
WEBHOOK_MANUAL = "Webhook (manual)",
|
||||
AGENT = "Agent",
|
||||
AI = "AI",
|
||||
AYRSHARE = "Ayrshare",
|
||||
}
|
||||
|
||||
export enum SpecialBlockID {
|
||||
|
||||
Reference in New Issue
Block a user