Download backend/main.py from TalimBot/talimbot: direct link, hf CLI and curl.
- Browser
- Download file 17.1 kB
-
https://huggingface.co/spaces/TalimBot/talimbot/resolve/main/backend/main.py
- Command line
-
hf download hf://spaces/TalimBot/talimbot/backend/main.py
-
curl -L -o main.py https://huggingface.co/spaces/TalimBot/talimbot/resolve/main/backend/main.py
17.1 kB
| from fastapi import FastAPI, HTTPException | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.staticfiles import StaticFiles | |
| from fastapi.responses import FileResponse | |
| from pydantic import BaseModel | |
| from typing import List, Optional, Dict, Any | |
| import json | |
| import os | |
| from pathlib import Path | |
| from dotenv import load_dotenv | |
| # Load environment variables from .env file (for local development) | |
| load_dotenv() | |
| app = FastAPI() | |
| # CORS configuration - allow all origins for flexibility | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| # Data file path | |
| DATA_FILE = Path(__file__).parent / "data" / "students.json" | |
| DATA_FILE.parent.mkdir(exist_ok=True) | |
| # Pydantic models | |
| class Student(BaseModel): | |
| studentNumber: str | |
| name: str | |
| nationalCode: str = "" | |
| mbti: Optional[str] = None | |
| learningStyle: Optional[str] = None | |
| ams: Optional[str] = None | |
| cooperative: Optional[str] = None | |
| grade: float | |
| preferredStudents: List[str] = [] | |
| group: Optional[int] = None | |
| class StudentUpdate(BaseModel): | |
| mbti: Optional[str] = None | |
| learningStyle: Optional[str] = None | |
| ams: Optional[str] = None | |
| cooperative: Optional[str] = None | |
| preferredStudents: Optional[List[str]] = None | |
| class GroupingRequest(BaseModel): | |
| courseName: str | |
| class TeacherAuthRequest(BaseModel): | |
| password: str | |
| class StudentAuthRequest(BaseModel): | |
| studentNumber: str | |
| nationalCode: str | |
| class SystemData(BaseModel): | |
| students: List[Student] | |
| courseName: str = "" | |
| groupingComplete: bool = False | |
| groupingResults: Optional[Dict[str, Any]] = None | |
| resultsVisible: bool = False | |
| teacherPassword: str = "teacher123" | |
| # Initialize data | |
| def load_data() -> SystemData: | |
| if DATA_FILE.exists(): | |
| with open(DATA_FILE, 'r', encoding='utf-8') as f: | |
| data = json.load(f) | |
| return SystemData(**data) | |
| else: | |
| # Initialize with real 30 students from class data | |
| initial_students = [ | |
| Student(studentNumber='S001', name='یاسمن آدینه پور', nationalCode='929986644', grade=18.77), | |
| Student(studentNumber='S002', name='پریا احمدزاده', nationalCode='980085330', grade=17.28), | |
| Student(studentNumber='S003', name='فاطمه اکبرزاده', nationalCode='970154550', grade=16.71), | |
| Student(studentNumber='S004', name='آناهیتا الهی مهر', nationalCode='26425955', grade=19.05), | |
| Student(studentNumber='S005', name='مریم امیری', nationalCode='980093341', grade=18.87), | |
| Student(studentNumber='S006', name='باران برادران رحیمی', nationalCode='960043985', grade=19.07), | |
| Student(studentNumber='S007', name='مایسا بصیری امین', nationalCode='960089446', grade=19.33), | |
| Student(studentNumber='S008', name='دلارام ثابت عهد', nationalCode='960125620', grade=19.55), | |
| Student(studentNumber='S009', name='شاینا جان محمدی', nationalCode='960068041', grade=19.47), | |
| Student(studentNumber='S010', name='آیدا جوان', nationalCode='95112313', grade=16.77), | |
| Student(studentNumber='S011', name='سارینا حاجی آبادی', nationalCode='999216751', grade=16.08), | |
| Student(studentNumber='S012', name='هستی حسن پور جوان', nationalCode='960074198', grade=19.55), | |
| Student(studentNumber='S013', name='فاطمه حسینی', nationalCode='2400410259', grade=19.07), | |
| Student(studentNumber='S014', name='غزل خسروی', nationalCode='929995767', grade=15.05), | |
| Student(studentNumber='S015', name='غزل ذباح', nationalCode='960110186', grade=19.25), | |
| Student(studentNumber='S016', name='نازنین زهرا راشکی', nationalCode='3661516087', grade=17.02), | |
| Student(studentNumber='S017', name='ویونا روح نواز', nationalCode='314458344', grade=18.70), | |
| Student(studentNumber='S018', name='روژینا سعادتی', nationalCode='960051023', grade=18.20), | |
| Student(studentNumber='S019', name='ترنم شعبانی', nationalCode='950083100', grade=19.37), | |
| Student(studentNumber='S020', name='ستایش شفابخش', nationalCode='960126899', grade=18.36), | |
| Student(studentNumber='S021', name='فاطمه شیرزادخان', nationalCode='980120756', grade=19.33), | |
| Student(studentNumber='S022', name='آرزو علی جوی', nationalCode='960054316', grade=17.98), | |
| Student(studentNumber='S023', name='آناهیتا قنادزاده', nationalCode='960089836', grade=18.84), | |
| Student(studentNumber='S024', name='نیایش کارگر', nationalCode='929956052', grade=17.74), | |
| Student(studentNumber='S025', name='باران کبریایی نسب', nationalCode='980119588', grade=18.82), | |
| Student(studentNumber='S026', name='زینب کیانوش', nationalCode='970072678', grade=18.58), | |
| Student(studentNumber='S027', name='ستایش محمودی', nationalCode='929904656', grade=19.33), | |
| Student(studentNumber='S028', name='ستایش مشتاقی', nationalCode='361282217', grade=17.67), | |
| Student(studentNumber='S029', name='مهتاب معلمی', nationalCode='960070265', grade=18.56), | |
| Student(studentNumber='S030', name='باران وحدتی', nationalCode='929916913', grade=15.02), | |
| ] | |
| data = SystemData(students=initial_students) | |
| save_data(data) | |
| return data | |
| def save_data(data: SystemData): | |
| with open(DATA_FILE, 'w', encoding='utf-8') as f: | |
| json.dump(data.dict(), f, ensure_ascii=False, indent=2) | |
| # API Endpoints | |
| def read_root(): | |
| return {"message": "TalimBot API is running"} | |
| def get_all_students(): | |
| data = load_data() | |
| return {"students": data.students} | |
| def get_student(student_number: str): | |
| # Return demo account if requested | |
| if student_number == "DEMO": | |
| return Student( | |
| studentNumber="DEMO", | |
| name="پریناز عاکف", | |
| nationalCode="0921111111", | |
| grade=0.0, | |
| mbti=None, | |
| learningStyle=None, | |
| ams=None, | |
| cooperative=None, | |
| preferredStudents=[], | |
| group=None | |
| ) | |
| data = load_data() | |
| student = next((s for s in data.students if s.studentNumber == student_number), None) | |
| if not student: | |
| raise HTTPException(status_code=404, detail="Student not found") | |
| return student | |
| def update_student(student_number: str, updates: StudentUpdate): | |
| # Silently ignore updates to demo account (pretend it worked) | |
| if student_number == "DEMO": | |
| demo_student = Student( | |
| studentNumber="DEMO", | |
| name="پریناز عاکف", | |
| nationalCode="0921111111", | |
| grade=0.0, | |
| mbti=updates.mbti, | |
| learningStyle=updates.learningStyle, | |
| ams=updates.ams, | |
| cooperative=updates.cooperative, | |
| preferredStudents=updates.preferredStudents or [], | |
| group=None | |
| ) | |
| return {"success": True, "student": demo_student} | |
| data = load_data() | |
| student = next((s for s in data.students if s.studentNumber == student_number), None) | |
| if not student: | |
| raise HTTPException(status_code=404, detail="Student not found") | |
| # Update only provided fields | |
| update_dict = updates.dict(exclude_unset=True) | |
| for key, value in update_dict.items(): | |
| setattr(student, key, value) | |
| save_data(data) | |
| return {"success": True, "student": student} | |
| class GroupingRequest(BaseModel): | |
| courseName: str | |
| async def perform_grouping(request: GroupingRequest): | |
| data = load_data() | |
| # Get API key from environment variable | |
| api_key = os.getenv("OPENROUTER_API_KEY") | |
| if not api_key: | |
| raise HTTPException( | |
| status_code=500, | |
| detail="OpenRouter API key not configured. Please set OPENROUTER_API_KEY in your environment variables or hosting platform settings" | |
| ) | |
| # Get students with complete info (mbti and learningStyle required) | |
| students_with_info = [s for s in data.students if s.mbti and s.learningStyle] | |
| if len(students_with_info) == 0: | |
| raise HTTPException(status_code=400, detail="No students have completed their profiles yet") | |
| try: | |
| # Import grouping logic | |
| from grouping_logic import group_students_with_ai | |
| # group_students_with_ai is now synchronous (uses requests library) | |
| # Run it in a thread pool to avoid blocking | |
| import asyncio | |
| from concurrent.futures import ThreadPoolExecutor | |
| loop = asyncio.get_event_loop() | |
| with ThreadPoolExecutor() as executor: | |
| grouping_result = await loop.run_in_executor( | |
| executor, | |
| lambda: group_students_with_ai(students_with_info, request.courseName, api_key) | |
| ) | |
| # Apply grouping to students | |
| for student in data.students: | |
| student.group = None | |
| for group in grouping_result["groups"]: | |
| for student_number in group["students"]: | |
| student = next((s for s in data.students if s.studentNumber == student_number), None) | |
| if student: | |
| student.group = group["groupNumber"] | |
| data.courseName = request.courseName | |
| data.groupingComplete = True | |
| data.groupingResults = grouping_result | |
| data.resultsVisible = False # Teacher must manually show results | |
| save_data(data) | |
| return {"success": True, "results": grouping_result} | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| def get_grouping_status(): | |
| data = load_data() | |
| students_with_info = [s for s in data.students if s.mbti and s.learningStyle] | |
| students_grouped = [s for s in data.students if s.group is not None] | |
| return { | |
| "totalStudents": len(data.students), | |
| "studentsWithCompleteInfo": len(students_with_info), | |
| "studentsGrouped": len(students_grouped), | |
| "groupingComplete": data.groupingComplete, | |
| "resultsVisible": data.resultsVisible, | |
| "courseName": data.courseName, | |
| "groups": data.groupingResults.get("groups", []) if data.groupingResults else [] | |
| } | |
| class PasswordRequest(BaseModel): | |
| password: str | |
| def toggle_results_visibility(request: PasswordRequest): | |
| data = load_data() | |
| if request.password != data.teacherPassword: | |
| raise HTTPException(status_code=403, detail="Invalid password") | |
| data.resultsVisible = not data.resultsVisible | |
| save_data(data) | |
| return {"success": True, "resultsVisible": data.resultsVisible} | |
| def reset_grouping(request: PasswordRequest): | |
| data = load_data() | |
| if request.password != data.teacherPassword: | |
| raise HTTPException(status_code=403, detail="Invalid password") | |
| # Clear ONLY grouping-related data, keep student profiles intact | |
| for student in data.students: | |
| student.group = None | |
| data.groupingComplete = False | |
| data.groupingResults = None | |
| data.resultsVisible = False | |
| data.courseName = "" | |
| save_data(data) | |
| return {"success": True} | |
| def reset_all_data(request: PasswordRequest): | |
| data = load_data() | |
| if request.password != data.teacherPassword: | |
| raise HTTPException(status_code=403, detail="Invalid password") | |
| # Clear ALL student data fields AND grouping | |
| for student in data.students: | |
| student.group = None | |
| student.mbti = None | |
| student.learningStyle = None | |
| student.ams = None | |
| student.cooperative = None | |
| student.preferredStudents = [] | |
| data.groupingComplete = False | |
| data.groupingResults = None | |
| data.resultsVisible = False | |
| data.courseName = "" | |
| save_data(data) | |
| return {"success": True} | |
| def check_teacher_password(request: TeacherAuthRequest): | |
| data = load_data() | |
| return {"valid": request.password == data.teacherPassword} | |
| def authenticate_student(request: StudentAuthRequest): | |
| # Special demo account - not stored in database | |
| # Check for national code without leading zero (frontend strips it) | |
| if request.nationalCode == "921111111": | |
| demo_student = Student( | |
| studentNumber="DEMO", | |
| name="پریناز عاکف", | |
| nationalCode="921111111", | |
| grade=0.0, | |
| mbti=None, | |
| learningStyle=None, | |
| ams=None, | |
| cooperative=None, | |
| preferredStudents=[], | |
| group=None | |
| ) | |
| return {"valid": True, "student": demo_student} | |
| data = load_data() | |
| student = next((s for s in data.students if s.studentNumber == request.studentNumber), None) | |
| if not student: | |
| raise HTTPException(status_code=404, detail="Student not found") | |
| if student.nationalCode != request.nationalCode: | |
| raise HTTPException(status_code=401, detail="Invalid national code") | |
| return {"valid": True, "student": student} | |
| class NationalCodeAuthRequest(BaseModel): | |
| nationalCode: str | |
| def authenticate_student_by_nationalcode(request: NationalCodeAuthRequest): | |
| # Special demo account - not stored in database | |
| # Check for national code without leading zero (frontend strips it) | |
| if request.nationalCode == "921111111": | |
| demo_student = Student( | |
| studentNumber="DEMO", | |
| name="پریناز عاکف", | |
| nationalCode="921111111", | |
| grade=0.0, | |
| mbti=None, | |
| learningStyle=None, | |
| ams=None, | |
| cooperative=None, | |
| preferredStudents=[], | |
| group=None | |
| ) | |
| return {"valid": True, "student": demo_student} | |
| data = load_data() | |
| # Find student by national code (without leading zero) | |
| student = next((s for s in data.students if s.nationalCode == request.nationalCode), None) | |
| if not student: | |
| raise HTTPException(status_code=404, detail="کد ملی در سیستم یافت نشد") | |
| return {"valid": True, "student": student} | |
| def get_student_group(student_number: str): | |
| # Demo account has no group | |
| if student_number == "DEMO": | |
| raise HTTPException(status_code=404, detail="Demo account is not part of any group") | |
| data = load_data() | |
| if not data.resultsVisible: | |
| raise HTTPException(status_code=403, detail="Results are not yet visible") | |
| student = next((s for s in data.students if s.studentNumber == student_number), None) | |
| if not student: | |
| raise HTTPException(status_code=404, detail="Student not found") | |
| if student.group is None: | |
| raise HTTPException(status_code=404, detail="Student not assigned to a group yet") | |
| # Find all students in the same group | |
| group_members = [s for s in data.students if s.group == student.group] | |
| # Find the group details from results | |
| group_info = None | |
| if data.groupingResults: | |
| for g in data.groupingResults.get("groups", []): | |
| if g["groupNumber"] == student.group: | |
| group_info = g | |
| break | |
| return { | |
| "groupNumber": student.group, | |
| "members": group_members, | |
| "reasoning": group_info.get("reasoning", "") if group_info else "", | |
| "courseName": data.courseName | |
| } | |
| def get_data_backup(): | |
| """Download complete student data as JSON backup for safekeeping""" | |
| data = load_data() | |
| return data.dict() | |
| # ============================================ | |
| # STATIC FILE SERVING (Frontend HTML/CSS/JS) | |
| # ============================================ | |
| # Define static directory path | |
| STATIC_DIR = Path(__file__).parent / "static" | |
| # Mount static files FIRST - this handles all non-API routes | |
| app.mount("/", StaticFiles(directory=str(STATIC_DIR), html=True), name="static") | |
| if __name__ == "__main__": | |
| import uvicorn | |
| uvicorn.run(app, host="0.0.0.0", port=8000) | |