2025-07-07 00:17:29 +08:00
|
|
|
from fastapi import APIRouter, HTTPException, Depends
|
|
|
|
from typing import List, Optional, Dict, Any
|
|
|
|
from pydantic import BaseModel
|
|
|
|
|
|
|
|
from utils.logger import logger
|
|
|
|
from utils.auth_utils import get_current_user_id_from_jwt
|
2025-07-29 13:55:18 +08:00
|
|
|
from services.supabase import DBConnection
|
|
|
|
|
|
|
|
from .template_service import (
|
|
|
|
get_template_service,
|
|
|
|
AgentTemplate,
|
2025-07-14 18:36:27 +08:00
|
|
|
TemplateNotFoundError,
|
|
|
|
TemplateAccessDeniedError,
|
2025-07-23 00:11:10 +08:00
|
|
|
SunaDefaultAgentTemplateError
|
2025-07-14 18:36:27 +08:00
|
|
|
)
|
2025-07-29 13:55:18 +08:00
|
|
|
from .installation_service import (
|
|
|
|
get_installation_service,
|
|
|
|
TemplateInstallationRequest,
|
|
|
|
TemplateInstallationResult,
|
|
|
|
TemplateInstallationError,
|
|
|
|
InvalidCredentialError
|
|
|
|
)
|
|
|
|
from .utils import format_template_for_response
|
2025-07-07 00:17:29 +08:00
|
|
|
|
2025-07-14 18:36:27 +08:00
|
|
|
router = APIRouter()
|
2025-07-07 00:17:29 +08:00
|
|
|
|
2025-07-29 13:55:18 +08:00
|
|
|
db: Optional[DBConnection] = None
|
|
|
|
|
|
|
|
|
2025-07-07 00:17:29 +08:00
|
|
|
class CreateTemplateRequest(BaseModel):
|
|
|
|
agent_id: str
|
|
|
|
make_public: bool = False
|
|
|
|
tags: Optional[List[str]] = None
|
|
|
|
|
2025-07-14 18:36:27 +08:00
|
|
|
|
2025-07-07 00:17:29 +08:00
|
|
|
class InstallTemplateRequest(BaseModel):
|
|
|
|
template_id: str
|
|
|
|
instance_name: Optional[str] = None
|
|
|
|
custom_system_prompt: Optional[str] = None
|
|
|
|
profile_mappings: Optional[Dict[str, str]] = None
|
|
|
|
custom_mcp_configs: Optional[Dict[str, Dict[str, Any]]] = None
|
|
|
|
|
2025-07-14 18:36:27 +08:00
|
|
|
|
2025-07-07 00:17:29 +08:00
|
|
|
class PublishTemplateRequest(BaseModel):
|
|
|
|
tags: Optional[List[str]] = None
|
|
|
|
|
2025-07-14 18:36:27 +08:00
|
|
|
|
2025-07-07 00:17:29 +08:00
|
|
|
class TemplateResponse(BaseModel):
|
|
|
|
template_id: str
|
2025-08-03 04:25:19 +08:00
|
|
|
creator_id: str
|
2025-07-07 00:17:29 +08:00
|
|
|
name: str
|
|
|
|
description: Optional[str]
|
2025-08-03 04:25:19 +08:00
|
|
|
system_prompt: str
|
2025-07-07 00:17:29 +08:00
|
|
|
mcp_requirements: List[Dict[str, Any]]
|
|
|
|
agentpress_tools: Dict[str, Any]
|
|
|
|
tags: List[str]
|
|
|
|
is_public: bool
|
|
|
|
download_count: int
|
|
|
|
marketplace_published_at: Optional[str]
|
|
|
|
created_at: str
|
2025-08-03 04:25:19 +08:00
|
|
|
updated_at: str
|
2025-07-07 00:17:29 +08:00
|
|
|
creator_name: Optional[str] = None
|
|
|
|
avatar: Optional[str]
|
|
|
|
avatar_color: Optional[str]
|
2025-08-03 04:25:19 +08:00
|
|
|
metadata: Dict[str, Any] = {}
|
2025-07-07 00:17:29 +08:00
|
|
|
|
2025-07-14 18:36:27 +08:00
|
|
|
|
2025-07-07 00:17:29 +08:00
|
|
|
class InstallationResponse(BaseModel):
|
2025-07-14 18:36:27 +08:00
|
|
|
status: str
|
2025-07-07 00:17:29 +08:00
|
|
|
instance_id: Optional[str] = None
|
2025-07-14 18:36:27 +08:00
|
|
|
name: Optional[str] = None
|
2025-07-29 13:55:18 +08:00
|
|
|
missing_regular_credentials: List[Dict[str, Any]] = []
|
|
|
|
missing_custom_configs: List[Dict[str, Any]] = []
|
|
|
|
template_info: Optional[Dict[str, Any]] = None
|
|
|
|
|
|
|
|
|
|
|
|
def initialize(database: DBConnection):
|
|
|
|
global db
|
|
|
|
db = database
|
2025-07-07 00:17:29 +08:00
|
|
|
|
|
|
|
|
|
|
|
@router.post("", response_model=Dict[str, str])
|
2025-07-29 13:55:18 +08:00
|
|
|
async def create_template_from_agent(
|
2025-07-07 00:17:29 +08:00
|
|
|
request: CreateTemplateRequest,
|
|
|
|
user_id: str = Depends(get_current_user_id_from_jwt)
|
|
|
|
):
|
|
|
|
try:
|
2025-07-29 13:55:18 +08:00
|
|
|
template_service = get_template_service(db)
|
|
|
|
|
|
|
|
template_id = await template_service.create_from_agent(
|
2025-07-07 00:17:29 +08:00
|
|
|
agent_id=request.agent_id,
|
|
|
|
creator_id=user_id,
|
|
|
|
make_public=request.make_public,
|
|
|
|
tags=request.tags
|
|
|
|
)
|
|
|
|
|
2025-07-29 13:55:18 +08:00
|
|
|
return {"template_id": template_id}
|
|
|
|
|
2025-07-14 18:36:27 +08:00
|
|
|
except TemplateNotFoundError as e:
|
|
|
|
raise HTTPException(status_code=404, detail=str(e))
|
|
|
|
except TemplateAccessDeniedError as e:
|
|
|
|
raise HTTPException(status_code=403, detail=str(e))
|
2025-07-23 00:11:10 +08:00
|
|
|
except SunaDefaultAgentTemplateError as e:
|
|
|
|
raise HTTPException(status_code=400, detail=str(e))
|
2025-07-07 00:17:29 +08:00
|
|
|
except Exception as e:
|
2025-07-29 13:55:18 +08:00
|
|
|
logger.error(f"Error creating template: {e}")
|
|
|
|
raise HTTPException(status_code=500, detail="Internal server error")
|
2025-07-07 00:17:29 +08:00
|
|
|
|
2025-07-14 18:36:27 +08:00
|
|
|
|
2025-07-07 00:17:29 +08:00
|
|
|
@router.post("/{template_id}/publish")
|
|
|
|
async def publish_template(
|
|
|
|
template_id: str,
|
|
|
|
request: PublishTemplateRequest,
|
|
|
|
user_id: str = Depends(get_current_user_id_from_jwt)
|
|
|
|
):
|
|
|
|
try:
|
2025-07-29 13:55:18 +08:00
|
|
|
template_service = get_template_service(db)
|
|
|
|
|
|
|
|
success = await template_service.publish_template(template_id, user_id)
|
|
|
|
|
|
|
|
if not success:
|
|
|
|
raise HTTPException(status_code=404, detail="Template not found or access denied")
|
|
|
|
|
|
|
|
return {"message": "Template published successfully"}
|
2025-07-07 00:17:29 +08:00
|
|
|
|
|
|
|
except Exception as e:
|
2025-07-29 13:55:18 +08:00
|
|
|
logger.error(f"Error publishing template: {e}")
|
|
|
|
raise HTTPException(status_code=500, detail="Internal server error")
|
2025-07-07 00:17:29 +08:00
|
|
|
|
2025-07-14 18:36:27 +08:00
|
|
|
|
2025-07-07 00:17:29 +08:00
|
|
|
@router.post("/{template_id}/unpublish")
|
|
|
|
async def unpublish_template(
|
|
|
|
template_id: str,
|
|
|
|
user_id: str = Depends(get_current_user_id_from_jwt)
|
|
|
|
):
|
|
|
|
try:
|
2025-07-29 13:55:18 +08:00
|
|
|
template_service = get_template_service(db)
|
|
|
|
|
|
|
|
success = await template_service.unpublish_template(template_id, user_id)
|
|
|
|
|
|
|
|
if not success:
|
|
|
|
raise HTTPException(status_code=404, detail="Template not found or access denied")
|
|
|
|
|
|
|
|
return {"message": "Template unpublished successfully"}
|
2025-07-07 00:17:29 +08:00
|
|
|
|
|
|
|
except Exception as e:
|
2025-07-29 13:55:18 +08:00
|
|
|
logger.error(f"Error unpublishing template: {e}")
|
|
|
|
raise HTTPException(status_code=500, detail="Internal server error")
|
2025-07-07 00:17:29 +08:00
|
|
|
|
2025-07-14 18:36:27 +08:00
|
|
|
|
2025-08-03 04:25:19 +08:00
|
|
|
@router.delete("/{template_id}")
|
|
|
|
async def delete_template(
|
|
|
|
template_id: str,
|
|
|
|
user_id: str = Depends(get_current_user_id_from_jwt)
|
|
|
|
):
|
|
|
|
try:
|
|
|
|
template_service = get_template_service(db)
|
|
|
|
|
|
|
|
success = await template_service.delete_template(template_id, user_id)
|
|
|
|
|
|
|
|
if not success:
|
|
|
|
raise HTTPException(status_code=404, detail="Template not found or access denied")
|
|
|
|
|
|
|
|
return {"message": "Template deleted successfully"}
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
logger.error(f"Error deleting template: {e}")
|
|
|
|
raise HTTPException(status_code=500, detail="Internal server error")
|
|
|
|
|
|
|
|
|
2025-07-07 00:17:29 +08:00
|
|
|
@router.post("/install", response_model=InstallationResponse)
|
|
|
|
async def install_template(
|
|
|
|
request: InstallTemplateRequest,
|
|
|
|
user_id: str = Depends(get_current_user_id_from_jwt)
|
|
|
|
):
|
|
|
|
try:
|
2025-07-29 13:55:18 +08:00
|
|
|
installation_service = get_installation_service(db)
|
|
|
|
|
|
|
|
install_request = TemplateInstallationRequest(
|
2025-07-07 00:17:29 +08:00
|
|
|
template_id=request.template_id,
|
|
|
|
account_id=user_id,
|
|
|
|
instance_name=request.instance_name,
|
|
|
|
custom_system_prompt=request.custom_system_prompt,
|
|
|
|
profile_mappings=request.profile_mappings,
|
|
|
|
custom_mcp_configs=request.custom_mcp_configs
|
|
|
|
)
|
|
|
|
|
2025-07-29 13:55:18 +08:00
|
|
|
result = await installation_service.install_template(install_request)
|
|
|
|
|
|
|
|
return InstallationResponse(
|
|
|
|
status=result.status,
|
|
|
|
instance_id=result.instance_id,
|
|
|
|
name=result.name,
|
|
|
|
missing_regular_credentials=result.missing_regular_credentials,
|
|
|
|
missing_custom_configs=result.missing_custom_configs,
|
|
|
|
template_info=result.template_info
|
|
|
|
)
|
|
|
|
|
|
|
|
except TemplateInstallationError as e:
|
|
|
|
raise HTTPException(status_code=400, detail=str(e))
|
2025-07-14 18:36:27 +08:00
|
|
|
except InvalidCredentialError as e:
|
|
|
|
raise HTTPException(status_code=400, detail=str(e))
|
2025-07-07 00:17:29 +08:00
|
|
|
except Exception as e:
|
2025-07-29 13:55:18 +08:00
|
|
|
logger.error(f"Error installing template: {e}")
|
|
|
|
raise HTTPException(status_code=500, detail="Internal server error")
|
2025-07-07 00:17:29 +08:00
|
|
|
|
2025-07-14 18:36:27 +08:00
|
|
|
|
2025-07-07 00:17:29 +08:00
|
|
|
@router.get("/marketplace", response_model=List[TemplateResponse])
|
2025-07-29 13:55:18 +08:00
|
|
|
async def get_marketplace_templates():
|
2025-07-07 00:17:29 +08:00
|
|
|
try:
|
2025-07-29 13:55:18 +08:00
|
|
|
template_service = get_template_service(db)
|
|
|
|
templates = await template_service.get_public_templates()
|
2025-07-07 00:17:29 +08:00
|
|
|
|
2025-07-29 13:55:18 +08:00
|
|
|
return [
|
2025-08-03 04:25:19 +08:00
|
|
|
TemplateResponse(**format_template_for_response(template))
|
2025-07-29 13:55:18 +08:00
|
|
|
for template in templates
|
|
|
|
]
|
2025-07-07 00:17:29 +08:00
|
|
|
|
|
|
|
except Exception as e:
|
2025-07-29 13:55:18 +08:00
|
|
|
logger.error(f"Error getting marketplace templates: {e}")
|
|
|
|
raise HTTPException(status_code=500, detail="Internal server error")
|
2025-07-07 00:17:29 +08:00
|
|
|
|
2025-07-14 18:36:27 +08:00
|
|
|
|
2025-07-07 00:17:29 +08:00
|
|
|
@router.get("/my", response_model=List[TemplateResponse])
|
|
|
|
async def get_my_templates(
|
|
|
|
user_id: str = Depends(get_current_user_id_from_jwt)
|
|
|
|
):
|
|
|
|
try:
|
2025-07-29 13:55:18 +08:00
|
|
|
template_service = get_template_service(db)
|
|
|
|
templates = await template_service.get_user_templates(user_id)
|
|
|
|
|
|
|
|
return [
|
|
|
|
TemplateResponse(
|
|
|
|
**format_template_for_response(template),
|
|
|
|
creator_name=None
|
|
|
|
)
|
|
|
|
for template in templates
|
|
|
|
]
|
2025-07-07 00:17:29 +08:00
|
|
|
|
|
|
|
except Exception as e:
|
2025-07-29 13:55:18 +08:00
|
|
|
logger.error(f"Error getting user templates: {e}")
|
|
|
|
raise HTTPException(status_code=500, detail="Internal server error")
|
2025-07-07 00:17:29 +08:00
|
|
|
|
2025-07-14 18:36:27 +08:00
|
|
|
|
2025-07-07 00:17:29 +08:00
|
|
|
@router.get("/{template_id}", response_model=TemplateResponse)
|
2025-07-29 13:55:18 +08:00
|
|
|
async def get_template(
|
2025-07-07 00:17:29 +08:00
|
|
|
template_id: str,
|
|
|
|
user_id: str = Depends(get_current_user_id_from_jwt)
|
|
|
|
):
|
|
|
|
try:
|
2025-07-29 13:55:18 +08:00
|
|
|
template_service = get_template_service(db)
|
|
|
|
template = await template_service.get_template(template_id)
|
2025-07-07 00:17:29 +08:00
|
|
|
|
|
|
|
if not template:
|
2025-07-29 13:55:18 +08:00
|
|
|
raise HTTPException(status_code=404, detail="Template not found")
|
2025-07-07 00:17:29 +08:00
|
|
|
|
2025-07-29 13:55:18 +08:00
|
|
|
await template_service.validate_access(template, user_id)
|
2025-07-07 00:17:29 +08:00
|
|
|
|
|
|
|
return TemplateResponse(
|
2025-07-29 13:55:18 +08:00
|
|
|
**format_template_for_response(template),
|
|
|
|
creator_name=None
|
2025-07-07 00:17:29 +08:00
|
|
|
)
|
2025-07-29 13:55:18 +08:00
|
|
|
|
|
|
|
except TemplateAccessDeniedError:
|
|
|
|
raise HTTPException(status_code=403, detail="Access denied to template")
|
2025-07-07 00:17:29 +08:00
|
|
|
except Exception as e:
|
2025-07-29 13:55:18 +08:00
|
|
|
logger.error(f"Error getting template: {e}")
|
|
|
|
raise HTTPException(status_code=500, detail="Internal server error")
|