from fastapi import FastAPI, HTTPException from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from fastapi.responses import FileResponse from pydantic import BaseModel import os import json import httpx import redis app = FastAPI(title="Manus Clone") app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # ── Redis connection (2GB, localhost) ────────────────────────────────────────── try: r = redis.Redis(host="localhost", port=6379, db=0, decode_responses=True) r.ping() REDIS_OK = True except Exception: REDIS_OK = False class ChatRequest(BaseModel): prompt: str model: str = "llama-3.1-8b-instant" api_key: str = "" provider: str = "Groq" PROVIDER_BASES = { "OpenAI": "https://api.openai.com/v1", "Anthropic": "https://api.anthropic.com/v1", "Groq": "https://api.groq.com/openai/v1", "HuggingFace": "https://api-inference.huggingface.co/v1", "OpenRouter": "https://openrouter.ai/api/v1", "Nvidia": "https://integrate.api.nvidia.com/v1", "Mistral": "https://api.mistral.ai/v1", "Cohere": "https://api.cohere.ai/v1", } # ── API endpoints ───────────────────────────────────────────────────────────── @app.get("/health") def health(): return {"status": "ok", "redis": REDIS_OK} @app.post("/chat") async def chat_endpoint(request: ChatRequest): api_key = ( request.api_key.strip() or os.environ.get("GROQ_API_KEY", "") or os.environ.get("OPENAI_API_KEY", "") ) if not api_key: return { "response": ( f'[Manus] Received: "{request.prompt}"\n\n' "No API key configured. Please add your key via the Providers panel." ), "doc": True } base_url = PROVIDER_BASES.get(request.provider, PROVIDER_BASES["Groq"]) # Build headers headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json", } # OpenRouter needs extra headers for some models if request.provider == "OpenRouter": headers["HTTP-Referer"] = "https://augment17-logic-engine.hf.space" headers["X-Title"] = "Manus Clone" try: async with httpx.AsyncClient(timeout=60) as client: resp = await client.post( f"{base_url}/chat/completions", headers=headers, json={ "model": request.model, "messages": [ {"role": "system", "content": "You are Manus, a helpful and concise autonomous AI agent."}, {"role": "user", "content": request.prompt}, ], "temperature": 0.7, } ) resp.raise_for_status() data = resp.json() # Robust response extraction reply = None try: choices = data.get("choices", []) if choices and len(choices) > 0: choice = choices[0] msg = choice.get("message") or choice.get("delta") or {} reply = msg.get("content") or choice.get("text") or "" except (KeyError, IndexError, TypeError): pass # Fallback keys if not reply: reply = ( data.get("text") or data.get("output") or data.get("response") or data.get("content") or data.get("result") or "" ) # Empty choices = model not found / not available if not reply: raise HTTPException( status_code=404, detail=( f"Model '{request.model}' returned an empty response from {request.provider}. " f"This usually means the model does not exist on {request.provider}'s API. " f"Try a different model or switch to OpenRouter which supports more models." ) ) # Cache to Redis if available if REDIS_OK: try: cache_key = f"chat:{request.provider}:{request.model}:{request.prompt[:100]}" r.setex(cache_key, 3600, reply) except Exception: pass return {"response": reply, "doc": True} except HTTPException: raise except httpx.HTTPStatusError as e: status = e.response.status_code try: err_body = e.response.json() err_msg = err_body.get("error", {}).get("message", e.response.text[:300]) except Exception: err_msg = e.response.text[:300] if status == 401: detail = ( f"401 Unauthorized from {request.provider}. " f"Check that your API key is valid for {request.provider} " f"and that the model '{request.model}' is available on {request.provider}. " f"Tip: Make sure the model you selected belongs to the same provider as your API key." ) elif status == 400: detail = f"400 Bad Request from {request.provider}: {err_msg}" elif status == 404: detail = f"404: Model '{request.model}' not found on {request.provider}. Select a model from the correct provider tab." else: detail = f"Provider error {status}: {err_msg}" raise HTTPException(status_code=status, detail=detail) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) # ── Serve React UI (MUST be last, catches all non-API routes) ───────────────── static_dir = os.path.join(os.path.dirname(__file__), "static") if os.path.isdir(static_dir): @app.get("/{full_path:path}") async def serve_spa(full_path: str): file_path = os.path.join(static_dir, full_path) if full_path and os.path.isfile(file_path): return FileResponse(file_path) return FileResponse(os.path.join(static_dir, "index.html"))