# backend.py import uvicorn from fastapi import FastAPI, UploadFile, File, Form from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse, StreamingResponse, FileResponse, HTMLResponse from fastapi.staticfiles import StaticFiles import tempfile, io, os, re, json, base64, hashlib from typing import List, Tuple, Dict import fitz # PyMuPDF import requests import pandas as pd from docx import Document from io import BytesIO from sqlalchemy import create_engine, Column, Integer, String, Text, DateTime, Boolean from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker import datetime from urllib.parse import quote_plus MYSQL_USER = "root" MYSQL_PASSWORD = "root@MySQL4admin" MYSQL_HOST = "localhost" MYSQL_PORT = 3306 MYSQL_DB = "mcq_db" # URL encode the password encoded_password = quote_plus(MYSQL_PASSWORD) from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker, declarative_base import os # Use SQLite instead of MySQL DATABASE_URL = "sqlite:///./app.db" engine = create_engine( DATABASE_URL, connect_args={"check_same_thread": False} # Needed for SQLite ) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) from sqlalchemy.orm import declarative_base Base = declarative_base() class Question(Base): __tablename__ = "questions" id = Column(Integer, primary_key=True, index=True) topic = Column(String(255)) type = Column(String(20)) # MCQ / Descriptive question = Column(Text, nullable=False) option_a = Column(Text) option_b = Column(Text) option_c = Column(Text) option_d = Column(Text) answer = Column(Text) descriptive_answer = Column(Text) difficulty = Column(String(10)) created_at = Column(DateTime, default=datetime.datetime.utcnow) flagged = Column(Boolean, default=None) # Change from True to None # Create table if not exists Base.metadata.create_all(bind=engine) import json def save_questions_to_db(results: dict): """ Save parsed results into the questions table. Expected `results` structure: { "Topic Name": { "mcqs": [ { "question": "...", "options": [...], "answer": "A", "difficulty": 2 }, ... ], "descriptive": [ { "question": "...", "answer": "...", "difficulty": 3 }, ... ] }, ... } The function is defensive: it skips entries missing the required 'question' text and logs skipped items. """ db = SessionLocal() saved = 0 skipped = 0 try: # optional: quick debug dump if things keep failing # print("DEBUG save_questions_to_db incoming:", json.dumps(results)[:2000]) for topic, data in (results or {}).items(): # normalize topic value (some callers send topic None) topic_val = topic if topic is not None else None # Save MCQs for mcq in data.get("mcqs", []) if data else []: # robust extraction of fields question_text = mcq.get("question") or mcq.get("q") or None if not question_text or not str(question_text).strip(): print("⚠️ Skipping MCQ with no question text:", mcq) skipped += 1 continue opts = mcq.get("options", []) or [] option_a = opts[0] if len(opts) > 0 else mcq.get("option_a") or None option_b = opts[1] if len(opts) > 1 else mcq.get("option_b") or None option_c = opts[2] if len(opts) > 2 else mcq.get("option_c") or None option_d = opts[3] if len(opts) > 3 else mcq.get("option_d") or None answer = mcq.get("answer") or mcq.get("ans") or None difficulty = mcq.get("difficulty") difficulty = str(difficulty) if difficulty is not None else None q = Question( topic=topic_val, type="MCQ", question=str(question_text).strip(), option_a=option_a, option_b=option_b, option_c=option_c, option_d=option_d, answer=answer, descriptive_answer=None, difficulty=difficulty, created_at=datetime.datetime.utcnow(), flagged=None # pending by default ) db.add(q) saved += 1 # Save Descriptive for dq in data.get("descriptive", []) if data else []: question_text = dq.get("question") or dq.get("q") or None if not question_text or not str(question_text).strip(): print("⚠️ Skipping Descriptive with no question text:", dq) skipped += 1 continue descriptive_answer = dq.get("answer") or dq.get("descriptive_answer") or None difficulty = dq.get("difficulty") difficulty = str(difficulty) if difficulty is not None else None q = Question( topic=topic_val, type="Descriptive", question=str(question_text).strip(), option_a=None, option_b=None, option_c=None, option_d=None, answer=None, descriptive_answer=descriptive_answer, difficulty=difficulty, created_at=datetime.datetime.utcnow(), flagged=None ) db.add(q) saved += 1 db.commit() return {"status": "success", "saved": saved, "skipped": skipped} except Exception as e: db.rollback() print("❌ DB error in save_questions_to_db:", e) # optional: raise or return an error dict return {"status": "error", "error": str(e)} finally: db.close() # ---------- CONFIG ---------- from dotenv import load_dotenv load_dotenv() # OpenRouter Configuration OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY", "") # Set your API key in environment variable OPENROUTER_API_URL = "https://openrouter.ai/api/v1/chat/completions" OPENROUTER_MODEL = "meta-llama/llama-3.3-70b-instruct:free" # Free model, you can change this # Headers for OpenRouter API OPENROUTER_HEADERS = { "Authorization": f"Bearer {OPENROUTER_API_KEY}", "Content-Type": "application/json", "HTTP-Referer": "http://localhost:8000", # Optional: your site URL "X-Title": "MCQ Generator" # Optional: your app name } MODEL = OPENROUTER_MODEL HOST = "127.0.0.1" PORT = 8000 # ---------- FASTAPI ---------- app = FastAPI() # HTML_PATH = "design.html" # @app.get("/") # async def read_root(): # return FileResponse(HTML_PATH) app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"], allow_credentials=True) # Serve static files (put design.html and any assets inside ./static/) static_dir = os.path.join(os.path.dirname(__file__), "static") if not os.path.isdir(static_dir): os.makedirs(static_dir, exist_ok=True) app.mount("/static", StaticFiles(directory=static_dir), name="static") # Serve design.html at root @app.get("/", response_class=HTMLResponse) async def index(): fpath = os.path.join(static_dir, "design.html") if os.path.exists(fpath): return HTMLResponse(open(fpath, "r", encoding="utf-8").read()) return HTMLResponse("