Build AI-Powered Study Assistant Application#1
Conversation
Built a full-stack production-ready study assistant application with: Backend (FastAPI + PostgreSQL + Ollama): - RESTful API with FastAPI and SQLAlchemy ORM - Database models for subjects, flashcards, quizzes, practice problems, progress tracking - AI service integration with Ollama for content generation - Spaced repetition algorithm (SM-2) for flashcard scheduling - Comprehensive API endpoints for all features - Alembic migrations for database schema management - Docker support for containerized deployment Frontend (React + Vite + TailwindCSS): - Modern React 18 with functional components and hooks - Responsive UI with TailwindCSS utility classes - Subject management with custom colors - AI-powered flashcard generation and review system - Quiz mode with multiple-choice questions and instant feedback - Practice problem generator with detailed solutions - Progress tracking dashboard with analytics - Real-time progress updates and mastery level calculation Key Features: - Generate flashcards automatically from topics using local LLM - Smart spaced repetition for optimal learning - Adaptive quiz generation with difficulty levels - Practice problems with step-by-step solutions - Comprehensive progress tracking and analytics - Study session logging - No external API dependencies (fully local with Ollama) Documentation: - Comprehensive README with installation and usage guides - Quick setup guide for first-time users - Complete API documentation with examples - Docker compose for easy deployment - Environment configuration templates Tech Stack: - Backend: FastAPI, SQLAlchemy, PostgreSQL, Alembic, Ollama - Frontend: React, Vite, TailwindCSS, Axios, React Router - Deployment: Docker, Docker Compose - AI: Ollama (local LLM - llama3.2) The application is designed for B.Tech CSE students at VIT Chennai but can be used for any educational context. All AI features run locally for privacy and no API costs.
WalkthroughThis PR introduces a complete full-stack AI-powered study assistant application featuring a FastAPI backend with PostgreSQL database, Ollama-based content generation, spaced repetition learning algorithms, a React frontend with Vite, Docker orchestration, and comprehensive documentation for setup and deployment. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant React
participant FastAPI
participant Ollama
participant PostgreSQL
User->>React: Click "Generate Flashcards"
React->>React: Open modal, collect topic & count
User->>React: Submit
React->>FastAPI: POST /api/flashcards/generate<br/>(subject_id, topic, num_cards)
FastAPI->>PostgreSQL: Verify Subject exists
PostgreSQL-->>FastAPI: Subject found
FastAPI->>Ollama: generate_flashcards(subject, topic, num_cards)
Ollama-->>FastAPI: JSON array of Q&A pairs
FastAPI->>PostgreSQL: INSERT Flashcards (with defaults:<br/>ease_factor=2.5, interval=1,<br/>next_review=now())
PostgreSQL-->>FastAPI: Flashcards created
FastAPI-->>React: List[FlashcardResponse]
React-->>User: Display new flashcard deck
User->>React: Flip & Rate Card (quality=4)
React->>FastAPI: POST /flashcards/{id}/review<br/>(quality=4)
FastAPI->>PostgreSQL: Load Flashcard
PostgreSQL-->>FastAPI: Flashcard + current values
FastAPI->>FastAPI: calculate_next_review(quality, <br/>ease_factor, interval, repetitions)<br/>→ (new_ease, new_interval, <br/>new_repetitions, next_date)
FastAPI->>PostgreSQL: UPDATE Flashcard<br/>(ease_factor, interval, repetitions,<br/>next_review, last_reviewed)
FastAPI->>PostgreSQL: UPDATE Progress<br/>(flashcards_reviewed++)
PostgreSQL-->>FastAPI: Changes committed
FastAPI-->>React: FlashcardResponse (updated)
React-->>User: Show next card
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60–90 minutes Areas requiring extra attention:
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 26
🧹 Nitpick comments (15)
docker-compose.yml (2)
27-28: Use environment variable references for database credentials.The DATABASE_URL contains the same hardcoded credentials as the postgres service. This creates duplication and maintenance burden.
Apply this diff to reference environment variables:
environment: - DATABASE_URL: postgresql://studyuser:studypass@postgres:5432/studyassistant + DATABASE_URL: postgresql://${POSTGRES_USER:-studyuser}:${POSTGRES_PASSWORD:-studypass}@postgres:5432/${POSTGRES_DB:-studyassistant} OLLAMA_BASE_URL: http://host.docker.internal:11434 OLLAMA_MODEL: llama3.2
28-28: Document platform-specific Docker networking assumption.The
host.docker.internalhostname is specific to Docker Desktop and may not work in other environments (Linux Docker Engine, production deployments, CI/CD).Consider documenting in the README:
- This setup requires Docker Desktop on macOS/Windows
- For Linux, users need to add
--add-host=host.docker.internal:host-gatewayto the docker run command or usenetwork_mode: "host"- For production, Ollama should be containerized or accessed via a proper service URL
frontend/src/components/Layout.jsx (1)
9-12: Consider moving navigation array outside component.The navigation array is re-created on every render. Moving it outside the component improves performance by avoiding unnecessary object allocations.
Apply this diff:
+const navigation = [ + { name: 'Dashboard', href: '/', icon: Home }, + { name: 'Progress', href: '/progress', icon: TrendingUp }, +]; + function Layout({ children }) { const location = useLocation(); const [mobileMenuOpen, setMobileMenuOpen] = React.useState(false); - const navigation = [ - { name: 'Dashboard', href: '/', icon: Home }, - { name: 'Progress', href: '/progress', icon: TrendingUp }, - ]; - const isActive = (path) => location.pathname === path;API_DOCUMENTATION.md (1)
364-366: Wrap bare URLs in angle brackets for proper Markdown formatting.Markdown linters prefer URLs to be wrapped in angle brackets or formatted as links.
Based on static analysis hints
Apply this diff:
## Interactive Documentation -Visit http://localhost:8000/docs for interactive Swagger UI documentation where you can test all endpoints directly. +Visit <http://localhost:8000/docs> for interactive Swagger UI documentation where you can test all endpoints directly. -Alternative documentation: http://localhost:8000/redoc +Alternative documentation: <http://localhost:8000/redoc>backend/app/database.py (2)
1-9: Consider upgrading to SQLAlchemy 2.0 DeclarativeBase style.The code uses the legacy
declarative_base()function. SQLAlchemy 2.0+ (version 2.0.25 is in use) recommends using the newDeclarativeBaseclass for better type checking and modern patterns.Apply this diff to use the modern SQLAlchemy 2.0 style:
from sqlalchemy import create_engine -from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import DeclarativeBase from sqlalchemy.orm import sessionmaker from .config import settings engine = create_engine(settings.DATABASE_URL) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) -Base = declarative_base() +class Base(DeclarativeBase): + passThis provides better IDE support and aligns with SQLAlchemy 2.0 best practices.
6-7: Consider adding connection pool configuration for production.The engine uses default connection pooling settings. For production deployments, you may want to configure pool size, timeouts, and overflow settings based on your load requirements.
Example configuration for production:
engine = create_engine( settings.DATABASE_URL, pool_size=5, # Number of connections to maintain max_overflow=10, # Additional connections under load pool_timeout=30, # Timeout for getting a connection pool_recycle=3600, # Recycle connections after 1 hour pool_pre_ping=True, # Verify connections before using )Monitor your application's connection usage and adjust these values accordingly.
backend/app/services/spaced_repetition.py (1)
8-26: Consider adding input validation for the quality parameter.The docstring specifies
quality: Response quality (0-5), but there's no validation to ensure the value is within this range. Invalid values could produce incorrect ease factor calculations.Add validation at the start of the method:
def calculate_next_review( quality: int, ease_factor: float, interval: int, repetitions: int ) -> Tuple[float, int, int, datetime]: """ Calculate next review date using SM-2 algorithm Args: quality: Response quality (0-5) ease_factor: Current ease factor interval: Current interval in days repetitions: Number of consecutive successful repetitions Returns: Tuple of (new_ease_factor, new_interval, new_repetitions, next_review_date) """ + if not 0 <= quality <= 5: + raise ValueError(f"Quality must be between 0 and 5, got {quality}") + # Update ease factor new_ease_factor = ease_factor + (0.1 - (5 - quality) * (0.08 + (5 - quality) * 0.02))frontend/src/pages/SubjectDetails.jsx (1)
24-25: Display user-facing error messages.Errors are logged to the console but not displayed to the user. If the API call fails, users will see a generic "Subject not found" message or no feedback at all.
Add error state and display it to users:
const [subject, setSubject] = useState(null); const [progress, setProgress] = useState(null); const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); useEffect(() => { const loadData = async () => { try { + setError(null); const [subjectRes, progressRes] = await Promise.all([ getSubject(id), getSubjectProgress(id) ]); setSubject(subjectRes.data); setProgress(progressRes.data); } catch (error) { console.error('Error loading data:', error); + setError('Failed to load subject data. Please try again.'); } finally { setLoading(false); } }; loadData(); }, [id]); if (loading) { return <div className="text-center py-12">Loading...</div>; } + if (error) { + return <div className="text-center py-12 text-red-600">{error}</div>; + }backend/app/models/__init__.py (1)
1-16: LGTM! Clean model export structure.The file properly consolidates all ORM model exports into a single namespace, making imports clean throughout the codebase.
Optional: The static analysis tool suggests sorting
__all__alphabetically, but the current logical grouping (Subject first, then related entities) is also reasonable. If you prefer consistency with import sorting tools, you could apply:__all__ = [ - "Subject", "Flashcard", - "Quiz", - "QuizQuestion", "PracticeProblem", + "Progress", + "Quiz", + "QuizQuestion", "StudySession", - "Progress", + "Subject", ]backend/app/schemas/study_session.py (1)
6-10: Consider adding validation for the duration field.The
durationfield (study session length in minutes) should be positive, but there's no validation to prevent negative or zero values, which could corrupt progress metrics.Add validation using Pydantic's
Field:-from pydantic import BaseModel +from pydantic import BaseModel, Field from typing import Optional from datetime import datetime class StudySessionCreate(BaseModel): subject_id: int - duration: int # in minutes + duration: int = Field(gt=0, description="Duration in minutes") topics_covered: Optional[str] = None notes: Optional[str] = Nonebackend/app/schemas/progress.py (1)
6-17: Consider adding field validation for numeric ranges.Several fields should have specific ranges or constraints to prevent invalid data:
mastery_levelshould be 0-100 (as indicated in the ORM model comment)average_quiz_scoreshould be 0-100- Counters (
total_study_time,flashcards_reviewed, etc.) should be non-negativeAdd validation using Pydantic's
Field:-from pydantic import BaseModel +from pydantic import BaseModel, Field from typing import Optional from datetime import datetime class ProgressResponse(BaseModel): id: int subject_id: int - mastery_level: float - total_study_time: int - flashcards_reviewed: int - quizzes_completed: int - problems_solved: int - average_quiz_score: float + mastery_level: float = Field(ge=0, le=100, description="Mastery level percentage") + total_study_time: int = Field(ge=0, description="Total study time in minutes") + flashcards_reviewed: int = Field(ge=0) + quizzes_completed: int = Field(ge=0) + problems_solved: int = Field(ge=0) + average_quiz_score: float = Field(ge=0, le=100, description="Average quiz score percentage") last_studied: Optional[datetime] = None created_at: datetime updated_at: Optional[datetime] = Nonebackend/app/models/quiz.py (2)
34-34: Consider using Boolean type instead of Integer for is_correct.The
is_correctfield usesInteger(with values 1/0), but SQLAlchemy'sBooleantype would be more idiomatic and type-safe. SQLAlchemy handles the database-specific representation automatically.Apply this diff:
-from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey, Float, JSON +from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey, Float, JSON, Boolean from sqlalchemy.orm import relationship from sqlalchemy.sql import func from ..database import Base class Quiz(Base): __tablename__ = "quizzes" id = Column(Integer, primary_key=True, index=True) subject_id = Column(Integer, ForeignKey("subjects.id", ondelete="CASCADE"), nullable=False) title = Column(String, nullable=False) difficulty = Column(String, default="medium") # easy, medium, hard score = Column(Float, default=0.0) total_questions = Column(Integer, default=0) completed_at = Column(DateTime(timezone=True), nullable=True) created_at = Column(DateTime(timezone=True), server_default=func.now()) # Relationships subject = relationship("Subject", back_populates="quizzes") questions = relationship("QuizQuestion", back_populates="quiz", cascade="all, delete-orphan") class QuizQuestion(Base): __tablename__ = "quiz_questions" id = Column(Integer, primary_key=True, index=True) quiz_id = Column(Integer, ForeignKey("quizzes.id", ondelete="CASCADE"), nullable=False) question = Column(Text, nullable=False) options = Column(JSON, nullable=False) # List of options correct_answer = Column(String, nullable=False) user_answer = Column(String, nullable=True) explanation = Column(Text, nullable=True) - is_correct = Column(Integer, nullable=True) # 1 for correct, 0 for incorrect + is_correct = Column(Boolean, nullable=True) # Relationships quiz = relationship("Quiz", back_populates="questions")Note: If you're using a database without native boolean support (e.g., older MySQL), SQLAlchemy will automatically map Boolean to TINYINT(1), so this change is safe.
7-21: LGTM! Well-structured ORM models with proper relationships.The Quiz and QuizQuestion models are well-designed with appropriate cascade behavior and relationships. The use of JSON for options provides flexibility for varying numbers of choices.
Optional: You could enhance type safety for the
difficultyfield by using an enum instead of a free-form string:-from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey, Float, JSON +from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey, Float, JSON, Enum as SQLEnum +from enum import Enum from sqlalchemy.orm import relationship from sqlalchemy.sql import func from ..database import Base +class DifficultyLevel(str, Enum): + EASY = "easy" + MEDIUM = "medium" + HARD = "hard" + + class Quiz(Base): __tablename__ = "quizzes" id = Column(Integer, primary_key=True, index=True) subject_id = Column(Integer, ForeignKey("subjects.id", ondelete="CASCADE"), nullable=False) title = Column(String, nullable=False) - difficulty = Column(String, default="medium") # easy, medium, hard + difficulty = Column(SQLEnum(DifficultyLevel), default=DifficultyLevel.MEDIUM) score = Column(Float, default=0.0) total_questions = Column(Integer, default=0)backend/app/api/endpoints/subjects.py (1)
13-66: AdoptAnnotated[..., Depends(...)]to satisfy modern FastAPI guidanceConsider switching the dependency parameters to use
Annotated[Session, Depends(get_db)]. This silences Ruff’s B008 warning and follows FastAPI’s current recommendation for dependency injection.(docs.astral.sh)backend/app/schemas/quiz.py (1)
1-45: Avoid the shared list default onquestionsThe empty list literal is shared across model instances. Swap to a default factory so each response gets its own list.
Apply this diff:
-from pydantic import BaseModel +from pydantic import BaseModel, Field @@ - questions: List[QuizQuestionResponse] = [] + questions: List[QuizQuestionResponse] = Field(default_factory=list)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (59)
.gitignore(1 hunks)API_DOCUMENTATION.md(1 hunks)README.md(1 hunks)SETUP_GUIDE.md(1 hunks)backend/.env.example(1 hunks)backend/Dockerfile(1 hunks)backend/alembic.ini(1 hunks)backend/alembic/env.py(1 hunks)backend/alembic/script.py.mako(1 hunks)backend/alembic/versions/001_initial_migration.py(1 hunks)backend/app/__init__.py(1 hunks)backend/app/api/__init__.py(1 hunks)backend/app/api/endpoints/__init__.py(1 hunks)backend/app/api/endpoints/flashcards.py(1 hunks)backend/app/api/endpoints/practice_problems.py(1 hunks)backend/app/api/endpoints/progress.py(1 hunks)backend/app/api/endpoints/quizzes.py(1 hunks)backend/app/api/endpoints/study_sessions.py(1 hunks)backend/app/api/endpoints/subjects.py(1 hunks)backend/app/config.py(1 hunks)backend/app/database.py(1 hunks)backend/app/main.py(1 hunks)backend/app/models/__init__.py(1 hunks)backend/app/models/flashcard.py(1 hunks)backend/app/models/practice_problem.py(1 hunks)backend/app/models/progress.py(1 hunks)backend/app/models/quiz.py(1 hunks)backend/app/models/study_session.py(1 hunks)backend/app/models/subject.py(1 hunks)backend/app/schemas/__init__.py(1 hunks)backend/app/schemas/flashcard.py(1 hunks)backend/app/schemas/practice_problem.py(1 hunks)backend/app/schemas/progress.py(1 hunks)backend/app/schemas/quiz.py(1 hunks)backend/app/schemas/study_session.py(1 hunks)backend/app/schemas/subject.py(1 hunks)backend/app/services/__init__.py(1 hunks)backend/app/services/ai_service.py(1 hunks)backend/app/services/spaced_repetition.py(1 hunks)backend/requirements.txt(1 hunks)docker-compose.yml(1 hunks)frontend/Dockerfile(1 hunks)frontend/index.html(1 hunks)frontend/package.json(1 hunks)frontend/postcss.config.js(1 hunks)frontend/src/App.jsx(1 hunks)frontend/src/components/Layout.jsx(1 hunks)frontend/src/index.css(1 hunks)frontend/src/main.jsx(1 hunks)frontend/src/pages/Dashboard.jsx(1 hunks)frontend/src/pages/Flashcards.jsx(1 hunks)frontend/src/pages/PracticeProblems.jsx(1 hunks)frontend/src/pages/Progress.jsx(1 hunks)frontend/src/pages/Quiz.jsx(1 hunks)frontend/src/pages/QuizTake.jsx(1 hunks)frontend/src/pages/SubjectDetails.jsx(1 hunks)frontend/src/services/api.js(1 hunks)frontend/tailwind.config.js(1 hunks)frontend/vite.config.js(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (29)
frontend/src/pages/SubjectDetails.jsx (1)
frontend/src/services/api.js (4)
getSubject(14-14)getSubject(14-14)getSubjectProgress(50-50)getSubjectProgress(50-50)
frontend/src/pages/Quiz.jsx (2)
backend/app/models/quiz.py (1)
Quiz(7-21)frontend/src/services/api.js (6)
getQuizzes(31-32)getQuizzes(31-32)generateQuiz(34-34)generateQuiz(34-34)deleteQuiz(36-36)deleteQuiz(36-36)
backend/app/schemas/flashcard.py (5)
backend/app/schemas/practice_problem.py (1)
Config(28-29)backend/app/schemas/progress.py (1)
Config(19-20)backend/app/schemas/quiz.py (2)
Config(18-19)Config(44-45)backend/app/schemas/study_session.py (1)
Config(17-18)backend/app/schemas/subject.py (1)
Config(27-28)
backend/app/models/quiz.py (2)
frontend/src/pages/SubjectDetails.jsx (1)
subject(8-8)frontend/src/pages/QuizTake.jsx (1)
quiz(9-9)
frontend/src/pages/Flashcards.jsx (1)
frontend/src/services/api.js (8)
getDueFlashcards(22-23)getDueFlashcards(22-23)getFlashcards(20-21)getFlashcards(20-21)generateFlashcards(24-25)generateFlashcards(24-25)reviewFlashcard(26-27)reviewFlashcard(26-27)
backend/app/schemas/progress.py (5)
backend/app/schemas/flashcard.py (1)
Config(38-39)backend/app/schemas/practice_problem.py (1)
Config(28-29)backend/app/schemas/quiz.py (2)
Config(18-19)Config(44-45)backend/app/schemas/study_session.py (1)
Config(17-18)backend/app/schemas/subject.py (1)
Config(27-28)
frontend/src/pages/Progress.jsx (3)
backend/app/models/progress.py (1)
Progress(7-23)frontend/src/pages/SubjectDetails.jsx (4)
progress(9-9)loading(10-10)loadData(16-29)subject(8-8)frontend/src/services/api.js (4)
getAllProgress(49-49)getAllProgress(49-49)getSubjects(13-13)getSubjects(13-13)
backend/app/schemas/quiz.py (6)
backend/app/schemas/flashcard.py (1)
Config(38-39)backend/app/schemas/practice_problem.py (1)
Config(28-29)backend/app/schemas/progress.py (1)
Config(19-20)backend/app/schemas/study_session.py (1)
Config(17-18)backend/app/schemas/subject.py (1)
Config(27-28)frontend/src/pages/QuizTake.jsx (1)
answers(10-10)
frontend/src/App.jsx (2)
backend/app/models/quiz.py (1)
Quiz(7-21)backend/app/models/progress.py (1)
Progress(7-23)
backend/app/services/ai_service.py (2)
backend/app/api/endpoints/flashcards.py (1)
generate_flashcards(47-81)backend/app/api/endpoints/practice_problems.py (1)
generate_practice_problems(42-84)
frontend/src/pages/QuizTake.jsx (1)
frontend/src/services/api.js (4)
getQuiz(33-33)getQuiz(33-33)submitQuiz(35-35)submitQuiz(35-35)
backend/app/api/endpoints/subjects.py (4)
backend/app/database.py (1)
get_db(12-18)backend/app/models/subject.py (1)
Subject(7-22)backend/app/models/progress.py (1)
Progress(7-23)backend/app/schemas/subject.py (3)
SubjectCreate(12-13)SubjectUpdate(16-19)SubjectResponse(22-28)
backend/app/main.py (1)
frontend/src/services/api.js (1)
api(5-10)
backend/app/api/endpoints/practice_problems.py (6)
backend/app/database.py (1)
get_db(12-18)backend/app/models/practice_problem.py (1)
PracticeProblem(7-22)backend/app/models/subject.py (1)
Subject(7-22)backend/app/models/progress.py (1)
Progress(7-23)backend/app/schemas/practice_problem.py (2)
PracticeProblemCreate(14-18)PracticeProblemResponse(21-29)backend/app/services/ai_service.py (1)
generate_practice_problems(55-91)
backend/app/api/endpoints/study_sessions.py (5)
backend/app/database.py (1)
get_db(12-18)backend/app/models/study_session.py (1)
StudySession(7-18)backend/app/models/subject.py (1)
Subject(7-22)backend/app/models/progress.py (1)
Progress(7-23)backend/app/schemas/study_session.py (2)
StudySessionCreate(6-10)StudySessionResponse(13-18)
backend/alembic/env.py (6)
backend/app/models/subject.py (1)
Subject(7-22)backend/app/models/flashcard.py (1)
Flashcard(7-27)backend/app/models/quiz.py (2)
Quiz(7-21)QuizQuestion(24-37)backend/app/models/practice_problem.py (1)
PracticeProblem(7-22)backend/app/models/study_session.py (1)
StudySession(7-18)backend/app/models/progress.py (1)
Progress(7-23)
frontend/src/pages/PracticeProblems.jsx (1)
frontend/src/services/api.js (6)
getPracticeProblems(39-40)getPracticeProblems(39-40)generatePracticeProblems(41-42)generatePracticeProblems(41-42)markProblemSolved(43-44)markProblemSolved(43-44)
backend/app/api/endpoints/progress.py (4)
backend/app/database.py (1)
get_db(12-18)backend/app/models/progress.py (1)
Progress(7-23)backend/app/models/subject.py (1)
Subject(7-22)backend/app/schemas/progress.py (1)
ProgressResponse(6-20)
backend/app/schemas/study_session.py (5)
backend/app/schemas/flashcard.py (1)
Config(38-39)backend/app/schemas/practice_problem.py (1)
Config(28-29)backend/app/schemas/progress.py (1)
Config(19-20)backend/app/schemas/quiz.py (2)
Config(18-19)Config(44-45)backend/app/schemas/subject.py (1)
Config(27-28)
backend/app/models/__init__.py (6)
backend/app/models/subject.py (1)
Subject(7-22)backend/app/models/flashcard.py (1)
Flashcard(7-27)backend/app/models/quiz.py (2)
Quiz(7-21)QuizQuestion(24-37)backend/app/models/practice_problem.py (1)
PracticeProblem(7-22)backend/app/models/study_session.py (1)
StudySession(7-18)backend/app/models/progress.py (1)
Progress(7-23)
frontend/src/pages/Dashboard.jsx (1)
frontend/src/services/api.js (6)
getSubjects(13-13)getSubjects(13-13)createSubject(15-15)createSubject(15-15)deleteSubject(17-17)deleteSubject(17-17)
frontend/src/services/api.js (1)
frontend/src/pages/QuizTake.jsx (1)
answers(10-10)
backend/app/schemas/practice_problem.py (5)
backend/app/schemas/flashcard.py (1)
Config(38-39)backend/app/schemas/progress.py (1)
Config(19-20)backend/app/schemas/quiz.py (2)
Config(18-19)Config(44-45)backend/app/schemas/study_session.py (1)
Config(17-18)backend/app/schemas/subject.py (1)
Config(27-28)
backend/app/schemas/__init__.py (6)
backend/app/schemas/subject.py (3)
SubjectCreate(12-13)SubjectUpdate(16-19)SubjectResponse(22-28)backend/app/schemas/flashcard.py (4)
FlashcardCreate(13-14)FlashcardUpdate(17-21)FlashcardResponse(28-39)FlashcardReviewRequest(24-25)backend/app/schemas/quiz.py (4)
QuizCreate(22-26)QuizResponse(33-45)QuizQuestionResponse(13-19)QuizSubmitRequest(29-30)backend/app/schemas/practice_problem.py (2)
PracticeProblemCreate(14-18)PracticeProblemResponse(21-29)backend/app/schemas/study_session.py (2)
StudySessionCreate(6-10)StudySessionResponse(13-18)backend/app/schemas/progress.py (1)
ProgressResponse(6-20)
backend/app/api/endpoints/quizzes.py (6)
backend/app/database.py (1)
get_db(12-18)backend/app/models/quiz.py (2)
Quiz(7-21)QuizQuestion(24-37)backend/app/models/subject.py (1)
Subject(7-22)backend/app/models/progress.py (1)
Progress(7-23)backend/app/schemas/quiz.py (3)
QuizCreate(22-26)QuizResponse(33-45)QuizSubmitRequest(29-30)backend/app/services/ai_service.py (1)
generate_quiz_questions(93-135)
backend/app/schemas/subject.py (5)
backend/app/schemas/flashcard.py (1)
Config(38-39)backend/app/schemas/practice_problem.py (1)
Config(28-29)backend/app/schemas/progress.py (1)
Config(19-20)backend/app/schemas/quiz.py (2)
Config(18-19)Config(44-45)backend/app/schemas/study_session.py (1)
Config(17-18)
backend/app/models/subject.py (4)
frontend/src/pages/Flashcards.jsx (1)
flashcards(8-8)frontend/src/pages/Quiz.jsx (1)
quizzes(9-9)frontend/src/pages/Progress.jsx (1)
progress(7-7)frontend/src/pages/SubjectDetails.jsx (1)
progress(9-9)
backend/app/api/endpoints/flashcards.py (7)
backend/app/database.py (1)
get_db(12-18)backend/app/models/flashcard.py (1)
Flashcard(7-27)backend/app/models/subject.py (1)
Subject(7-22)backend/app/models/progress.py (1)
Progress(7-23)backend/app/schemas/flashcard.py (4)
FlashcardCreate(13-14)FlashcardUpdate(17-21)FlashcardResponse(28-39)FlashcardReviewRequest(24-25)backend/app/services/ai_service.py (1)
generate_flashcards(14-53)backend/app/services/spaced_repetition.py (1)
calculate_next_review(9-48)
backend/app/services/__init__.py (2)
backend/app/services/ai_service.py (1)
AIService(7-141)backend/app/services/spaced_repetition.py (1)
SpacedRepetitionService(5-48)
🪛 Checkov (3.2.334)
docker-compose.yml
[medium] 27-28: Basic Auth Credentials
(CKV_SECRET_4)
🪛 dotenv-linter (4.0.0)
backend/.env.example
[warning] 14-14: [ValueWithoutQuotes] This value needs to be surrounded in quotes
(ValueWithoutQuotes)
[warning] 18-18: [QuoteCharacter] The value has quote characters (', ")
(QuoteCharacter)
🪛 markdownlint-cli2 (0.18.1)
API_DOCUMENTATION.md
364-364: Bare URL used
(MD034, no-bare-urls)
366-366: Bare URL used
(MD034, no-bare-urls)
README.md
62-62: Bare URL used
(MD034, no-bare-urls)
158-158: Bare URL used
(MD034, no-bare-urls)
159-159: Bare URL used
(MD034, no-bare-urls)
160-160: Bare URL used
(MD034, no-bare-urls)
182-182: Bare URL used
(MD034, no-bare-urls)
226-226: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
256-256: Bare URL used
(MD034, no-bare-urls)
338-338: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
350-350: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
360-360: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
374-374: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
384-384: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
391-391: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
523-523: Bare URL used
(MD034, no-bare-urls)
🪛 OSV Scanner (2.2.4)
backend/requirements.txt
[CRITICAL] 1-1: python-jose 3.3.0: undefined
(PYSEC-2024-232)
[CRITICAL] 1-1: python-jose 3.3.0: undefined
(PYSEC-2024-233)
[CRITICAL] 1-1: python-jose 3.3.0: python-jose algorithm confusion with OpenSSH ECDSA keys
[CRITICAL] 1-1: python-jose 3.3.0: python-jose denial of service via compressed JWE content
[HIGH] 1-1: python-multipart 0.0.9: Denial of service (DoS) via deformation multipart/form-data boundary
🪛 Ruff (0.14.3)
backend/app/services/ai_service.py
51-51: Do not catch blind exception: Exception
(BLE001)
89-89: Do not catch blind exception: Exception
(BLE001)
133-133: Do not catch blind exception: Exception
(BLE001)
137-137: Unused method argument: content
(ARG002)
137-137: Unused method argument: num_cards
(ARG002)
backend/app/api/endpoints/subjects.py
14-14: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
21-21: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
30-30: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
49-49: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
66-66: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
backend/app/api/endpoints/practice_problems.py
18-18: PEP 484 prohibits implicit Optional
Convert to T | None
(RUF013)
19-19: PEP 484 prohibits implicit Optional
Convert to T | None
(RUF013)
20-20: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
33-33: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
44-44: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
88-88: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
112-112: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
backend/app/config.py
16-16: Possible binding to all interfaces
(S104)
backend/app/api/endpoints/study_sessions.py
15-15: PEP 484 prohibits implicit Optional
Convert to T | None
(RUF013)
15-15: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
25-25: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
34-34: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
55-55: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
backend/app/api/endpoints/progress.py
14-14: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
21-21: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
backend/app/models/__init__.py
8-16: __all__ is not sorted
Apply an isort-style sorting to __all__
(RUF022)
backend/app/schemas/__init__.py
8-25: __all__ is not sorted
Apply an isort-style sorting to __all__
(RUF022)
backend/app/api/endpoints/quizzes.py
17-17: PEP 484 prohibits implicit Optional
Convert to T | None
(RUF013)
17-17: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
27-27: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
36-36: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
85-85: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
128-128: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
backend/app/api/endpoints/flashcards.py
18-18: PEP 484 prohibits implicit Optional
Convert to T | None
(RUF013)
18-18: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
28-28: PEP 484 prohibits implicit Optional
Convert to T | None
(RUF013)
28-28: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
38-38: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
51-51: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
85-85: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
102-102: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
139-139: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
156-156: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
(B008)
🔇 Additional comments (13)
.gitignore (1)
1-45: LGTM! Comprehensive ignore patterns.The .gitignore appropriately covers Python artifacts, environment files, build outputs, dependencies, and platform-specific files for this full-stack application.
frontend/postcss.config.js (1)
1-6: LGTM! Standard PostCSS configuration for Tailwind.The configuration properly integrates Tailwind CSS and Autoprefixer with their default settings, which works well with the Tailwind setup.
frontend/vite.config.js (1)
1-15: LGTM! Proper Vite development configuration.The proxy setup correctly forwards
/apirequests to the backend at port 8000 during development, enabling seamless full-stack development workflow.backend/requirements.txt (1)
1-13: I'll verify whether these package versions are actually outdated by checking the latest available versions:Update packages to more recent stable versions. No known vulnerabilities were found for the pinned versions, but several packages are significantly outdated. FastAPI's latest version is 0.121.0 released November 3, 2025, compared to your pinned 0.109.2 from early 2024. Similarly, SQLAlchemy's latest version was released October 10, 2025, and Pydantic's latest version was released November 5, 2025. All packages are actively maintained with recent updates. Consider updating to their latest stable versions within the current major release series (e.g., FastAPI 0.121.0, SQLAlchemy 2.0.44, Pydantic 2.12.4) to benefit from bug fixes, performance improvements, and security enhancements.
frontend/src/main.jsx (1)
1-10: Bootstrapper looks good.
Nothing to fix here—the Vite/React entrypoint is wired correctly.frontend/src/index.css (1)
12-41: No action required.The Tailwind primary color palette is properly configured with all required scales (500, 600, 700) used in the CSS classes. The configuration is complete and will build successfully.
docker-compose.yml (1)
32-34: LGTM! Proper health check dependency.Using
condition: service_healthyensures the backend waits for PostgreSQL to be ready before starting, preventing connection errors during startup.API_DOCUMENTATION.md (1)
354-360: LGTM! Security considerations properly documented.The documentation correctly identifies missing authentication and rate limiting as production deployment concerns. This transparency helps users understand the current limitations.
backend/app/services/__init__.py (1)
1-4: LGTM! Clean module exports.The service module properly exports AIService and SpacedRepetitionService with an explicit
__all__list, following Python best practices for public API definition.backend/alembic.ini (1)
15-28: LGTM! Appropriate logging configuration.The logging setup properly configures root, SQLAlchemy, and Alembic loggers with appropriate levels (WARN for root and SQLAlchemy, INFO for Alembic), which helps with debugging migrations without excessive output.
frontend/tailwind.config.js (1)
1-26: LGTM! Clean Tailwind configuration.The Tailwind config is properly structured with:
- Appropriate content paths for Vite + React
- Well-defined primary color palette (blue theme, shades 50-900)
- Proper TypeScript type annotation
The configuration aligns with the frontend build setup and provides a consistent color system used throughout the Layout component and other UI elements.
backend/app/api/__init__.py (1)
1-11: LGTM! Clean API router composition.The central API router properly aggregates all endpoint routers with:
- Consistent prefix patterns (
/subjects,/flashcards, etc.)- Matching tags for OpenAPI documentation organization
- Clean import structure
This composition pattern makes it easy to mount all API routes under
/apiin the main application and maintains good separation of concerns.backend/app/database.py (1)
12-18: LGTM! Proper database session dependency.The
get_db()dependency correctly:
- Yields a session for request handling
- Ensures cleanup in a
finallyblock- Follows FastAPI's recommended pattern for database session management
| ## Filtering and Sorting | ||
|
|
||
| **Filtering:** | ||
| ```http | ||
| GET /flashcards?subject_id=1&difficulty=medium&topic=Calculus | ||
| ``` | ||
|
|
||
| **Sorting:** | ||
| ```http | ||
| GET /quizzes?subject_id=1&sort=created_at&order=desc | ||
| ``` | ||
|
|
||
| (Note: Some filtering/sorting options may need to be implemented) |
There was a problem hiding this comment.
Clarify which filtering and sorting features are implemented.
The note "(Note: Some filtering/sorting options may need to be implemented)" is vague and could confuse users trying to use these features.
Consider either:
- Explicitly documenting which filters/sorts are currently supported
- Removing the examples for unimplemented features and noting them as "planned"
- Adding a status indicator (✅ Implemented, 🚧 Planned) next to each feature
This helps users understand what they can use today versus what's planned.
🤖 Prompt for AI Agents
In API_DOCUMENTATION.md around lines 380 to 392, the "Filtering and Sorting"
section contains vague note about unimplemented options; update the doc to
clearly indicate which query parameters are supported today versus planned: list
each filter/sort option used in the examples with a status indicator (e.g., ✅
Implemented or 🚧 Planned), or remove any example that references an
unimplemented feature and mark it as Planned; ensure every query parameter shown
has a short one-line status and, for implemented ones, include expected behavior
and accepted values.
| DEBUG=True | ||
|
|
||
| # CORS Configuration | ||
| CORS_ORIGINS=["http://localhost:5173", "http://localhost:3000"] |
There was a problem hiding this comment.
Fix CORS_ORIGINS format for proper parsing.
The JSON array value should be wrapped in quotes for environment variable parsing. Without quotes, this may cause parsing issues with pydantic-settings.
Apply this diff:
-CORS_ORIGINS=["http://localhost:5173", "http://localhost:3000"]
+CORS_ORIGINS='["http://localhost:5173", "http://localhost:3000"]'📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| CORS_ORIGINS=["http://localhost:5173", "http://localhost:3000"] | |
| CORS_ORIGINS='["http://localhost:5173", "http://localhost:3000"]' |
🧰 Tools
🪛 dotenv-linter (4.0.0)
[warning] 14-14: [ValueWithoutQuotes] This value needs to be surrounded in quotes
(ValueWithoutQuotes)
🤖 Prompt for AI Agents
In backend/.env.example around line 14, the CORS_ORIGINS value is currently an
unquoted JSON array which pydantic-settings may not parse; replace it with a
quoted JSON string (e.g., wrap the entire array in single quotes so the
environment variable is a string: CORS_ORIGINS='[\"http://localhost:5173\",
\"http://localhost:3000\"]') so parsers read it as a single string containing a
JSON array.
|
|
||
| # Application Settings | ||
| APP_NAME="AI Study Assistant" | ||
| APP_VERSION="1.0.0" |
There was a problem hiding this comment.
Remove redundant quotes from APP_VERSION.
The value already contains quotes which will be parsed as part of the string literal (e.g., "1.0.0" instead of 1.0.0).
Apply this diff:
-APP_VERSION="1.0.0"
+APP_VERSION=1.0.0📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| APP_VERSION="1.0.0" | |
| APP_VERSION=1.0.0 |
🧰 Tools
🪛 dotenv-linter (4.0.0)
[warning] 18-18: [QuoteCharacter] The value has quote characters (', ")
(QuoteCharacter)
🤖 Prompt for AI Agents
In backend/.env.example around line 18, APP_VERSION is set with redundant
surrounding quotes which will be parsed into the literal value; remove the
surrounding double quotes so the environment variable is stored without quotes
(e.g., APP_VERSION=1.0.0) to ensure loaders read the raw version string.
| [alembic] | ||
| script_location = alembic | ||
| prepend_sys_path = . | ||
| sqlalchemy.url = postgresql://studyuser:studypass@localhost:5432/studyassistant |
There was a problem hiding this comment.
Replace hardcoded database URL with placeholder.
The hardcoded database credentials pose a security risk if this file is committed. According to the AI summary, backend/alembic/env.py overrides this value from settings at runtime, making this hardcoded URL both redundant and misleading.
Apply this diff to use a placeholder that makes the override behavior clear:
-sqlalchemy.url = postgresql://studyuser:studypass@localhost:5432/studyassistant
+sqlalchemy.url = driver://user:pass@localhost/dbnameThis follows Alembic best practices where the actual URL is provided by the environment script, and the ini file contains only a placeholder for documentation purposes.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| sqlalchemy.url = postgresql://studyuser:studypass@localhost:5432/studyassistant | |
| sqlalchemy.url = driver://user:pass@localhost/dbname |
🤖 Prompt for AI Agents
In backend/alembic.ini around line 4, the file contains a hardcoded SQLAlchemy
URL which is insecure and redundant because backend/alembic/env.py supplies the
real URL at runtime; replace the hardcoded credentials with a clear placeholder
(e.g., sqlalchemy.url = driver://user:pass@localhost/dbname or a comment) that
documents the value is provided by the env.py or environment, ensuring no real
credentials remain in the committed ini file.
| if not progress: | ||
| # Create progress if it doesn't exist | ||
| subject = db.query(Subject).filter(Subject.id == subject_id).first() | ||
| if not subject: | ||
| raise HTTPException(status_code=404, detail="Subject not found") | ||
|
|
||
| progress = Progress(subject_id=subject_id) | ||
| db.add(progress) | ||
| db.commit() | ||
| db.refresh(progress) |
There was a problem hiding this comment.
Keep GET /progress/{id} side-effect free.
This GET endpoint creates rows when none exist, breaching HTTP safety semantics and causing hidden writes on cache retries or monitoring pings. Please refuse the request (404) or move initialization to a dedicated create flow instead of mutating inside GET.
- progress = db.query(Progress).filter(Progress.subject_id == subject_id).first()
- if not progress:
- # Create progress if it doesn't exist
- subject = db.query(Subject).filter(Subject.id == subject_id).first()
- if not subject:
- raise HTTPException(status_code=404, detail="Subject not found")
-
- progress = Progress(subject_id=subject_id)
- db.add(progress)
- db.commit()
- db.refresh(progress)
-
- return progress
+ subject = db.query(Subject).filter(Subject.id == subject_id).first()
+ if not subject:
+ raise HTTPException(status_code=404, detail="Subject not found")
+
+ progress = db.query(Progress).filter(Progress.subject_id == subject_id).first()
+ if not progress:
+ raise HTTPException(status_code=404, detail="Progress not found")
+
+ return progressCommittable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In backend/app/api/endpoints/progress.py around lines 24 to 33, the GET handler
currently creates a Progress row when none exists which introduces a
side-effectful write; remove the creation logic so the GET remains read-only and
instead raise HTTPException(status_code=404, detail="Progress not found") when
no progress exists; if initialization is required, move that code to a dedicated
POST/PUT create endpoint or an initialization service and ensure the GET only
performs queries (no db.add/db.commit/db.refresh).
| const loadQuiz = async () => { | ||
| try { | ||
| const response = await getQuiz(quizId); | ||
| setQuiz(response.data); | ||
|
|
||
| // Initialize answers for completed quiz | ||
| if (response.data.completed_at) { | ||
| const existingAnswers = {}; | ||
| response.data.questions.forEach((q) => { | ||
| if (q.user_answer) { | ||
| existingAnswers[q.id] = q.user_answer; | ||
| } | ||
| }); | ||
| setAnswers(existingAnswers); | ||
| } | ||
| } catch (error) { | ||
| console.error('Error loading quiz:', error); | ||
| } finally { | ||
| setLoading(false); | ||
| } | ||
| }; |
There was a problem hiding this comment.
Reset answers when loading a different quiz.
When you switch from one quiz to another, the old answers map sticks around because we never clear it. That leaves stale question IDs in state, so the UI shows an inflated answered count and the submission payload contains answers for unrelated questions (the backend can’t match those IDs). Please reset answers before optionally seeding completed quizzes.
const response = await getQuiz(quizId);
setQuiz(response.data);
+ setAnswers({});
// Initialize answers for completed quiz
if (response.data.completed_at) {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const loadQuiz = async () => { | |
| try { | |
| const response = await getQuiz(quizId); | |
| setQuiz(response.data); | |
| // Initialize answers for completed quiz | |
| if (response.data.completed_at) { | |
| const existingAnswers = {}; | |
| response.data.questions.forEach((q) => { | |
| if (q.user_answer) { | |
| existingAnswers[q.id] = q.user_answer; | |
| } | |
| }); | |
| setAnswers(existingAnswers); | |
| } | |
| } catch (error) { | |
| console.error('Error loading quiz:', error); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }; | |
| const loadQuiz = async () => { | |
| try { | |
| const response = await getQuiz(quizId); | |
| setQuiz(response.data); | |
| setAnswers({}); | |
| // Initialize answers for completed quiz | |
| if (response.data.completed_at) { | |
| const existingAnswers = {}; | |
| response.data.questions.forEach((q) => { | |
| if (q.user_answer) { | |
| existingAnswers[q.id] = q.user_answer; | |
| } | |
| }); | |
| setAnswers(existingAnswers); | |
| } | |
| } catch (error) { | |
| console.error('Error loading quiz:', error); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }; |
🤖 Prompt for AI Agents
In frontend/src/pages/QuizTake.jsx around lines 18 to 38, the answers state is
not cleared when loading a new quiz so stale answers persist across quizzes;
before optionally seeding answers for a completed quiz, call the setter to reset
answers to an empty object (e.g., setAnswers({})) so you clear old question IDs,
then only populate existingAnswers from response.data.questions when
response.data.completed_at is set; ensure this happens inside the try block
before seeding and does not rely on prior state.
| useEffect(() => { | ||
| loadData(); | ||
| }, [id]); | ||
|
|
||
| const loadData = async () => { | ||
| try { | ||
| const [subjectRes, progressRes] = await Promise.all([ | ||
| getSubject(id), | ||
| getSubjectProgress(id) | ||
| ]); | ||
| setSubject(subjectRes.data); | ||
| setProgress(progressRes.data); | ||
| } catch (error) { | ||
| console.error('Error loading data:', error); | ||
| } finally { | ||
| setLoading(false); | ||
| } | ||
| }; |
There was a problem hiding this comment.
Add loadData to useEffect dependencies or move it inside useEffect.
The useEffect hook calls loadData() but doesn't include it in the dependency array. This violates React Hooks rules and the react-hooks/exhaustive-deps ESLint rule, potentially causing stale closure bugs.
Preferred solution: Define loadData inside the useEffect:
useEffect(() => {
- loadData();
- }, [id]);
-
- const loadData = async () => {
- try {
- const [subjectRes, progressRes] = await Promise.all([
- getSubject(id),
- getSubjectProgress(id)
- ]);
- setSubject(subjectRes.data);
- setProgress(progressRes.data);
- } catch (error) {
- console.error('Error loading data:', error);
- } finally {
- setLoading(false);
- }
- };
+ const loadData = async () => {
+ try {
+ const [subjectRes, progressRes] = await Promise.all([
+ getSubject(id),
+ getSubjectProgress(id)
+ ]);
+ setSubject(subjectRes.data);
+ setProgress(progressRes.data);
+ } catch (error) {
+ console.error('Error loading data:', error);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ loadData();
+ }, [id]);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| useEffect(() => { | |
| loadData(); | |
| }, [id]); | |
| const loadData = async () => { | |
| try { | |
| const [subjectRes, progressRes] = await Promise.all([ | |
| getSubject(id), | |
| getSubjectProgress(id) | |
| ]); | |
| setSubject(subjectRes.data); | |
| setProgress(progressRes.data); | |
| } catch (error) { | |
| console.error('Error loading data:', error); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }; | |
| useEffect(() => { | |
| const loadData = async () => { | |
| try { | |
| const [subjectRes, progressRes] = await Promise.all([ | |
| getSubject(id), | |
| getSubjectProgress(id) | |
| ]); | |
| setSubject(subjectRes.data); | |
| setProgress(progressRes.data); | |
| } catch (error) { | |
| console.error('Error loading data:', error); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }; | |
| loadData(); | |
| }, [id]); |
🤖 Prompt for AI Agents
In frontend/src/pages/SubjectDetails.jsx around lines 12 to 29, the useEffect
calls loadData but doesn't include it in the dependency array which violates
React Hooks rules; define the async loadData function inside the useEffect (or
alternatively wrap it with useCallback and include it in deps) and then call it
immediately, ensuring you keep [id] in the dependency array and remove the
external loadData reference so ESLint no longer flags
react-hooks/exhaustive-deps.
| const API_BASE_URL = 'http://localhost:8000/api'; | ||
|
|
||
| const api = axios.create({ | ||
| baseURL: API_BASE_URL, | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| }, | ||
| }); |
There was a problem hiding this comment.
Externalize the API base URL
Hard-coding http://localhost:8000/api means any deployed build (Docker, staging, production, even a different dev port) will fail to reach the backend. Please read the base URL from configuration (e.g., import.meta.env.VITE_API_URL) and fall back to localhost only when the env var is absent.
-const API_BASE_URL = 'http://localhost:8000/api';
+const API_BASE_URL =
+ import.meta.env.VITE_API_URL?.replace(/\/$/, '') ?? 'http://localhost:8000/api';Then initialize Axios with baseURL: API_BASE_URL.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const API_BASE_URL = 'http://localhost:8000/api'; | |
| const api = axios.create({ | |
| baseURL: API_BASE_URL, | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| }); | |
| const API_BASE_URL = | |
| import.meta.env.VITE_API_URL?.replace(/\/$/, '') ?? 'http://localhost:8000/api'; | |
| const api = axios.create({ | |
| baseURL: API_BASE_URL, | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| }); |
🤖 Prompt for AI Agents
In frontend/src/services/api.js around lines 3 to 10, the API base URL is
hard-coded to 'http://localhost:8000/api'; change this to read from
configuration by setting API_BASE_URL = import.meta.env.VITE_API_URL ||
'http://localhost:8000/api' (or equivalent env access pattern), then pass that
API_BASE_URL into axios.create via baseURL: API_BASE_URL so deployments can
override the backend URL via VITE_API_URL while falling back to localhost.
| export const generateFlashcards = (subjectId, topic, numCards = 5) => | ||
| api.post(`/flashcards/generate?subject_id=${subjectId}&topic=${topic}&num_cards=${numCards}`); |
There was a problem hiding this comment.
Encode generation query params
Interpolating topic directly into the query string breaks when the topic contains &, ?, or = (e.g., “Signals & Systems”), so the backend receives truncated or incorrect values. Use Axios’ params option to handle encoding.
-export const generateFlashcards = (subjectId, topic, numCards = 5) =>
- api.post(`/flashcards/generate?subject_id=${subjectId}&topic=${topic}&num_cards=${numCards}`);
+export const generateFlashcards = (subjectId, topic, numCards = 5) =>
+ api.post('/flashcards/generate', null, {
+ params: { subject_id: subjectId, topic, num_cards: numCards },
+ });📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const generateFlashcards = (subjectId, topic, numCards = 5) => | |
| api.post(`/flashcards/generate?subject_id=${subjectId}&topic=${topic}&num_cards=${numCards}`); | |
| export const generateFlashcards = (subjectId, topic, numCards = 5) => | |
| api.post('/flashcards/generate', null, { | |
| params: { subject_id: subjectId, topic, num_cards: numCards }, | |
| }); |
🤖 Prompt for AI Agents
In frontend/src/services/api.js around lines 24-25, the function builds the
query string by interpolating topic (and other params) directly which breaks
when topic contains characters like '&', '?' or '='; change the API call to use
Axios' params option (pass an object with subject_id, topic and num_cards)
instead of manual string concatenation so Axios URL-encodes values correctly and
avoids truncated/incorrect backend values.
| #### 1. Clone the Repository | ||
| ```bash | ||
| cd /home/user/study | ||
| ``` |
There was a problem hiding this comment.
Fix the clone instructions.
Step “Clone the Repository” never actually clones anything—cd /home/user/study assumes a path that doesn’t exist yet. Anyone following the README will fail right here. Please add the missing git clone … command (and then cd into the freshly cloned directory).
-```bash
-cd /home/user/study
-```
+```bash
+git clone https://github.com/qwertystars/study.git
+cd study
+```🤖 Prompt for AI Agents
In README.md around lines 78 to 81, the "Clone the Repository" step only does
`cd /home/user/study` and never actually clones the repo; update the step to
first run a git clone command for the repository URL (e.g., git clone
https://github.com/qwertystars/study.git) and then cd into the newly created
directory (cd study) so the instructions create and enter the project folder
correctly.
Built a full-stack production-ready study assistant application with:
Backend (FastAPI + PostgreSQL + Ollama):
Frontend (React + Vite + TailwindCSS):
Key Features:
Documentation:
Tech Stack:
The application is designed for B.Tech CSE students at VIT Chennai but can be used for any educational context. All AI features run locally for privacy and no API costs.
Summary by CodeRabbit
Release Notes
New Features
Documentation