Version 1.0

This commit is contained in:
Alexander R.
2026-07-21 22:24:25 +00:00
parent f3645fbfbc
commit 9edbfd3f77
4134 changed files with 1448752 additions and 1 deletions
+181
View File
@@ -0,0 +1,181 @@
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from fastapi.middleware.cors import CORSMiddleware
from src.core.loader import ChallengeLoader
from src.core.executor import executor
from src.core.linter import linter
from src.core.prompts import get_socratic_prompt
app = FastAPI(title="Socratic Tutor API")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
loader = ChallengeLoader()
class LintRequest(BaseModel):
language: str
code: str
from typing import Optional
class GuideRequest(BaseModel):
challenge_id: str
language: str
code: str
question: Optional[str] = ""
class RunRequest(BaseModel):
language: str
code: str
stdin: Optional[str] = ""
@app.get("/challenges")
def list_challenges():
"""Returns a list of all challenge details."""
challenges = loader.load_all()
return {"challenges": [c.to_dict(challenge_id=k) for k, c in sorted(challenges.items())]}
@app.get("/challenges/{challenge_id}")
def get_challenge(challenge_id: str):
"""Returns details of a single challenge."""
challenges = loader.load_all()
if challenge_id not in challenges:
raise HTTPException(status_code=404, detail=f"Challenge '{challenge_id}' not found.")
return challenges[challenge_id].to_dict(challenge_id=challenge_id)
@app.post("/lint")
def lint_code(req: LintRequest):
"""Runs linting on user code using Core Linter."""
res = linter.lint(req.language, req.code)
if res.get("stderr") == f"Language '{req.language}' is not supported.":
raise HTTPException(status_code=404, detail=res["stderr"])
return res
@app.post("/run")
def run_code(req: RunRequest):
"""Executes user code using Core Executor."""
res = executor.run(req.language, req.code, stdin=req.stdin or "")
if res.get("stderr") == f"Language '{req.language}' is not supported.":
raise HTTPException(status_code=404, detail=res["stderr"])
return res
from src.core.mentor import mentor
from src.core.generator import generator
class GenerateChallengeRequest(BaseModel):
prompt: str
difficulty: Optional[str] = "Medium"
language: Optional[str] = "Python"
subject: Optional[str] = "General"
class SaveChallengeRequest(BaseModel):
filename: str
markdown: str
@app.post("/guide")
async def guide_code(req: GuideRequest):
"""Provides mentor guidance based on user's code and question for a challenge."""
challenges = loader.load_all()
if req.challenge_id not in challenges:
raise HTTPException(status_code=404, detail=f"Challenge '{req.challenge_id}' not found.")
challenge = challenges[req.challenge_id]
guidance = await mentor.get_guidance_async(
challenge, req.code, user_question=req.question or ""
)
return {
"challenge_id": req.challenge_id,
"status": guidance.get("status"),
"mentor_response": guidance.get("mentor_response"),
}
@app.post("/challenges/generate")
async def generate_challenge_endpoint(req: GenerateChallengeRequest):
"""Generates a new challenge markdown document via LLM."""
res = await generator.generate_challenge_async(
prompt_text=req.prompt,
difficulty=req.difficulty or "Medium",
language=req.language or "Python",
subject=req.subject or "General",
)
if res.get("status") == "error":
raise HTTPException(status_code=500, detail=res.get("message"))
return res
@app.post("/challenges/save")
def save_challenge_endpoint(req: SaveChallengeRequest):
"""Saves generated markdown challenge content to disk."""
res = generator.save_challenge(req.filename, req.markdown)
if res.get("status") == "error":
raise HTTPException(status_code=500, detail=res.get("message"))
return res
from src.core.handbook import handbook_service
class HandbookExampleRequest(BaseModel):
language: str
topic_id: str
topic_title: str
@app.get("/handbook/catalog/{language}")
def get_handbook_catalog_endpoint(language: str):
"""Returns W3Schools-style syntax handbook catalog (functions & subjects) for specified language."""
catalog = handbook_service.get_catalog(language)
return {"language": language, "catalog": catalog}
@app.get("/handbook/topics/{language}")
def get_handbook_topics_endpoint(language: str):
"""Returns syntax handbook catalog for backwards compatibility."""
catalog = handbook_service.get_catalog(language)
return {"language": language, "topics": catalog.get("functions", []) + catalog.get("subjects", [])}
@app.post("/handbook/example")
async def get_handbook_example_endpoint(req: HandbookExampleRequest):
"""Generates an LLM code example for a handbook reference topic."""
res = await handbook_service.generate_example_async(
language=req.language,
topic_id=req.topic_id,
topic_title=req.topic_title,
)
if res.get("status") == "error":
raise HTTPException(status_code=500, detail=res.get("message"))
return res
if __name__ == "__main__":
import uvicorn
from src.core.config import config
uvicorn.run("src.api.main:app", host=config.web_host, port=config.web_port, reload=True)