diff --git a/bases/rsptx/book_server_api/routers/coach.py b/bases/rsptx/book_server_api/routers/coach.py index 5d672728a..40368aa08 100644 --- a/bases/rsptx/book_server_api/routers/coach.py +++ b/bases/rsptx/book_server_api/routers/coach.py @@ -21,6 +21,18 @@ # ------------------------- from rsptx.logging import rslogger +# CodeTailor related imports +# ------------------------- +from fastapi.responses import JSONResponse +from .personalized_parsons.end_to_end import get_parsons_help +from typing import Optional +import re +from fastapi import status + +from .assessment import get_question_source, SelectQRequest +# Import function for fetching api - comment out for DEV purposes +from rsptx.db.crud.crud import fetch_api_token +from rsptx.db.crud.course import fetch_course # .. _APIRouter config: # @@ -65,3 +77,187 @@ async def python_check(request: Request): resultMessage = f"{filename}:{str(e.lineno)}:{str(e.offset)}: {e.args[0]}\n" return resultMessage + + + +# Starting here -- Added code for CodeTailor --- +DEV_API_KEY = "" +# for dev/test -- replace with your own key for local testing + +def extract_parsons_code(html_block): + """ + Given the full HTML/pre block for a Parsons problem extracted from DB, + return only the Parsons code part. + """ + # Remove all HTML tags and extract the code lines + text = re.sub(r"<.*?>", "", html_block, flags=re.DOTALL) + lines = text.strip().splitlines() + if "-----" in lines: + idx = lines.index("-----") + code_lines = lines[idx+1:] + else: + code_lines = lines + + clean_lines = [line for line in code_lines if line.strip() and line.strip() != "====="] + return "\n".join(clean_lines) + + +@router.get("/get_question_html") +async def get_question_html(request: Request, div_id: str): + """ + Fetch and return just the HTML for a single question (case 1). + No grading — points are set to 0. + Falls back to 'LLM-example' if the question is not found. + """ + request_data = SelectQRequest( + selector_id=div_id, + questions=div_id, + points=0, + proficiency=None, + min_difficulty=None, + max_difficulty=None, + not_seen_ever=False, + autogradable=None, + primary=None, + AB=None, + toggleOptions=None, + timedWrapper=None, + limitBaseCourse=None, + ) + + result = await get_question_source(request, request_data) + + html = None + if isinstance(result, dict): + html = result.get("detail") + else: + html = getattr(result, "detail", None) + + # Handle missing or error cases + if not html or "No Questions" in html or "not in the database" in html: + return {"html": "LLM-example"} + + return {"html": html} + +# @router.post("/ns/coach/parsons_scaffolding") +@router.post("/parsons_scaffolding") +async def parsons_scaffolding( + request: Request, + course: Optional[str] +): + # Get `course` directly from the query string + rslogger.warning(f"URL seen: {request.url}") + rslogger.warning(f"Query parameters: {request.query_params}") + course_name = request.query_params.get("course") + # Import api key and handles errors + api_token = None + rslogger.warning(f"CodeTailor: Received request for course '{course_name}'") + try: + if course_name is None or course_name == "personalized_parsons": # the test course for development + # Dev/Test mode testing + rslogger.warning("CodeTailor: Using predefined dev API key") + api_token = DEV_API_KEY + else: + # obtain the CoursesValidator object - Brad's review + try: + course = await fetch_course(course_name) + except AttributeError: + rslogger.error(f"CodeTailor: Course '{course_name}' not found.") + return JSONResponse( + content={"error": "CodeTailor: No course found"}, + status_code=status.HTTP_400_BAD_REQUEST, + ) + rslogger.warning(f"[CodeTailor] Fetching course: {course_name}, id: {course.id}") + # this does not return a token, it returns an APITokenValidator object + + token_record = await fetch_api_token( # handles decryption already + course_id = course.id, + provider = 'openai', # from add_token.html