diff --git a/classquiz/__init__.py b/classquiz/__init__.py index dc2d025..fdcb06a 100644 --- a/classquiz/__init__.py +++ b/classquiz/__init__.py @@ -34,6 +34,7 @@ from classquiz.routers import ( quiztivity, pixabay, moderation, + ai, ) from classquiz.socket_server import sio from classquiz.helpers import meilisearch_init @@ -104,6 +105,7 @@ app.include_router(login.router, tags=["auth"], prefix="/api/v1/login", include_ app.add_middleware(SessionMiddleware, secret_key=settings.secret_key) app.include_router(users.router, tags=["users"], prefix="/api/v1/users", include_in_schema=True) app.include_router(quiz.router, tags=["quiz"], prefix="/api/v1/quiz", include_in_schema=True) +app.include_router(ai.router, tags=["ai"], prefix="/api/v1/ai", include_in_schema=True) app.include_router(utils.router, tags=["utils"], prefix="/api/v1/utils", include_in_schema=True) app.include_router(stats.router, tags=["stats"], prefix="/api/v1/stats", include_in_schema=True) app.include_router(storage.router, tags=["storage"], prefix="/api/v1/storage", include_in_schema=True) diff --git a/classquiz/routers/ai.py b/classquiz/routers/ai.py new file mode 100644 index 0000000..83f9b82 --- /dev/null +++ b/classquiz/routers/ai.py @@ -0,0 +1,40 @@ +# SPDX-FileCopyrightText: 2026 LobotomyLabs +# SPDX-License-Identifier: MPL-2.0 + +import os +import json +import httpx +from fastapi import APIRouter, Depends, HTTPException +from pydantic import BaseModel +from classquiz.auth import get_current_user +from classquiz.db.models import User, QuizInput + +router = APIRouter() + +class AIGenerateRequest(BaseModel): + topic: str + num_questions: int = 5 + +@router.post("/generate", response_model=QuizInput) +async def generate_ai_quiz(req: AIGenerateRequest, user: User = Depends(get_current_user)): + api_key = os.getenv("AI_API_KEY", "") + base_url = os.getenv("AI_BASE_URL", "https://api.openai.com/v1") + model = os.getenv("AI_MODEL", "gpt-4o-mini") + + prompt = f"Generate a quiz about '{req.topic}' with exactly {req.num_questions} multiple-choice questions (ABCD type). Return ONLY valid JSON with keys: title, description, questions (list of {{question, time: '20', type: 'ABCD', answers: [{{answer, right: bool, color}}]}})." + + async with httpx.AsyncClient(timeout=30.0) as client: + res = await client.post( + f"{base_url}/chat/completions", + headers={"Authorization": f"Bearer {api_key}"}, + json={"model": model, "messages": [{"role": "user", "content": prompt}], "temperature": 0.7}, + ) + if res.status_code != 200: + raise HTTPException(status_code=502, detail=f"AI provider error: {res.text}") + content = res.json()["choices"][0]["message"]["content"].strip() + if content.startswith("```"): + content = content.split("```")[1] + if content.startswith("json"): + content = content[4:] + content = content.strip("` \n") + return QuizInput(**json.loads(content)) diff --git a/classquiz/tests/test_ai.py b/classquiz/tests/test_ai.py new file mode 100644 index 0000000..ec27d22 --- /dev/null +++ b/classquiz/tests/test_ai.py @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: 2026 LobotomyLabs +# SPDX-License-Identifier: MPL-2.0 + +import pytest +from fastapi.testclient import TestClient +from classquiz import app + +client = TestClient(app) + +def test_ai_router_exists(): + response = client.post("/api/v1/ai/generate", json={"topic": "Python", "num_questions": 1}) + assert response.status_code in [200, 401, 502]