import os import json import re from time import time from flask import Flask, request, jsonify from flask_cors import CORS from dotenv import load_dotenv from openai import OpenAI load_dotenv() app = Flask(__name__) allowed_origins = os.getenv("ALLOWED_ORIGINS", "https://sumit989bishnoi-crypto.github.io") CORS(app, origins=allowed_origins.split(",")) API_BASE_URL = os.getenv("API_BASE_URL", "https://router.huggingface.co/v1") MODEL_NAME = os.getenv("MODEL_NAME", "google/gemma-4-31B-it") API_KEY = os.getenv("API_KEY") or os.getenv("HF_TOKEN") client = OpenAI(base_url=API_BASE_URL, api_key=API_KEY) if API_KEY else None _last_request: dict[str, float] = {} def is_rate_limited(ip: str) -> bool: now = time() if ip in _last_request and now - _last_request[ip] < 2: return True _last_request[ip] = now return False def extract_json(raw: str) -> dict | None: """Strip markdown fences then attempt JSON parse two ways.""" cleaned = re.sub(r"^```(?:json)?\s*", "", raw.strip()) cleaned = re.sub(r"\s*```$", "", cleaned).strip() try: return json.loads(cleaned) except json.JSONDecodeError: pass # Boundary-aware fallback — stops exactly where JSON ends try: idx = cleaned.index("{") decoded, _ = json.JSONDecoder().raw_decode(cleaned, idx) return decoded except (ValueError, json.JSONDecodeError): return None def safe_parse(raw: str) -> dict: """ Always returns a valid dict. Runs extract_json first; falls back to a safe default on any failure so a bad model response never crashes the endpoint. """ if not raw: return { "language": "unknown", "explanation": "The AI returned an empty response. Please try again.", "fixed_code": "", "confidence": "low", } result = extract_json(raw) if result and isinstance(result, dict): return result # Last-resort: return the raw text as the explanation so the user sees something return { "language": "unknown", "explanation": "Output parsing failed — the AI did not return valid JSON.", "fixed_code": raw[:800], "confidence": "low", } @app.route("/") def index(): return jsonify({"name": "CodeRescue API", "version": "1.0.0", "status": "running"}) @app.route("/health") def health(): return jsonify({"status": "ok"}) @app.route("/analyze", methods=["POST"]) def analyze_code(): forwarded = request.headers.get("X-Forwarded-For", "") ip = forwarded.split(",")[0].strip() if forwarded else (request.remote_addr or "unknown") if is_rate_limited(ip): return jsonify({ "error": "Too many requests. Please wait a moment.", "fixed_code": "", "language": "" }), 429 if not client: return jsonify({"error": "API key not configured", "fixed_code": "", "language": ""}), 500 data = request.get_json(silent=True) if not data or not data.get("code", "").strip(): return jsonify({"error": "No code provided"}), 400 raw_code = data["code"].strip() warning = "" if len(raw_code) > 800: warning = "Code was truncated to 800 characters for processing." user_code = raw_code[:800] try: response = client.chat.completions.create( model=MODEL_NAME, max_tokens=1200, # raised: 800 caused truncated JSON for longer fixes messages=[ { "role": "system", "content": ( "You are an expert developer and code debugger.\n" "\n" "OUTPUT RULES — follow exactly:\n" "1. Return ONLY valid, complete JSON. No markdown. No prose outside JSON.\n" "2. The JSON object MUST be fully closed with all braces and brackets.\n" "3. Do NOT truncate fixed_code under any circumstance.\n" "4. Escape all special characters inside strings properly:\n" " - newlines → \\n\n" " - tabs → \\t\n" " - backslashes → \\\\\n" " - double quotes → \\\"\n" "5. Never wrap the JSON in ```code fences```.\n" "Before answering, verify:\n" "1. Is there memory issue?\n" "2. Is there concurrency issue?\n" "3. Is fix consistent with explanation\n" "4. Does fix preserve original logic?\n" "\n" "STEPS:\n" "Step 1 — Detect the programming language automatically.\n" "Step 2 — Find all bugs or errors in the code.\n" "Step 3 — Explain briefly WHY the error happened (2-3 line).\n" "Step 4 — Write the fully corrected code.\n" "Step 5 — Rate your confidence as 'high', 'medium', or 'low'.\n" "\n" "REQUIRED JSON FORMAT (no deviations):\n" "{\"language\":\"...\",\"explanation\":\"...\"," "\"fixed_code\":\"...\",\"confidence\":\"high|medium|low\"}" ), }, { "role": "user", "content": f"Code:\n{user_code}", }, ], ) raw = response.choices[0].message.content.strip() # safe_parse always returns a dict — never raises parsed = safe_parse(raw) fixed_code = parsed.get("fixed_code", "") # Unescape literal \n / \t the model emits inside JSON strings fixed_code = fixed_code.replace("\\n", "\n").replace("\\t", "\t") return jsonify({ "explanation": parsed.get("explanation", "No explanation provided."), "fixed_code": fixed_code, "language": parsed.get("language", "unknown"), "confidence": parsed.get("confidence", "medium"), "warning": warning, }) except Exception as e: return jsonify({ "explanation": f"Server error: {str(e)}", "fixed_code": "", "language": "unknown", "confidence": "low", "warning": warning }), 500 @app.route("/openenv/reset", methods=["POST"]) @app.route("/reset", methods=["POST"]) def openenv_reset(): return jsonify({"status": "success"}) if __name__ == "__main__": port = int(os.environ.get("PORT", 7860)) app.run(host="0.0.0.0", port=port)