diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6496626 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +backend/.env +backend/venv +backend/mem0_client.py +backend/dashboard.py \ No newline at end of file diff --git a/README.md b/README.md index 284b10c..c155470 100644 --- a/README.md +++ b/README.md @@ -1 +1,5 @@ -# AITutor \ No newline at end of file +# AITutor backend + +GET /api/survey/questions - get all questions +GET /api/survey/questions/{category} - get questions by category +POST /api/survey/submit - submit answers \ No newline at end of file diff --git a/backend/__pycache__/app.cpython-311.pyc b/backend/__pycache__/app.cpython-311.pyc new file mode 100644 index 0000000..baa17e9 Binary files /dev/null and b/backend/__pycache__/app.cpython-311.pyc differ diff --git a/backend/__pycache__/config.cpython-311.pyc b/backend/__pycache__/config.cpython-311.pyc new file mode 100644 index 0000000..7efef88 Binary files /dev/null and b/backend/__pycache__/config.cpython-311.pyc differ diff --git a/backend/__pycache__/main.cpython-311.pyc b/backend/__pycache__/main.cpython-311.pyc new file mode 100644 index 0000000..d8d7364 Binary files /dev/null and b/backend/__pycache__/main.cpython-311.pyc differ diff --git a/backend/__pycache__/mem0_client.cpython-311.pyc b/backend/__pycache__/mem0_client.cpython-311.pyc new file mode 100644 index 0000000..bf3391e Binary files /dev/null and b/backend/__pycache__/mem0_client.cpython-311.pyc differ diff --git a/backend/app/__pycache__/main.cpython-311.pyc b/backend/app/__pycache__/main.cpython-311.pyc new file mode 100644 index 0000000..6d59633 Binary files /dev/null and b/backend/app/__pycache__/main.cpython-311.pyc differ diff --git a/backend/app/api/__init__.py b/backend/app/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/api/__pycache__/__init__.cpython-311.pyc b/backend/app/api/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..67ddaa9 Binary files /dev/null and b/backend/app/api/__pycache__/__init__.cpython-311.pyc differ diff --git a/backend/app/api/__pycache__/survey.cpython-311.pyc b/backend/app/api/__pycache__/survey.cpython-311.pyc new file mode 100644 index 0000000..efe467a Binary files /dev/null and b/backend/app/api/__pycache__/survey.cpython-311.pyc differ diff --git a/backend/app/api/survey.py b/backend/app/api/survey.py new file mode 100644 index 0000000..1951e4d --- /dev/null +++ b/backend/app/api/survey.py @@ -0,0 +1,73 @@ +from fastapi import APIRouter, HTTPException, Depends +from typing import List +from app.models.survey import SurveyQuestion, UserSurveyResponse +from app.services.survey import SurveyService +from app.services.mem0 import Mem0Service +from config import get_settings + +router = APIRouter(prefix="/survey", tags=["survey"]) + +@router.get("/questions", response_model=List[SurveyQuestion]) +async def get_survey_questions( + survey_service: SurveyService = Depends(lambda: SurveyService()) +): + """ + Retrieves all available survey questions. + Returns a list of questions with their options. + """ + return survey_service.get_all_questions() + +@router.get("/questions/{category}", response_model=List[SurveyQuestion]) +async def get_questions_by_category( + category: str, + survey_service: SurveyService = Depends(lambda: SurveyService()) +): + """ + Retrieves questions for a specific category. + Args: + category: The category of questions to retrieve (e.g., 'learning_format', 'learning_pace') + Returns: + List of questions for the specified category + """ + questions = survey_service.get_questions_by_category(category) + if not questions: + raise HTTPException( + status_code=404, + detail=f"No questions found for category: {category}" + ) + return questions + +@router.post("/submit") +async def submit_survey( + response: UserSurveyResponse, + survey_service: SurveyService = Depends(lambda: SurveyService()), + settings = Depends(get_settings) +): + """ + Submits survey responses and stores them in Mem0.ai. + Args: + response: User's survey responses including user_id and answers + Returns: + Processed survey data and initial learning path + """ + # Process survey responses + processed_data = survey_service.process_answers(response.dict()) + + # Store in Mem0.ai and get learning path + mem0_service = Mem0Service(api_key=settings.MEM0_API_KEY) + try: + result = await mem0_service.store_survey_response( + response.user_id, + processed_data + ) + return { + "status": "success", + "message": "Survey responses successfully processed", + "learning_path": result, + "user_id": response.user_id + } + except Exception as e: + raise HTTPException( + status_code=400, + detail=f"Failed to process survey: {str(e)}" + ) \ No newline at end of file diff --git a/backend/app/api/tutor.py b/backend/app/api/tutor.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/main.py b/backend/app/main.py new file mode 100644 index 0000000..0366dd0 --- /dev/null +++ b/backend/app/main.py @@ -0,0 +1,49 @@ +from fastapi import FastAPI +from app.api import survey +from fastapi.middleware.cors import CORSMiddleware +from config import get_settings + +# Initialize FastAPI app +app = FastAPI( + title="AI Tutor API", + description="API for personalized JavaScript learning experience", + version="1.0.0" +) + +# Configure CORS +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], # In production, replace with actual frontend domain + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# Include routers +app.include_router(survey.router, prefix="/api") + +@app.get("/") +async def root(): + """ + Root endpoint returning API information + """ + return { + "message": "Welcome to AI Tutor API", + "version": "1.0.0", + "documentation": "/docs", + "health_check": "/health" + } + +@app.get("/health") +async def health_check(): + """ + Health check endpoint for monitoring + """ + return { + "status": "healthy", + "api_version": "1.0.0" + } + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=8000) \ No newline at end of file diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/models/__pycache__/__init__.cpython-311.pyc b/backend/app/models/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..417097c Binary files /dev/null and b/backend/app/models/__pycache__/__init__.cpython-311.pyc differ diff --git a/backend/app/models/__pycache__/survey.cpython-311.pyc b/backend/app/models/__pycache__/survey.cpython-311.pyc new file mode 100644 index 0000000..6006d21 Binary files /dev/null and b/backend/app/models/__pycache__/survey.cpython-311.pyc differ diff --git a/backend/app/models/survey.py b/backend/app/models/survey.py new file mode 100644 index 0000000..d917d6b --- /dev/null +++ b/backend/app/models/survey.py @@ -0,0 +1,20 @@ +from pydantic import BaseModel +from typing import List, Optional + +class SurveyOption(BaseModel): + id: int + text: str + +class SurveyQuestion(BaseModel): + id: int + question: str + options: List[SurveyOption] + category: str + +class UserAnswer(BaseModel): + question_id: int + selected_option_id: int + +class UserSurveyResponse(BaseModel): + user_id: str + answers: List[UserAnswer] \ No newline at end of file diff --git a/backend/app/models/tutor.py b/backend/app/models/tutor.py new file mode 100644 index 0000000..d7cdc3f --- /dev/null +++ b/backend/app/models/tutor.py @@ -0,0 +1,22 @@ +from pydantic import BaseModel +from typing import Optional, Dict, List + +class LearningPreferences(BaseModel): + learning_format: str + weekly_time: str + learning_pace: str + theory_vs_practice: str + independence_level: str + +class LearningSession(BaseModel): + user_id: str + topic: str + session_type: str + completion_status: str + feedback: Optional[str] + +class LearningPath(BaseModel): + user_id: str + current_topic: str + next_topics: List[str] + recommendations: Dict[str, str] \ No newline at end of file diff --git a/backend/app/services/__init__.py b/backend/app/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/services/__pycache__/__init__.cpython-311.pyc b/backend/app/services/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..b486d92 Binary files /dev/null and b/backend/app/services/__pycache__/__init__.cpython-311.pyc differ diff --git a/backend/app/services/__pycache__/mem0.cpython-311.pyc b/backend/app/services/__pycache__/mem0.cpython-311.pyc new file mode 100644 index 0000000..1c7b8d6 Binary files /dev/null and b/backend/app/services/__pycache__/mem0.cpython-311.pyc differ diff --git a/backend/app/services/__pycache__/survey.cpython-311.pyc b/backend/app/services/__pycache__/survey.cpython-311.pyc new file mode 100644 index 0000000..2569b93 Binary files /dev/null and b/backend/app/services/__pycache__/survey.cpython-311.pyc differ diff --git a/backend/app/services/mem0.py b/backend/app/services/mem0.py new file mode 100644 index 0000000..c8272ca --- /dev/null +++ b/backend/app/services/mem0.py @@ -0,0 +1,58 @@ +import aiohttp +from typing import Dict, Any, Optional +import json + +class Mem0Service: + def __init__(self, api_key: str, base_url: str = "https://api.mem0.ai/v1"): + self.api_key = api_key + self.base_url = base_url + self.headers = { + "Authorization": f"Bearer {api_key}", + "Content-Type": "application/json" + } + + async def store_survey_response(self, user_id: str, survey_data: Dict) -> Dict[str, Any]: + """Stores survey responses in Mem0.ai""" + prompt = f""" + Analyze the learning preferences for user {user_id}: + {json.dumps(survey_data, indent=2)} + + Based on these preferences, create a personalized learning path and provide recommendations. + Include: + 1. Suggested learning format + 2. Recommended pace + 3. Theory/practice balance + 4. Initial topics to cover + """ + + async with aiohttp.ClientSession() as session: + async with session.post( + f"{self.base_url}/store", + headers=self.headers, + json={ + "user_id": user_id, + "content": survey_data, + "metadata": {"type": "survey_response"} + } + ) as response: + return await response.json() + + async def get_learning_path(self, user_id: str) -> Dict[str, Any]: + """Retrieves personalized learning path based on survey responses""" + async with aiohttp.ClientSession() as session: + async with session.get( + f"{self.base_url}/users/{user_id}/learning_path", + headers=self.headers + ) as response: + return await response.json() + + async def update_progress(self, user_id: str, progress_data: Dict) -> Dict[str, Any]: + """Updates learning progress and gets recommendations""" + async with aiohttp.ClientSession() as session: + async with session.post( + f"{self.base_url}/users/{user_id}/progress", + headers=self.headers, + json=progress_data + ) as response: + return await response.json() + \ No newline at end of file diff --git a/backend/app/services/survey.py b/backend/app/services/survey.py new file mode 100644 index 0000000..5f9018e --- /dev/null +++ b/backend/app/services/survey.py @@ -0,0 +1,83 @@ +from typing import List, Dict +from app.models.survey import SurveyQuestion, SurveyOption + +class SurveyService: + def __init__(self): + self.questions = [ + SurveyQuestion( + id=1, + category="learning_format", + question="What's the best way for you to receive information during learning?", + options=[ + SurveyOption(id=1, text="Text-based guide with examples"), + SurveyOption(id=2, text="Video with step-by-step explanations"), + SurveyOption(id=3, text="Practical tasks to solve on your own"), + SurveyOption(id=4, text="Combined format (all options together)") + ] + ), + SurveyQuestion( + id=2, + category="learning_pace", + question="How much time per week can you dedicate to learning?", + options=[ + SurveyOption(id=1, text="Less than 3 hours"), + SurveyOption(id=2, text="3-5 hours"), + SurveyOption(id=3, text="5-10 hours"), + SurveyOption(id=4, text="More than 10 hours") + ] + ), + SurveyQuestion( + id=3, + category="learning_style", + question="What learning pace do you prefer?", + options=[ + SurveyOption(id=1, text="Fast-paced (intensive material coverage)"), + SurveyOption(id=2, text="Balanced (mix of learning and breaks)"), + SurveyOption(id=3, text="Slow-paced (step-by-step learning)") + ] + ), + SurveyQuestion( + id=4, + category="theory_practice", + question="How do you prefer to learn new concepts?", + options=[ + SurveyOption(id=1, text="Read theory first, then practice"), + SurveyOption(id=2, text="Learn by doing, reference theory as needed") + ] + ) + ] + + def get_all_questions(self) -> List[SurveyQuestion]: + """Retrieves all survey questions""" + return self.questions + + def get_questions_by_category(self, category: str) -> List[SurveyQuestion]: + """Retrieves questions filtered by category""" + return [q for q in self.questions if q.category == category] + + def validate_answer(self, question_id: int, option_id: int) -> bool: + """Validates if the answer option exists for the given question""" + question = next((q for q in self.questions if q.id == question_id), None) + if not question: + return False + return any(opt.id == option_id for opt in question.options) + + def process_answers(self, answers: Dict) -> Dict: + """Processes survey answers and formats data for Mem0.ai storage""" + processed_data = { + "learning_preferences": {}, + "metadata": { + "timestamp": "2024-01-01T00:00:00Z", + "survey_version": "1.0" + } + } + + for answer in answers["answers"]: + question = next(q for q in self.questions if q.id == answer["question_id"]) + option = next(opt for opt in question.options if opt.id == answer["selected_option_id"]) + processed_data["learning_preferences"][question.category] = { + "selected_option": option.text, + "question_id": question.id + } + + return processed_data \ No newline at end of file diff --git a/backend/config.py b/backend/config.py new file mode 100644 index 0000000..72b376d --- /dev/null +++ b/backend/config.py @@ -0,0 +1,28 @@ +# config.py +from pydantic_settings import BaseSettings +from functools import lru_cache + +class Settings(BaseSettings): + # Mem0.ai settings + MEM0_API_KEY: str + MEM0_API_URL: str = "https://api.mem0.ai/v1" + + # Database settings (if needed later) + DATABASE_URL: str = "sqlite:///./test.db" + + # API settings + API_V1_PREFIX: str = "/api/v1" + PROJECT_NAME: str = "AI Tutor" + + # Security settings + SECRET_KEY: str + ALGORITHM: str = "HS256" + ACCESS_TOKEN_EXPIRE_MINUTES: int = 30 + + class Config: + env_file = ".env" + case_sensitive = True + +@lru_cache() +def get_settings(): + return Settings() \ No newline at end of file diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..5061159 Binary files /dev/null and b/backend/requirements.txt differ