From 990383c651e70b7f036e9c5c797087b0a87060c2 Mon Sep 17 00:00:00 2001 From: YKuts Date: Thu, 5 Dec 2024 16:03:05 +0100 Subject: [PATCH] First backend commit --- .gitignore | 4 + README.md | 6 +- backend/__pycache__/app.cpython-311.pyc | Bin 0 -> 6754 bytes backend/__pycache__/config.cpython-311.pyc | Bin 0 -> 1478 bytes backend/__pycache__/main.cpython-311.pyc | Bin 0 -> 1766 bytes .../__pycache__/mem0_client.cpython-311.pyc | Bin 0 -> 5318 bytes backend/app/__pycache__/main.cpython-311.pyc | Bin 0 -> 1774 bytes backend/app/api/__init__.py | 0 .../api/__pycache__/__init__.cpython-311.pyc | Bin 0 -> 177 bytes .../api/__pycache__/survey.cpython-311.pyc | Bin 0 -> 3844 bytes backend/app/api/survey.py | 73 +++++++++++++++ backend/app/api/tutor.py | 0 backend/app/main.py | 49 +++++++++++ backend/app/models/__init__.py | 0 .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 180 bytes .../models/__pycache__/survey.cpython-311.pyc | Bin 0 -> 1602 bytes backend/app/models/survey.py | 20 +++++ backend/app/models/tutor.py | 22 +++++ backend/app/services/__init__.py | 0 .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 182 bytes .../services/__pycache__/mem0.cpython-311.pyc | Bin 0 -> 5545 bytes .../__pycache__/survey.cpython-311.pyc | Bin 0 -> 5650 bytes backend/app/services/mem0.py | 58 ++++++++++++ backend/app/services/survey.py | 83 ++++++++++++++++++ backend/config.py | 28 ++++++ backend/requirements.txt | Bin 0 -> 4468 bytes 26 files changed, 342 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 backend/__pycache__/app.cpython-311.pyc create mode 100644 backend/__pycache__/config.cpython-311.pyc create mode 100644 backend/__pycache__/main.cpython-311.pyc create mode 100644 backend/__pycache__/mem0_client.cpython-311.pyc create mode 100644 backend/app/__pycache__/main.cpython-311.pyc create mode 100644 backend/app/api/__init__.py create mode 100644 backend/app/api/__pycache__/__init__.cpython-311.pyc create mode 100644 backend/app/api/__pycache__/survey.cpython-311.pyc create mode 100644 backend/app/api/survey.py create mode 100644 backend/app/api/tutor.py create mode 100644 backend/app/main.py create mode 100644 backend/app/models/__init__.py create mode 100644 backend/app/models/__pycache__/__init__.cpython-311.pyc create mode 100644 backend/app/models/__pycache__/survey.cpython-311.pyc create mode 100644 backend/app/models/survey.py create mode 100644 backend/app/models/tutor.py create mode 100644 backend/app/services/__init__.py create mode 100644 backend/app/services/__pycache__/__init__.cpython-311.pyc create mode 100644 backend/app/services/__pycache__/mem0.cpython-311.pyc create mode 100644 backend/app/services/__pycache__/survey.cpython-311.pyc create mode 100644 backend/app/services/mem0.py create mode 100644 backend/app/services/survey.py create mode 100644 backend/config.py create mode 100644 backend/requirements.txt 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 0000000000000000000000000000000000000000..baa17e9c82d2d1857aa57181a61305a00a868e00 GIT binary patch literal 6754 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 CNMMQp literal 0 HcmV?d00001 diff --git a/backend/__pycache__/main.cpython-311.pyc b/backend/__pycache__/main.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d8d7364fe0741a2193f2b2359f21e92a16419aee GIT binary patch literal 1766 zcmZux&2Jk;6rb4-uanr0V+T79fi8j`jD(#6LI?pO6eyu;DNQ9LE7EG&nK+y5hc&xS z8=nG1&|?lraOi>VHoG`dl}vRpnHC7z@ywwLo*-$%2s}l^*-AE2d21%q zN#VG7D1jeH+Js*rn_V19=@7Cz0!bU0-A9L@9%T!tQ-m}B-?#jn@7#ZUOLUf0wnZGQ z0jyP>a;FT;l(*)x18APilNXL_lNxLMgyg7_H3SDkI@bL-Z^et8Bu)X zmGC{t(|LgF2=ltcL?SX#i26WTL(Saa%Crvcw(o~J^+?aRy-;T~?5BBg0&EW`aLo|H z7Pf=>*VM6mm+GOfU*wDNH!x7!#J7TTtqnms;U;9MxikpOE@iwH<0AarcOgHqV_Znh z3~Q5G+rJ}L=kbD|^LIaaZ~dxIsQ$6z2Z8Ud-(vn2wZdS1WmVkKc>Kn+wjua+*R;J> zZ#PyLq{9|rlLkoeD!||H?V+TA=HG~_i%~<57G6oF)Iu&nDX2NN0CubjFhQaW3xdQl zC>Ke5AOV5Ope0E8TqLWDES?d`Ysd9H8wv`lL>R_en((e*t0_To0u%ipo`I`0M&%(` zNI?X}(_pm>E`t)h1Tapjx)jx4PE?tvlfo#SIByu76~nkW(4Z{&XMc1F9*7hxn|=_+ zO3!Cuvl43?@LV7S#`BG3uEAd%itD5GmOFkmEz4&Ccu6K5aXF` zT(UjO=@V)&zaLT-=SHIp?@nBi$cxA6nJzVqSnKcDaDW%ftnWR?!Fm3D*C&04e#lyI zBL5!>t^p=8#yCPR4kvqrYQxDMp*M$@+n## zqUAra@?-tG`u*y+OZ!WSG=TIKkCZTumg6n*ngM`SD5xDTVuk uM{;8*Hz2yboBWHE*}ZG~*AL`hnh#$aD(w@ceX6uUNqeFqWfAPf3-}kgtiqrG literal 0 HcmV?d00001 diff --git a/backend/__pycache__/mem0_client.cpython-311.pyc b/backend/__pycache__/mem0_client.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bf3391e5899d4c755e8b0e620f2c2689c41d2fef GIT binary patch literal 5318 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..417097c90fbc460f9dc895760d70b49748d281de GIT binary patch literal 180 zcmZ3^%ge<81dMwF(?RrO5CH>>P{wCAAY(d13PUi1CZpd)=dU}j`w{J;PsikN|70D_?`$p8QV literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..6006d2100933378f9b96043ac503ac90dad8566c GIT binary patch literal 1602 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 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 0000000000000000000000000000000000000000..506115903ddc0ef8b1a3abb5141d47f8b4c58871 GIT binary patch literal 4468 zcmZ{nTW=#p5QY00iT{9vc=X1XT#=B7KtiloY0<(1Pm%4|j)P+xd$ODCj|a|I$K@U; zUX8|=sp?Zzr!L*|-@n5$FROA_hGnhmxlH07>Hk94w)~*0U*42c-GlOV`AQP6XiB3M z$On@3N>`s(Esd@8W@Q;jX!QaCO`|Nd9;{7ZoJop}lrFV$6&j6vx-A|3ewMw-&L^y0 z=&mXhKZmcTi0$?$Zuj|C|adF4n^&SmRwA=?xDylTnuqLJ1lu(8{V_5E@dw#SNM zECwgiTIovGh=|*E#)03+)>OAy?FuKo>F=<}1ipgS2@W>pAvm0u&w-JQ!+NeFhOuHm zb{QVR4m^*;qm?j+frADym?Jc9wRS6On;m#&f`s?%J6zEQm$8K0|-{M!CBJcpa5^c)LF*d|JA%_M0Q z>nN=7+?llWmyo`fXN@ws5)ufu1d1zvCdo=QjYl2%KG)|g>{2^Jt(_}Myd4T*Rvz_@ zJma#@=Hn&SI?%}0X1BsDR|b!-pl8$PjyQq=m@u2p(rK%nU!pw5YCJ@D(5l$unjP`u z&d1V7t1`NpwZoOi7=pW8N4>u0mw7}kiD{+UdeG-m|JIRhYSFU^e$EfPhCkN=m5G(i zV^+dOC}W{}S{Q-cxSM9Okn&E{J(+zxuvfM3RA0S)EWM~WXE=Fk=hWH6V>&N2UNc#_ zOr58_jeg8M`#M)eGhs7IRvmdr6(Sq$t5)IPfx z1LR<>wT$*0U$^o9c6i;}gEQ{$A_2`m!Ta8(i{tO#fKzXSF-4IgyU7X2MdtxdMxX+kD(Q&_TY4uCHk!;#|4%!v;EDar8th4JjZWav zpU?Yh1lM*G?@{s(S~-=6S+kB2J|AT7QK+ul^eXK=>N%NazZ&Zo>s_5`1>QFDmkj4F zoN?hhHkPvRu=f&aKTur%FZoA)CXKX^a-w|N`H2)i5@Dn6a;Id3u0!v~bPqcjI|EfX zi#U7^aHXQLRFuPgJ-Y^6qfA9kX5(B3=KRc|DnY53@>Va_|3us4uj#6J<*ynIuB)SbKi_vIw6p9;VJ z6c4WFlk!%W)c8=(f0obVh|>GrR?pt;FvqF`s;nvB$!52_Z|#y_*7`$;KEIvpy{kxV zjB6qOs(3!j-nx7%52-m$gy)L*LViv4Z=(BBk#@CD4&vD`{`2ol&(4(PX?$}My3+qs z$;Z0*f2q5x`$8zEy3X{xrxjhTzR-71pGKa>%Ar0lqlaAw&sw|DRV#%I)Se^@)YwvM zGT(LDx1~2?ViNg0p3Hh&#*vGS1a}Ggm+9%PDJ!rqWWFnjnFuAG-5FMZLj4m z0e?QmI&64;z1Dr%kM7X+EcPxA2aPJse&n8JHeukea;4HviFPrPC^N&~|^NsH0We`l74RG2zysf#HMv^eMS(U8h8Y(4E5o89d z&I~88TiL4n37+>4EN+!AP^m({chZGeV?vN|dh%g!nJHv+D#8)tQ7@*qePUlEKYq{g z+gny0ox+)eDOFF7t|+qnq5Q6FT+6!O1|O88?+oJyMwKeAIA2Pw)Z9VbSE1@jjT0HL8i*T?WX~c`)sLINt*6Nt4q@cCV$*;*C7xghK>muAcNff9m;&ZgCg8 zGHN~BU(Oaj-*N(IGi#*u+h-7R!=%m}V#givDRM%ViQ7APcA)FXv)8P-#;{=7|5u8V Av;Y7A literal 0 HcmV?d00001