Spaces:
Sleeping
Sleeping
| 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 βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def health(): | |
| return {"status": "ok", "redis": REDIS_OK} | |
| 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): | |
| 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")) | |