# app.py (Python_ai) import difflib import json import os from typing import Dict, Any from fastapi import FastAPI, Request from fastapi.responses import JSONResponse from fastapi.middleware.cors import CORSMiddleware APP_NAME = "Python_ai" app = FastAPI(title=APP_NAME) # Allow CORS just in case you call it directly from the Brain UI app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=[""], allow_headers=[""], ) def ok(payload: Dict[str, Any] | None = None) -> JSONResponse: return JSONResponse({"ok": True, **(payload or {})}) @app.get("/health") def health(): return ok() def make_unified_diff(filename: str, old_text: str, new_text: str) -> str: # Ensure both end with newline so ranges are sane if not old_text.endswith("\n"): old_text += "\n" if not new_text.endswith("\n"): new_text += "\n" diff = difflib.unified_diff( old_text.splitlines(keepends=True), new_text.splitlines(keepends=True), fromfile=f"a/{filename}", tofile=f"b/{filename}", n=3, ) return "".join(diff) def minimal_fix(view_text: str) -> tuple[str, str, str]: """ Very small heuristic fixer: - reslt -> result (common typo) - NameError 'bar' if foo returns bar but bar undefined -> return 'result' instead Only edits when the change is obvious; otherwise returns original text. """ old = view_text new = old # Trivial typo fix if "reslt" in new: new = new.replace("reslt", "result") # If there's a pattern like: # def foo():\n return bar\n\nresult = foo() # we can't know bar; safer to leave it alone here. # Keep this service minimal/fast. return old, new, "Fixed trivial misspelling(s) where obvious." @app.post("/code_help") async def code_help(req: Request): """ Accepts ANY JSON shape to avoid 422. We only read fields we need. Returns a PATCH-first JSON as your 'PythonFixer' contract expects. """ try: body = await req.json() except Exception: body = {} # Extract with defaults (never 422) utterance = body.get("utterance", "") telemetry = body.get("telemetry", {}) or {} filename = telemetry.get("file", "main.py") viewport = telemetry.get("viewport", {}) or {} view_text = viewport.get("text", "") # Minimal edit logic old_text, new_text, why = minimal_fix(view_text) if new_text != old_text: patch = make_unified_diff(filename, old_text, new_text) resp = { "mode": "patch", "patch": patch, "full_text": "", "explanation": why, "confidence": 0.96, "need": {"function": False, "xrefs": [], "page_ids": []}, } else: # Nothing obvious to fix; ask for more context (keeps contract) resp = { "mode": "ask", "patch": "", "full_text": "", "explanation": "Need more context or a specific error message to propose a safe minimal patch.", "confidence": 0.55, "need": {"function": False, "xrefs": [], "page_ids": []}, } # Always 200 with the contract object return JSONResponse(resp) # HF entry if _name_ == "_main_": import uvicorn uvicorn.run("app:app", host="0.0.0.0", port=int(os.getenv("PORT", "7860")), reload=False)