|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
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: |
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
if "reslt" in new: |
|
|
new = new.replace("reslt", "result") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 = {} |
|
|
|
|
|
|
|
|
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", "") |
|
|
|
|
|
|
|
|
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: |
|
|
|
|
|
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": []}, |
|
|
} |
|
|
|
|
|
|
|
|
return JSONResponse(resp) |
|
|
|
|
|
|
|
|
if _name_ == "_main_": |
|
|
import uvicorn |
|
|
uvicorn.run("app:app", host="0.0.0.0", port=int(os.getenv("PORT", "7860")), reload=False) |