File size: 4,839 Bytes
3ae68d6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
import os
from fastapi import FastAPI, HTTPException, Body, Response
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel, Field
from typing import Optional, List, Dict, Any

from src.config import settings
from src.services.scraper import scrape_url
from src.services.executor import run_tests
from src.agent import run_agent_workflow


app = FastAPI(
    title="Smart API DevTool",
    description="Backend API for crawling docs and auto-generating self-healing API wrappers.",
    version="1.0.0"
)

# Enable CORS for frontend dashboard
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

class AnalyzeRequest(BaseModel):
    url: Optional[str] = Field(None, description="The URL of the API documentation.")
    raw_docs: Optional[str] = Field(None, description="Raw pasted API documentation text.")
    use_case: str = Field(..., description="Details on what the wrapper will do.")
    language: str = Field("python", description="Target programming language.")
    model_provider: str = Field("gemini", description="Either 'gemini', 'ollama', 'groq' or 'openrouter'.")
    gemini_key: Optional[str] = Field(None, description="Optional Google Gemini API Key.")
    gemini_model: Optional[str] = Field("gemini-2.5-flash", description="Optional Google Gemini Model ID.")
    groq_key: Optional[str] = Field(None, description="Optional Groq API Key.")
    groq_model: Optional[str] = Field(None, description="Optional Groq Model ID.")
    openrouter_key: Optional[str] = Field(None, description="Optional OpenRouter API Key.")
    openrouter_model: Optional[str] = Field("openrouter/free", description="Optional OpenRouter Model ID.")
    firecrawl_key: Optional[str] = Field(None, description="Optional Firecrawl API Key.")

@app.get("/api/health")
def health_check():
    """Simple endpoint to verify server status."""
    return {
        "status": "healthy",
        "configuration": {
            "has_gemini_key": bool(settings.gemini_api_key),
            "has_groq_key": bool(settings.groq_api_key),
            "has_openrouter_key": bool(settings.openrouter_api_key),
            "has_firecrawl_key": bool(settings.firecrawl_api_key),
            "ollama_base_url": settings.ollama_base_url
        }
    }

@app.get("/favicon.ico", include_in_schema=False)
def favicon():
    """Handles browser favicon requests with 204 No Content to prevent console 404 log littering."""
    return Response(status_code=204)

@app.post("/api/analyze")
async def analyze_api(request: AnalyzeRequest):
    """
    Analyzes API documentation (via URL scraping or raw text) and triggers
    the LangGraph self-healing agent loop to output verified wrapper classes.
    """
    try:
        scraped_text = ""
        if request.url:
            scraped_text = scrape_url(request.url)
        elif request.raw_docs:
            scraped_text = request.raw_docs
        else:
            raise HTTPException(status_code=400, detail="Must provide either a URL or raw_docs.")
            
        result = run_agent_workflow(
            scraped_text=scraped_text,
            use_case=request.use_case,
            language=request.language,
            model_provider=request.model_provider,
            gemini_key=request.gemini_key,
            gemini_model=request.gemini_model,
            groq_key=request.groq_key,
            groq_model=request.groq_model,
            openrouter_key=request.openrouter_key,
            openrouter_model=request.openrouter_model,
            firecrawl_key=request.firecrawl_key
        )
        
        return {
            "success": result.get("test_passed", False),
            "overview": result.get("overview", ""),
            "endpoints": result.get("endpoints", ""),
            "code": result.get("code", ""),
            "tests": result.get("tests", ""),
            "readme": result.get("readme", ""),
            "retry_count": result.get("retry_count", 0),
            "error_logs": result.get("error_logs", ""),
            "test_passed": result.get("test_passed", False)
        }
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

# Mount static files programmatically if the public folder exists
public_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "public"))
if os.path.isdir(public_path):
    app.mount("/", StaticFiles(directory=public_path, html=True), name="public")
else:
    # Fallback message route for clean dev experience before frontend is built
    @app.get("/")
    def read_root():
        return {
            "message": "Welcome to Smart API DevTool Backend! The 'public' directory is not yet present. Exposing API routes at /api/health."
        }