File size: 6,713 Bytes
91990f9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
from fastapi import APIRouter, HTTPException, Request
from pydantic import BaseModel
import httpx
import logging
from config.settings import get_settings
from services.ai_router import route_analysis
from services.quotas import check_quota, check_guest_quota

logger = logging.getLogger(__name__)

router = APIRouter(prefix="/api/analyze", tags=["analyze"])
settings = get_settings()

JDOODLE_LANGUAGE_MAP = {
    "python": {"language": "python3", "versionIndex": "4"},
    "javascript": {"language": "nodejs", "versionIndex": "4"},
    "typescript": {"language": "typescript", "versionIndex": "1"},
    "java": {"language": "java", "versionIndex": "4"},
    "c++": {"language": "cpp17", "versionIndex": "1"},
    "c": {"language": "c", "versionIndex": "5"},
    "go": {"language": "go", "versionIndex": "4"},
    "rust": {"language": "rust", "versionIndex": "4"},
    "ruby": {"language": "ruby", "versionIndex": "4"},
    "php": {"language": "php", "versionIndex": "4"},
}

class RunRequest(BaseModel):
    language: str
    version: str
    code: str
    stdin: str = ""

class AnalyzeRequest(BaseModel):
    model_config = {"protected_namespaces": ()}
    language: str
    code: str
    model_choice: str = "auto"

@router.get("/config")
async def get_ai_config():
    from services.settings import is_ai_enabled
    return {"ai_features_enabled": is_ai_enabled()}

@router.post("/run")
async def run_code(data: RunRequest):
    try:
        lang_config = JDOODLE_LANGUAGE_MAP.get(data.language.lower())
        if not lang_config:
            raise HTTPException(status_code=400, detail=f"Language {data.language} not supported")
        async with httpx.AsyncClient(timeout=30.0) as client:
            response = await client.post(
                "https://api.jdoodle.com/v1/execute",
                json={
                    "clientId": settings.jdoodle_client_id,
                    "clientSecret": settings.jdoodle_client_secret,
                    "script": data.code,
                    "stdin": data.stdin,
                    "language": lang_config["language"],
                    "versionIndex": lang_config["versionIndex"],
                }
            )
            result = response.json()
            output = result.get("output", "No output")
            return {"output": output}
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"Analysis failed: {str(e)}")
        raise HTTPException(status_code=500, detail="AI analysis failed. Please try again later.")

@router.post("/analyze")
async def analyze_code(data: AnalyzeRequest, request: Request):
    from services.settings import is_ai_enabled
    from middleware.auth_guard import get_current_user_optional, get_current_user
    
    if not is_ai_enabled():
        raise HTTPException(status_code=403, detail="AI analysis features are currently disabled by the administrator.")

    try:
        # Get user
        user = await get_current_user_optional(request)
        
        # 1. Tier Enforcement
        # Free Tier (4 Models + Auto): 'auto', 'gemma-4-31b', 'llama-3.1', 'qwen-2.5', 'nemotron-120b'
        # Pro Tier (4 Elite Models): 'minimax-2.5', 'mistral-large', 'groq-70b', 'gemini-flash'
        effective_model = data.model_choice
        user_role = user.role if user else "guest"
        
        free_models = ["auto", "gemma-4-31b", "llama-3.1", "qwen-2.5", "nemotron-120b"]
        
        if user_role != "pro" and data.model_choice not in free_models:
            logger.info(f"User {user.id if user else 'guest'} requested {data.model_choice} but is not Pro. Defaulting to auto.")
            effective_model = "auto"

        # 2. Quota Check
        if user:
            await check_quota(str(user.id), "analysis")
        else:
            await check_guest_quota(request.client.host, "analysis")

        prompt = f"""[CRITICAL: ELITE SYSTEMS ARCHITECT PERSONA]
Analyze this {data.language} code with the precision of a Lead Performance Engineer.

CODE TO ANALYZE:
{data.code}

RETURN ONLY THIS JSON STRUCTURE:
{{
  "time_complexity": "string (Big O)",
  "time_explanation": "Elite technical insight (e.g. 'Constant time access via hash map—Excellent speed.')",
  "space_complexity": "string (Big O)",
  "space_explanation": "Elite technical insight (e.g. 'Minimal auxiliary space—Optimal memory footprint.')",
  "issues": ["list of sharp technical issues"],
  "suggestions": ["list of architectural improvements - MINIMUM 3"],
  "optimized_code": "string (the superior solution)",
  "optimized_time_complexity": "string (Big O of optimized version)",
  "optimized_time_explanation": "Technical optimization insight",
  "optimized_space_complexity": "string (Big O of optimized version)",
  "optimized_space_explanation": "Memory optimization insight"
}}
[NOTE: Return valid JSON only. If the code is already optimally written, structure/format it securely into standard format for `optimized_code`, and return the same complexities.]"""

        # Using the robust AI router with Smart Recovery Failover
        result_with_meta = await route_analysis(prompt, model_choice=effective_model)
        
        # Extract the actual model used from the metadata
        actual_model = result_with_meta.get("_actual_model", effective_model)
        
        # Remove internal metadata before returning to frontend
        parsed = {k: v for k, v in result_with_meta.items() if not k.startswith("_")}

        # Save to DB if logged in
        if user:
            try:
                from config.database import get_supabase_admin
                admin_client = get_supabase_admin()
                admin_client.table("analyses").insert({
                    "user_id": str(user.id),
                    "language": data.language,
                    "code": data.code,
                    "ai_result": parsed,
                    "time_complexity": parsed.get("time_complexity", ""),
                    "space_complexity": parsed.get("space_complexity", ""),
                    "model_used": actual_model # Use the ACTUAL model that responded
                }).execute()
                logger.info(f"Analysis saved for user {user.id} (Actual model: {actual_model})")
            except Exception as save_err:
                logger.error(f"Failed to save analysis to DB for user {user.id}: {save_err}")

        # Return result with actual model name so frontend can know
        return {**parsed, "actual_model_used": actual_model}
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"Analysis process failed: {str(e)}")
        raise HTTPException(status_code=500, detail="AI analysis failed. Please try again later.")