175 lines
4.3 KiB
Python
175 lines
4.3 KiB
Python
|
|
"""
|
||
|
|
Manus AI Clone - AI-powered browser automation API
|
||
|
|
"""
|
||
|
|
|
||
|
|
import os
|
||
|
|
from contextlib import asynccontextmanager
|
||
|
|
from pathlib import Path
|
||
|
|
from typing import Optional
|
||
|
|
|
||
|
|
from dotenv import load_dotenv
|
||
|
|
from fastapi import FastAPI, HTTPException, Request
|
||
|
|
from fastapi.responses import HTMLResponse
|
||
|
|
from fastapi.staticfiles import StaticFiles
|
||
|
|
from fastapi.templating import Jinja2Templates
|
||
|
|
from pydantic import BaseModel, ConfigDict
|
||
|
|
from pydantic_settings import BaseSettings
|
||
|
|
|
||
|
|
from browser_agent import BrowserAgent
|
||
|
|
|
||
|
|
# Load environment variables
|
||
|
|
load_dotenv()
|
||
|
|
|
||
|
|
# Get base directory
|
||
|
|
BASE_DIR = Path(__file__).resolve().parent
|
||
|
|
|
||
|
|
# Setup templates and static files
|
||
|
|
templates = Jinja2Templates(directory=str(BASE_DIR / "templates"))
|
||
|
|
|
||
|
|
|
||
|
|
class Settings(BaseSettings):
|
||
|
|
"""Application settings"""
|
||
|
|
|
||
|
|
model_config = ConfigDict(env_file=".env")
|
||
|
|
|
||
|
|
openai_api_key: str = os.getenv("OPENAI_API_KEY", "")
|
||
|
|
model: str = os.getenv("MODEL", "gpt-4o-mini")
|
||
|
|
headless: bool = os.getenv("HEADLESS", "false").lower() == "true"
|
||
|
|
|
||
|
|
|
||
|
|
# Global agent instance
|
||
|
|
agent: Optional[BrowserAgent] = None
|
||
|
|
settings = Settings()
|
||
|
|
|
||
|
|
|
||
|
|
@asynccontextmanager
|
||
|
|
async def lifespan(app: FastAPI):
|
||
|
|
"""Manage application lifecycle"""
|
||
|
|
global agent
|
||
|
|
|
||
|
|
# Startup
|
||
|
|
if not settings.openai_api_key:
|
||
|
|
print("Warning: OPENAI_API_KEY not set. Please set it in .env file")
|
||
|
|
else:
|
||
|
|
print("Initializing browser agent...")
|
||
|
|
agent = BrowserAgent(
|
||
|
|
openai_api_key=settings.openai_api_key, model=settings.model
|
||
|
|
)
|
||
|
|
await agent.initialize(headless=settings.headless)
|
||
|
|
print("Browser agent ready!")
|
||
|
|
|
||
|
|
yield
|
||
|
|
|
||
|
|
# Shutdown
|
||
|
|
if agent:
|
||
|
|
print("Cleaning up browser agent...")
|
||
|
|
await agent.cleanup()
|
||
|
|
print("Cleanup complete")
|
||
|
|
|
||
|
|
|
||
|
|
# Create FastAPI app
|
||
|
|
app = FastAPI(
|
||
|
|
title="Manus AI Clone",
|
||
|
|
description="AI-powered browser automation using LangChain and Playwright",
|
||
|
|
version="0.1.0",
|
||
|
|
lifespan=lifespan,
|
||
|
|
)
|
||
|
|
|
||
|
|
# Mount static files
|
||
|
|
app.mount("/static", StaticFiles(directory=str(BASE_DIR / "static")), name="static")
|
||
|
|
|
||
|
|
|
||
|
|
# Pydantic models
|
||
|
|
class TaskRequest(BaseModel):
|
||
|
|
"""Request model for browser automation tasks"""
|
||
|
|
|
||
|
|
model_config = ConfigDict(
|
||
|
|
json_schema_extra={
|
||
|
|
"example": {
|
||
|
|
"prompt": "Go to google.com and search for 'LangChain tutorial'"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
)
|
||
|
|
|
||
|
|
prompt: str
|
||
|
|
|
||
|
|
|
||
|
|
class TaskResponse(BaseModel):
|
||
|
|
"""Response model for browser automation tasks"""
|
||
|
|
|
||
|
|
success: bool
|
||
|
|
output: Optional[str] = None
|
||
|
|
error: Optional[str] = None
|
||
|
|
screenshot: Optional[str] = None
|
||
|
|
action_history: list = []
|
||
|
|
|
||
|
|
|
||
|
|
# API Routes
|
||
|
|
@app.get("/", response_class=HTMLResponse)
|
||
|
|
async def home(request: Request):
|
||
|
|
"""Serve the frontend interface"""
|
||
|
|
return templates.TemplateResponse("index.html", {"request": request})
|
||
|
|
|
||
|
|
|
||
|
|
@app.get("/health")
|
||
|
|
async def health_check():
|
||
|
|
"""Health check endpoint"""
|
||
|
|
return {
|
||
|
|
"status": "healthy",
|
||
|
|
"agent_initialized": agent is not None,
|
||
|
|
"model": settings.model,
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
@app.post("/execute", response_model=TaskResponse)
|
||
|
|
async def execute_task(request: TaskRequest):
|
||
|
|
"""Execute a browser automation task"""
|
||
|
|
if not agent:
|
||
|
|
raise HTTPException(
|
||
|
|
status_code=503,
|
||
|
|
detail="Browser agent not initialized. Please check OPENAI_API_KEY.",
|
||
|
|
)
|
||
|
|
|
||
|
|
if not request.prompt.strip():
|
||
|
|
raise HTTPException(status_code=400, detail="Prompt cannot be empty")
|
||
|
|
|
||
|
|
try:
|
||
|
|
result = await agent.execute_task(request.prompt)
|
||
|
|
return TaskResponse(**result)
|
||
|
|
except Exception as e:
|
||
|
|
raise HTTPException(status_code=500, detail=f"Error executing task: {str(e)}")
|
||
|
|
|
||
|
|
|
||
|
|
@app.get("/status")
|
||
|
|
async def get_status():
|
||
|
|
"""Get current browser status and action history"""
|
||
|
|
if not agent:
|
||
|
|
raise HTTPException(status_code=503, detail="Browser agent not initialized")
|
||
|
|
|
||
|
|
return {
|
||
|
|
"action_history": agent.browser.action_history,
|
||
|
|
"current_url": agent.browser.page.url if agent.browser.page else None,
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
def main():
|
||
|
|
"""Run the application"""
|
||
|
|
import uvicorn
|
||
|
|
|
||
|
|
port = int(os.getenv("PORT", "8000"))
|
||
|
|
host = os.getenv("HOST", "0.0.0.0")
|
||
|
|
|
||
|
|
print(f"""
|
||
|
|
🤖 Manus AI Clone Starting...
|
||
|
|
|
||
|
|
📝 Make sure to set OPENAI_API_KEY in your .env file
|
||
|
|
🌐 Server will be available at: http://localhost:{port}
|
||
|
|
|
||
|
|
""")
|
||
|
|
|
||
|
|
uvicorn.run(app, host=host, port=port)
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
main()
|